pycityagent 1.0.0__py3-none-any.whl → 2.0.0a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pycityagent/__init__.py +7 -3
- pycityagent/agent.py +180 -284
- pycityagent/economy/__init__.py +5 -0
- pycityagent/economy/econ_client.py +307 -0
- pycityagent/environment/__init__.py +7 -0
- pycityagent/environment/interact/interact.py +141 -0
- pycityagent/environment/sence/__init__.py +0 -0
- pycityagent/{brain → environment/sence}/static.py +1 -1
- pycityagent/environment/sidecar/__init__.py +8 -0
- pycityagent/environment/sidecar/sidecarv2.py +109 -0
- pycityagent/environment/sim/__init__.py +27 -0
- pycityagent/environment/sim/aoi_service.py +38 -0
- pycityagent/environment/sim/client.py +126 -0
- pycityagent/environment/sim/clock_service.py +43 -0
- pycityagent/environment/sim/economy_services.py +191 -0
- pycityagent/environment/sim/lane_service.py +110 -0
- pycityagent/environment/sim/light_service.py +120 -0
- pycityagent/environment/sim/person_service.py +294 -0
- pycityagent/environment/sim/road_service.py +38 -0
- pycityagent/environment/sim/social_service.py +58 -0
- pycityagent/environment/simulator.py +369 -0
- pycityagent/environment/utils/__init__.py +8 -0
- pycityagent/environment/utils/geojson.py +26 -0
- pycityagent/environment/utils/grpc.py +57 -0
- pycityagent/environment/utils/map_utils.py +157 -0
- pycityagent/environment/utils/protobuf.py +39 -0
- pycityagent/llm/__init__.py +6 -0
- pycityagent/llm/embedding.py +136 -0
- pycityagent/llm/llm.py +430 -0
- pycityagent/llm/llmconfig.py +15 -0
- pycityagent/llm/utils.py +6 -0
- pycityagent/memory/__init__.py +11 -0
- pycityagent/memory/const.py +41 -0
- pycityagent/memory/memory.py +453 -0
- pycityagent/memory/memory_base.py +168 -0
- pycityagent/memory/profile.py +165 -0
- pycityagent/memory/self_define.py +165 -0
- pycityagent/memory/state.py +173 -0
- pycityagent/memory/utils.py +27 -0
- pycityagent/message/__init__.py +0 -0
- pycityagent/simulation/__init__.py +7 -0
- pycityagent/simulation/interview.py +36 -0
- pycityagent/simulation/simulation.py +286 -0
- pycityagent/simulation/survey/__init__.py +9 -0
- pycityagent/simulation/survey/manager.py +67 -0
- pycityagent/simulation/survey/models.py +49 -0
- pycityagent/simulation/ui/__init__.py +3 -0
- pycityagent/simulation/ui/interface.py +602 -0
- pycityagent/utils/__init__.py +0 -0
- pycityagent/utils/decorators.py +89 -0
- pycityagent/utils/parsers/__init__.py +12 -0
- pycityagent/utils/parsers/code_block_parser.py +37 -0
- pycityagent/utils/parsers/json_parser.py +86 -0
- pycityagent/utils/parsers/parser_base.py +60 -0
- pycityagent/workflow/__init__.py +22 -0
- pycityagent/workflow/block.py +137 -0
- pycityagent/workflow/prompt.py +72 -0
- pycityagent/workflow/tool.py +246 -0
- pycityagent/workflow/trigger.py +66 -0
- pycityagent-2.0.0a1.dist-info/METADATA +208 -0
- pycityagent-2.0.0a1.dist-info/RECORD +65 -0
- {pycityagent-1.0.0.dist-info → pycityagent-2.0.0a1.dist-info}/WHEEL +1 -2
- pycityagent/ac/__init__.py +0 -6
- pycityagent/ac/ac.py +0 -50
- pycityagent/ac/action.py +0 -14
- pycityagent/ac/controled.py +0 -13
- pycityagent/ac/converse.py +0 -31
- pycityagent/ac/idle.py +0 -17
- pycityagent/ac/shop.py +0 -80
- pycityagent/ac/trip.py +0 -37
- pycityagent/brain/__init__.py +0 -10
- pycityagent/brain/brain.py +0 -52
- pycityagent/brain/brainfc.py +0 -10
- pycityagent/brain/memory.py +0 -541
- pycityagent/brain/persistence/social.py +0 -1
- pycityagent/brain/persistence/spatial.py +0 -14
- pycityagent/brain/reason/shop.py +0 -37
- pycityagent/brain/reason/social.py +0 -148
- pycityagent/brain/reason/trip.py +0 -67
- pycityagent/brain/reason/user.py +0 -122
- pycityagent/brain/retrive/social.py +0 -6
- pycityagent/brain/scheduler.py +0 -408
- pycityagent/brain/sence.py +0 -375
- pycityagent/cc/__init__.py +0 -5
- pycityagent/cc/cc.py +0 -102
- pycityagent/cc/conve.py +0 -6
- pycityagent/cc/idle.py +0 -20
- pycityagent/cc/shop.py +0 -6
- pycityagent/cc/trip.py +0 -13
- pycityagent/cc/user.py +0 -13
- pycityagent/hubconnector/__init__.py +0 -3
- pycityagent/hubconnector/hubconnector.py +0 -137
- pycityagent/image/__init__.py +0 -3
- pycityagent/image/image.py +0 -158
- pycityagent/simulator.py +0 -161
- pycityagent/st/__init__.py +0 -4
- pycityagent/st/st.py +0 -96
- pycityagent/urbanllm/__init__.py +0 -3
- pycityagent/urbanllm/urbanllm.py +0 -132
- pycityagent-1.0.0.dist-info/LICENSE +0 -21
- pycityagent-1.0.0.dist-info/METADATA +0 -181
- pycityagent-1.0.0.dist-info/RECORD +0 -48
- pycityagent-1.0.0.dist-info/top_level.txt +0 -1
- /pycityagent/{brain/persistence/__init__.py → config.py} +0 -0
- /pycityagent/{brain/reason → environment/interact}/__init__.py +0 -0
- /pycityagent/{brain/retrive → environment/message}/__init__.py +0 -0
@@ -0,0 +1,369 @@
|
|
1
|
+
"""Simulator: 城市模拟器类及其定义"""
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
import logging
|
5
|
+
from datetime import datetime, timedelta
|
6
|
+
from typing import Any, Optional, Tuple, Union, cast
|
7
|
+
|
8
|
+
from mosstool.type import TripMode
|
9
|
+
from pycitydata.map import Map as SimMap
|
10
|
+
from pycityproto.city.person.v2 import person_pb2 as person_pb2
|
11
|
+
from pycityproto.city.person.v2 import person_service_pb2 as person_service
|
12
|
+
|
13
|
+
from .sim import CityClient
|
14
|
+
|
15
|
+
|
16
|
+
class Simulator:
|
17
|
+
"""
|
18
|
+
- 模拟器主类
|
19
|
+
- Simulator Class
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(self, config, secure: bool = False) -> None:
|
23
|
+
self.config = config
|
24
|
+
"""
|
25
|
+
- 模拟器配置
|
26
|
+
- simulator config
|
27
|
+
"""
|
28
|
+
|
29
|
+
self._client = CityClient(self.config["simulator"]["server"], secure=secure)
|
30
|
+
"""
|
31
|
+
- 模拟器grpc客户端
|
32
|
+
- grpc client of simulator
|
33
|
+
"""
|
34
|
+
|
35
|
+
self.map = SimMap(
|
36
|
+
mongo_uri=config["map_request"]["mongo_uri"],
|
37
|
+
mongo_db=config["map_request"]["mongo_db"],
|
38
|
+
mongo_coll=config["map_request"]["mongo_coll"],
|
39
|
+
cache_dir=config["map_request"]["cache_dir"],
|
40
|
+
)
|
41
|
+
"""
|
42
|
+
- 模拟器地图对象
|
43
|
+
- Simulator map object
|
44
|
+
"""
|
45
|
+
|
46
|
+
self.pois_matrix: dict[str, list[list[list]]] = {}
|
47
|
+
"""
|
48
|
+
pois的基于区块的划分——方便快速粗略地查询poi
|
49
|
+
通过Simulator.set_pois_matrix()初始化
|
50
|
+
"""
|
51
|
+
|
52
|
+
self.time: int = 0
|
53
|
+
"""
|
54
|
+
- 模拟城市当前时间
|
55
|
+
- The current time of simulator
|
56
|
+
"""
|
57
|
+
self.poi_cate = {
|
58
|
+
"10": "eat",
|
59
|
+
"13": "shopping",
|
60
|
+
"18": "sports",
|
61
|
+
"22": "excursion",
|
62
|
+
"16": "entertainment",
|
63
|
+
"20": "medical tratment",
|
64
|
+
"14": "trivialities",
|
65
|
+
"25": "financial",
|
66
|
+
"12": "government and political services",
|
67
|
+
"23": "cultural institutions",
|
68
|
+
"28": "residence",
|
69
|
+
}
|
70
|
+
self.map_x_gap = None
|
71
|
+
self.map_y_gap = None
|
72
|
+
self._bbox: Tuple[float, float, float, float] = (-1, -1, -1, -1)
|
73
|
+
self.poi_matrix_centers = []
|
74
|
+
self._lock = asyncio.Lock()
|
75
|
+
|
76
|
+
# * Agent相关
|
77
|
+
def FindAgentsByArea(self, req: dict, status=None):
|
78
|
+
"""
|
79
|
+
通过区域范围查找agent/person
|
80
|
+
Get agents/persons in the provided area
|
81
|
+
|
82
|
+
Args:
|
83
|
+
- req (dict): 用于描述区域的请求 https://cityproto.sim.fiblab.net/#city.person.1.GetPersonByLongLatBBoxRequest
|
84
|
+
- status (int): 用于限制agent/person状态 if 'status' is not None, then you get those persons in 'status' https://cityproto.sim.fiblab.net/#city.agent.v2.Status
|
85
|
+
|
86
|
+
Returns:
|
87
|
+
- https://cityproto.sim.fiblab.net/#city.person.1.GetPersonByLongLatBBoxResponse
|
88
|
+
"""
|
89
|
+
loop = asyncio.get_event_loop()
|
90
|
+
resp = loop.run_until_complete(
|
91
|
+
self._client.person_service.GetPersonByLongLatBBox(req=req)
|
92
|
+
)
|
93
|
+
loop.close()
|
94
|
+
if status == None:
|
95
|
+
return resp
|
96
|
+
else:
|
97
|
+
motions = []
|
98
|
+
for agent in resp.motions: # type: ignore
|
99
|
+
if agent.status in status:
|
100
|
+
motions.append(agent)
|
101
|
+
resp.motions = motions # type: ignore
|
102
|
+
return resp
|
103
|
+
|
104
|
+
def set_poi_matrix(
|
105
|
+
self, row_number: int = 12, col_number: int = 10, radius: int = 10000
|
106
|
+
):
|
107
|
+
"""
|
108
|
+
初始化pois_matrix
|
109
|
+
|
110
|
+
Args:
|
111
|
+
- map (dict): 地图参数
|
112
|
+
east, west, north, south
|
113
|
+
- row_number (int): 行数
|
114
|
+
- col_number (int): 列数
|
115
|
+
- radius (int): 搜索半径, 单位m
|
116
|
+
"""
|
117
|
+
self.matrix_map = self.map
|
118
|
+
map_header = self.matrix_map.header
|
119
|
+
min_x, min_y, max_x, max_y = (
|
120
|
+
map_header[k] for k in ("west", "south", "east", "north")
|
121
|
+
)
|
122
|
+
self._bbox = (min_x, min_y, max_x, max_y)
|
123
|
+
logging.info(
|
124
|
+
f"Building Poi searching matrix, Row_number: {row_number}, Col_number: {col_number}, Radius: {radius}m"
|
125
|
+
)
|
126
|
+
self.map_x_gap = map_x_gap = (max_x - min_x) / col_number
|
127
|
+
self.map_y_gap = map_y_gap = (max_y - min_y) / row_number
|
128
|
+
self.poi_matrix_centers = poi_matrix_centers = [[] for _ in range(row_number)]
|
129
|
+
for i, i_centers in enumerate(poi_matrix_centers):
|
130
|
+
for j in range(col_number):
|
131
|
+
center_x = map_header["west"] + map_x_gap * j + map_x_gap / 2
|
132
|
+
center_y = map_header["south"] + map_y_gap * i + map_y_gap / 2
|
133
|
+
i_centers.append((center_x, center_y))
|
134
|
+
|
135
|
+
for pre, _ in self.poi_cate.items():
|
136
|
+
logging.info(f"Building matrix for Poi category: {pre}")
|
137
|
+
self.pois_matrix[pre] = []
|
138
|
+
for row_centers in self.poi_matrix_centers:
|
139
|
+
row_pois = []
|
140
|
+
for center in row_centers:
|
141
|
+
pois = self.map.query_pois(
|
142
|
+
center=center, radius=radius, category_prefix=pre
|
143
|
+
)
|
144
|
+
row_pois.append(pois)
|
145
|
+
self.pois_matrix[pre].append(row_pois)
|
146
|
+
logging.info("Finished")
|
147
|
+
|
148
|
+
def get_pois_from_matrix(self, center: Tuple[float, float], prefix: str):
|
149
|
+
"""
|
150
|
+
从poi搜索矩阵中快速获取poi
|
151
|
+
|
152
|
+
Args:
|
153
|
+
- center (Tuple[float, float]): 位置信息
|
154
|
+
- prefix (str): 类型前缀
|
155
|
+
"""
|
156
|
+
(min_x, min_y, max_x, max_y) = self._bbox
|
157
|
+
_x, _y = center
|
158
|
+
if self.map_x_gap is None or self.map_y_gap is None:
|
159
|
+
logging.warning("Set Poi Matrix first")
|
160
|
+
return
|
161
|
+
elif prefix not in self.poi_cate:
|
162
|
+
logging.warning(f"Wrong prefix, only {self.poi_cate.keys()} is usable")
|
163
|
+
return
|
164
|
+
elif not (min_x < _x < max_x) or not (min_y < _y < max_y):
|
165
|
+
logging.warning("Wrong center")
|
166
|
+
return
|
167
|
+
|
168
|
+
# 矩阵匹配
|
169
|
+
rows = int((_y - min_y) / self.map_y_gap)
|
170
|
+
cols = int((_x - min_x) / self.map_x_gap)
|
171
|
+
pois = self.pois_matrix[prefix][rows][cols]
|
172
|
+
return pois
|
173
|
+
|
174
|
+
def get_cat_from_pois(self, pois: list):
|
175
|
+
cat_2_num = {}
|
176
|
+
for poi in pois:
|
177
|
+
cate = poi["category"][:2]
|
178
|
+
if cate not in self.poi_cate:
|
179
|
+
continue
|
180
|
+
if cate in cat_2_num:
|
181
|
+
cat_2_num[cate] += 1
|
182
|
+
else:
|
183
|
+
cat_2_num[cate] = 1
|
184
|
+
max_cat = ""
|
185
|
+
max_num = 0
|
186
|
+
for key in cat_2_num.keys():
|
187
|
+
if cat_2_num[key] > max_num:
|
188
|
+
max_num = cat_2_num[key]
|
189
|
+
max_cat = self.poi_cate[key]
|
190
|
+
return max_cat
|
191
|
+
|
192
|
+
def get_poi_matrix_in_rec(
|
193
|
+
self,
|
194
|
+
center: Tuple[float, float],
|
195
|
+
radius: int = 2500,
|
196
|
+
rows: int = 5,
|
197
|
+
cols: int = 5,
|
198
|
+
):
|
199
|
+
"""
|
200
|
+
获取以center为中心的正方形区域内的poi集合
|
201
|
+
|
202
|
+
Args:
|
203
|
+
- center (Tuple[float, float]): 中心位置点
|
204
|
+
- radius (int): 半径
|
205
|
+
"""
|
206
|
+
north = center[1] + radius
|
207
|
+
south = center[1] - radius
|
208
|
+
west = center[0] - radius
|
209
|
+
east = center[0] + radius
|
210
|
+
x_gap = (east - west) / cols
|
211
|
+
y_gap = (north - south) / rows
|
212
|
+
matrix = []
|
213
|
+
for i in range(rows):
|
214
|
+
matrix.append([])
|
215
|
+
for j in range(cols):
|
216
|
+
matrix[i].append([])
|
217
|
+
for poi in self.map.pois.values():
|
218
|
+
x = poi["position"]["x"]
|
219
|
+
y = poi["position"]["y"]
|
220
|
+
if west < x < east and south < y < north:
|
221
|
+
row_index = int((y - south) / x_gap)
|
222
|
+
col_index = int((x - west) / y_gap)
|
223
|
+
matrix[row_index][col_index].append(poi)
|
224
|
+
matrix_type = []
|
225
|
+
for i in range(rows):
|
226
|
+
for j in range(cols):
|
227
|
+
matrix_type.append(self.get_cat_from_pois(matrix[i][j]))
|
228
|
+
poi_total_number = []
|
229
|
+
poi_type_number = []
|
230
|
+
for i in range(rows):
|
231
|
+
for j in range(cols):
|
232
|
+
poi_total_number.append(len(matrix[i][j]))
|
233
|
+
number = 0
|
234
|
+
for poi in matrix[i][j]:
|
235
|
+
if (
|
236
|
+
poi["category"][:2] in self.poi_cate.keys()
|
237
|
+
and self.poi_cate[poi["category"][:2]]
|
238
|
+
== matrix_type[i * cols + j]
|
239
|
+
):
|
240
|
+
number += 1
|
241
|
+
poi_type_number.append(number)
|
242
|
+
|
243
|
+
return matrix, matrix_type, poi_total_number, poi_type_number
|
244
|
+
|
245
|
+
async def GetTime(
|
246
|
+
self, format_time: bool = False, format: str = "%H:%M:%S"
|
247
|
+
) -> Union[int, str]:
|
248
|
+
"""
|
249
|
+
获取模拟器当前时间 Get current time of simulator
|
250
|
+
默认返回以00:00:00为始的, 以s为单位的时间(int)
|
251
|
+
支持格式化时间
|
252
|
+
|
253
|
+
Args:
|
254
|
+
- format_time (bool): 是否格式化 format or not
|
255
|
+
- format (str): 格式化模板,默认为"Hour:Minute:Second" the formation
|
256
|
+
|
257
|
+
Returns:
|
258
|
+
- time Union[int, str]: 时间 time in second(int) or formatted time(str)
|
259
|
+
"""
|
260
|
+
t_sec = await self._client.clock_service.Now({})
|
261
|
+
t_sec = cast(dict[str, int], t_sec)
|
262
|
+
self.time = t_sec["t"]
|
263
|
+
if format_time:
|
264
|
+
current_date = datetime.now().date()
|
265
|
+
start_of_day = datetime.combine(current_date, datetime.min.time())
|
266
|
+
current_time = start_of_day + timedelta(seconds=t_sec["t"])
|
267
|
+
formatted_time = current_time.strftime(format)
|
268
|
+
return formatted_time
|
269
|
+
else:
|
270
|
+
return t_sec["t"]
|
271
|
+
|
272
|
+
async def GetPerson(self, person_id: int) -> dict:
|
273
|
+
return await self._client.person_service.GetPerson(
|
274
|
+
req={"person_id": person_id}
|
275
|
+
) # type:ignore
|
276
|
+
|
277
|
+
async def AddPerson(self, person: Any) -> dict:
|
278
|
+
if isinstance(person, person_pb2.Person):
|
279
|
+
req = person_service.AddPersonRequest(person=person)
|
280
|
+
else:
|
281
|
+
req = person
|
282
|
+
return await self._client.person_service.AddPerson(req) # type:ignore
|
283
|
+
|
284
|
+
async def SetAoiSchedules(
|
285
|
+
self,
|
286
|
+
person_id: int,
|
287
|
+
target_positions: Union[
|
288
|
+
list[Union[int, tuple[int, int]]], Union[int, tuple[int, int]]
|
289
|
+
],
|
290
|
+
departure_times: Optional[list[float]] = None,
|
291
|
+
modes: Optional[list[TripMode]] = None,
|
292
|
+
):
|
293
|
+
cur_time = float(await self.GetTime())
|
294
|
+
if not isinstance(target_positions, list):
|
295
|
+
target_positions = [target_positions]
|
296
|
+
if departure_times is None:
|
297
|
+
departure_times = [cur_time for _ in range(len(target_positions))]
|
298
|
+
else:
|
299
|
+
for _ in range(len(target_positions) - len(departure_times)):
|
300
|
+
departure_times.append(cur_time)
|
301
|
+
if modes is None:
|
302
|
+
modes = [
|
303
|
+
TripMode.TRIP_MODE_DRIVE_ONLY for _ in range(len(target_positions))
|
304
|
+
]
|
305
|
+
else:
|
306
|
+
for _ in range(len(target_positions) - len(modes)):
|
307
|
+
modes.append(TripMode.TRIP_MODE_DRIVE_ONLY)
|
308
|
+
_schedules = []
|
309
|
+
for target_pos, _time, _mode in zip(target_positions, departure_times, modes):
|
310
|
+
if isinstance(target_pos, int):
|
311
|
+
aoi_id = target_pos
|
312
|
+
end = {
|
313
|
+
"aoi_position": {
|
314
|
+
"aoi_id": aoi_id,
|
315
|
+
}
|
316
|
+
}
|
317
|
+
else:
|
318
|
+
aoi_id, poi_id = target_pos
|
319
|
+
end = {"aoi_position": {"aoi_id": aoi_id, "poi_id": poi_id}}
|
320
|
+
# activity = ""
|
321
|
+
trips = [
|
322
|
+
{
|
323
|
+
"mode": _mode,
|
324
|
+
"end": end,
|
325
|
+
"departure_time": _time,
|
326
|
+
},
|
327
|
+
]
|
328
|
+
_schedules.append(
|
329
|
+
{"trips": trips, "loop_count": 1, "departure_time": _time}
|
330
|
+
)
|
331
|
+
req = {"person_id": person_id, "schedules": _schedules}
|
332
|
+
await self._client.person_service.SetSchedule(req)
|
333
|
+
|
334
|
+
async def ResetPersonPosition(
|
335
|
+
self,
|
336
|
+
person_id: int,
|
337
|
+
aoi_id: Optional[int] = None,
|
338
|
+
poi_id: Optional[int] = None,
|
339
|
+
lane_id: Optional[int] = None,
|
340
|
+
s: Optional[float] = None,
|
341
|
+
):
|
342
|
+
reset_position = {}
|
343
|
+
if aoi_id is not None:
|
344
|
+
reset_position["aoi_position"] = {"aoi_id": aoi_id}
|
345
|
+
if poi_id is not None:
|
346
|
+
reset_position["aoi_position"]["poi_id"] = poi_id
|
347
|
+
logging.debug(
|
348
|
+
f"Setting person {person_id} pos to AoiPosition {reset_position}"
|
349
|
+
)
|
350
|
+
await self._client.person_service.ResetPersonPosition(
|
351
|
+
{"person_id": person_id, "position": reset_position}
|
352
|
+
)
|
353
|
+
elif lane_id is not None:
|
354
|
+
reset_position["lane_position"] = {
|
355
|
+
"lane_id": lane_id,
|
356
|
+
"s": 0.0,
|
357
|
+
}
|
358
|
+
if s is not None:
|
359
|
+
reset_position["lane_position"]["s"] = s
|
360
|
+
logging.debug(
|
361
|
+
f"Setting person {person_id} pos to LanePosition {reset_position}"
|
362
|
+
)
|
363
|
+
await self._client.person_service.ResetPersonPosition(
|
364
|
+
{"person_id": person_id, "position": reset_position}
|
365
|
+
)
|
366
|
+
else:
|
367
|
+
logging.debug(
|
368
|
+
f"Neither aoi or lane pos provided for person {person_id} position reset!!"
|
369
|
+
)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
from typing import List
|
2
|
+
|
3
|
+
__all__ = ["wrap_feature_collection"]
|
4
|
+
|
5
|
+
|
6
|
+
def wrap_feature_collection(features: List[dict], name: str):
|
7
|
+
"""
|
8
|
+
将 GeoJSON Feature 集合包装为 FeatureCollection
|
9
|
+
Wrap GeoJSON Feature collection as FeatureCollection
|
10
|
+
|
11
|
+
Args:
|
12
|
+
- features: GeoJSON Feature 集合。GeoJSON Feature collection.
|
13
|
+
- name: FeatureCollection 名称。FeatureCollection name.
|
14
|
+
|
15
|
+
Returns:
|
16
|
+
- dict: GeoJSON FeatureCollection
|
17
|
+
"""
|
18
|
+
return {
|
19
|
+
"type": "FeatureCollection",
|
20
|
+
"name": name,
|
21
|
+
"crs": {
|
22
|
+
"type": "name",
|
23
|
+
"properties": {"name": "urn:ogc:def:crs:OGC:1.3:CRS84"},
|
24
|
+
},
|
25
|
+
"features": features,
|
26
|
+
}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import grpc
|
2
|
+
|
3
|
+
__all__ = ["create_channel", "create_aio_channel"]
|
4
|
+
|
5
|
+
|
6
|
+
def create_channel(server_address: str, secure: bool = False) -> grpc.Channel:
|
7
|
+
"""
|
8
|
+
创建一个grpc的channel
|
9
|
+
Create a grpc channel
|
10
|
+
|
11
|
+
Args:
|
12
|
+
- server_address (str): 服务器地址。server address.
|
13
|
+
- secure (bool, optional): 是否使用安全连接. Defaults to False. Whether to use a secure connection. Defaults to False.
|
14
|
+
|
15
|
+
Returns:
|
16
|
+
- grpc.Channel: grpc的channel。grpc channel.
|
17
|
+
"""
|
18
|
+
if server_address.startswith("http://"):
|
19
|
+
server_address = server_address.split("//")[1]
|
20
|
+
if secure:
|
21
|
+
raise ValueError("secure channel must use `https` or not use `http`")
|
22
|
+
elif server_address.startswith("https://"):
|
23
|
+
server_address = server_address.split("//")[1]
|
24
|
+
if not secure:
|
25
|
+
secure = True
|
26
|
+
|
27
|
+
if secure:
|
28
|
+
return grpc.secure_channel(server_address, grpc.ssl_channel_credentials())
|
29
|
+
else:
|
30
|
+
return grpc.insecure_channel(server_address)
|
31
|
+
|
32
|
+
|
33
|
+
def create_aio_channel(server_address: str, secure: bool = False) -> grpc.aio.Channel:
|
34
|
+
"""
|
35
|
+
创建一个grpc的异步channel
|
36
|
+
Create a grpc asynchronous channel
|
37
|
+
|
38
|
+
Args:
|
39
|
+
- server_address (str): 服务器地址。server address.
|
40
|
+
- secure (bool, optional): 是否使用安全连接. Defaults to False. Whether to use a secure connection. Defaults to False.
|
41
|
+
|
42
|
+
Returns:
|
43
|
+
- grpc.aio.Channel: grpc的异步channel。grpc asynchronous channel.
|
44
|
+
"""
|
45
|
+
if server_address.startswith("http://"):
|
46
|
+
server_address = server_address.split("//")[1]
|
47
|
+
if secure:
|
48
|
+
raise ValueError("secure channel must use `https` or not use `http`")
|
49
|
+
elif server_address.startswith("https://"):
|
50
|
+
server_address = server_address.split("//")[1]
|
51
|
+
if not secure:
|
52
|
+
secure = True
|
53
|
+
|
54
|
+
if secure:
|
55
|
+
return grpc.aio.secure_channel(server_address, grpc.ssl_channel_credentials())
|
56
|
+
else:
|
57
|
+
return grpc.aio.insecure_channel(server_address)
|
@@ -0,0 +1,157 @@
|
|
1
|
+
import math
|
2
|
+
from typing import Dict, List, Literal, Optional, Tuple, Union
|
3
|
+
|
4
|
+
import numpy as np
|
5
|
+
|
6
|
+
|
7
|
+
def get_angle(x, y):
|
8
|
+
return math.atan2(y, x) * 180 / math.pi
|
9
|
+
|
10
|
+
|
11
|
+
def point_on_line_given_distance(start_node, end_node, distance):
|
12
|
+
"""
|
13
|
+
Given two points (start_point and end_point) defining a line, and a distance s to travel along the line,
|
14
|
+
return the coordinates of the point reached after traveling s units along the line, starting from start_point.
|
15
|
+
|
16
|
+
Args:
|
17
|
+
start_point (tuple): Tuple of (x, y) representing the starting point on the line.
|
18
|
+
end_point (tuple): Tuple of (x, y) representing the ending point on the line.
|
19
|
+
distance (float): Distance to travel along the line, starting from start_point.
|
20
|
+
|
21
|
+
Returns:
|
22
|
+
tuple: Tuple of (x, y) representing the new point reached after traveling s units along the line.
|
23
|
+
"""
|
24
|
+
|
25
|
+
x1, y1 = start_node["x"], start_node["y"]
|
26
|
+
x2, y2 = end_node["x"], end_node["y"]
|
27
|
+
|
28
|
+
# Calculate the slope m and the y-intercept b of the line
|
29
|
+
if x1 == x2:
|
30
|
+
# Vertical line, distance is only along the y-axis
|
31
|
+
return (x1, y1 + distance if distance >= 0 else y1 - abs(distance))
|
32
|
+
else:
|
33
|
+
m = (y2 - y1) / (x2 - x1)
|
34
|
+
b = y1 - m * x1
|
35
|
+
|
36
|
+
# Calculate the direction vector (dx, dy) along the line
|
37
|
+
dx = (x2 - x1) / math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
|
38
|
+
dy = (y2 - y1) / math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
|
39
|
+
|
40
|
+
# Scale the direction vector by the given distance
|
41
|
+
scaled_dx = dx * distance
|
42
|
+
scaled_dy = dy * distance
|
43
|
+
|
44
|
+
# Calculate the new point's coordinates
|
45
|
+
x = x1 + scaled_dx
|
46
|
+
y = y1 + scaled_dy
|
47
|
+
|
48
|
+
return [x, y]
|
49
|
+
|
50
|
+
|
51
|
+
def get_key_index_in_lane(
|
52
|
+
nodes: List[Dict[str, float]],
|
53
|
+
distance: float,
|
54
|
+
direction: Union[Literal["front"], Literal["back"]],
|
55
|
+
) -> int:
|
56
|
+
if direction == "front":
|
57
|
+
_nodes = [n for n in nodes]
|
58
|
+
_index_offset, _index_factor = 0, 1
|
59
|
+
elif direction == "back":
|
60
|
+
_nodes = [n for n in nodes[::-1]]
|
61
|
+
_index_offset, _index_factor = len(_nodes) - 1, -1
|
62
|
+
else:
|
63
|
+
raise ValueError(f"Invalid direction type {direction}!")
|
64
|
+
_lane_points: List[Tuple[float, float, float]] = [
|
65
|
+
(n["x"], n["y"], n.get("z", 0)) for n in _nodes
|
66
|
+
]
|
67
|
+
_line_lengths: List[float] = [0.0 for _ in range(len(_nodes))]
|
68
|
+
_s = 0.0
|
69
|
+
for i, (cur_p, next_p) in enumerate(zip(_lane_points[:-1], _lane_points[1:])):
|
70
|
+
_s += math.hypot(next_p[0] - cur_p[0], next_p[1] - cur_p[1])
|
71
|
+
_line_lengths[i + 1] = _s
|
72
|
+
s = np.clip(distance, _line_lengths[0], _line_lengths[-1])
|
73
|
+
_key_index = 0
|
74
|
+
for (
|
75
|
+
prev_s,
|
76
|
+
cur_s,
|
77
|
+
) in zip(_line_lengths[:-1], _line_lengths[1:]):
|
78
|
+
if prev_s <= s < cur_s:
|
79
|
+
break
|
80
|
+
_key_index += 1
|
81
|
+
return _index_offset + _index_factor * _key_index
|
82
|
+
|
83
|
+
|
84
|
+
def get_xy_in_lane(
|
85
|
+
nodes: List[Dict[str, float]],
|
86
|
+
distance: float,
|
87
|
+
direction: Union[Literal["front"], Literal["back"]],
|
88
|
+
) -> Tuple[float, float]:
|
89
|
+
if direction == "front":
|
90
|
+
_nodes = [n for n in nodes]
|
91
|
+
elif direction == "back":
|
92
|
+
_nodes = [n for n in nodes[::-1]]
|
93
|
+
else:
|
94
|
+
raise ValueError(f"Invalid direction type {direction}!")
|
95
|
+
_lane_points: List[Tuple[float, float, float]] = [
|
96
|
+
(n["x"], n["y"], n.get("z", 0)) for n in _nodes
|
97
|
+
]
|
98
|
+
_line_lengths: List[float] = [0.0 for _ in range(len(_nodes))]
|
99
|
+
_s = 0.0
|
100
|
+
for i, (cur_p, next_p) in enumerate(zip(_lane_points[:-1], _lane_points[1:])):
|
101
|
+
_s += math.hypot(next_p[0] - cur_p[0], next_p[1] - cur_p[1])
|
102
|
+
_line_lengths[i + 1] = _s
|
103
|
+
s = np.clip(distance, _line_lengths[0], _line_lengths[-1])
|
104
|
+
for prev_s, prev_p, cur_s, cur_p in zip(
|
105
|
+
_line_lengths[:-1],
|
106
|
+
_lane_points[:-1],
|
107
|
+
_line_lengths[1:],
|
108
|
+
_lane_points[1:],
|
109
|
+
):
|
110
|
+
if prev_s <= s < cur_s:
|
111
|
+
_delta_x, _delta_y, _delta_z = [
|
112
|
+
cur_p[_idx] - prev_p[_idx] for _idx in [0, 1, 2]
|
113
|
+
]
|
114
|
+
_blend_x, _blend_y, _blend_z = [prev_p[_idx] for _idx in [0, 1, 2]]
|
115
|
+
_ratio = (s - prev_s) / (cur_s - prev_s)
|
116
|
+
return (
|
117
|
+
_blend_x + _ratio * _delta_x,
|
118
|
+
_blend_y + _ratio * _delta_y,
|
119
|
+
_blend_z + _ratio * _delta_z,
|
120
|
+
)[:2]
|
121
|
+
return _lane_points[-1][:2]
|
122
|
+
|
123
|
+
|
124
|
+
def get_direction_by_s(
|
125
|
+
nodes: List[Dict[str, float]],
|
126
|
+
distance: float,
|
127
|
+
direction: Union[Literal["front"], Literal["back"]],
|
128
|
+
) -> float:
|
129
|
+
if direction == "front":
|
130
|
+
_nodes = [n for n in nodes]
|
131
|
+
elif direction == "back":
|
132
|
+
_nodes = [n for n in nodes[::-1]]
|
133
|
+
else:
|
134
|
+
raise ValueError(f"Invalid direction type {direction}!")
|
135
|
+
_lane_points: List[Tuple[float, float, float]] = [
|
136
|
+
(n["x"], n["y"], n.get("z", 0)) for n in _nodes
|
137
|
+
]
|
138
|
+
_line_lengths: List[float] = [0.0 for _ in range(len(_nodes))]
|
139
|
+
_line_directions: List[Tuple[float, float]] = []
|
140
|
+
_s = 0.0
|
141
|
+
for i, (cur_p, next_p) in enumerate(zip(_lane_points[:-1], _lane_points[1:])):
|
142
|
+
_s += math.hypot(next_p[0] - cur_p[0], next_p[1] - cur_p[1])
|
143
|
+
_line_lengths[i + 1] = _s
|
144
|
+
for i, (cur_p, next_p) in enumerate(zip(_lane_points[:-1], _lane_points[1:])):
|
145
|
+
_direction = math.atan2(next_p[1] - cur_p[1], next_p[0] - cur_p[0])
|
146
|
+
_pitch = math.atan2(
|
147
|
+
next_p[2] - cur_p[2],
|
148
|
+
math.hypot(next_p[0] - cur_p[0], next_p[1] - cur_p[1]),
|
149
|
+
)
|
150
|
+
_line_directions.append((_direction / math.pi * 180, _pitch / math.pi * 180))
|
151
|
+
s = np.clip(distance, _line_lengths[0], _line_lengths[-1])
|
152
|
+
for prev_s, cur_s, direcs in zip(
|
153
|
+
_line_lengths[:-1], _line_lengths[1:], _line_directions
|
154
|
+
):
|
155
|
+
if prev_s <= s < cur_s:
|
156
|
+
return direcs[0]
|
157
|
+
return _line_directions[-1][0]
|
@@ -0,0 +1,39 @@
|
|
1
|
+
from typing import Any, Awaitable, TypeVar, Union, Dict
|
2
|
+
from google.protobuf.message import Message
|
3
|
+
from google.protobuf.json_format import MessageToDict
|
4
|
+
|
5
|
+
__all__ = ["parse", "async_parse"]
|
6
|
+
|
7
|
+
T = TypeVar("T", bound=Message)
|
8
|
+
|
9
|
+
|
10
|
+
def parse(res: T, dict_return: bool) -> Union[Dict[str, Any], T]:
|
11
|
+
"""
|
12
|
+
将Protobuf返回值转换为dict或者原始值
|
13
|
+
Convert Protobuf return value to dict or original value
|
14
|
+
"""
|
15
|
+
if dict_return:
|
16
|
+
return MessageToDict(
|
17
|
+
res,
|
18
|
+
including_default_value_fields=True,
|
19
|
+
preserving_proto_field_name=True,
|
20
|
+
use_integers_for_enums=True,
|
21
|
+
)
|
22
|
+
else:
|
23
|
+
return res
|
24
|
+
|
25
|
+
|
26
|
+
async def async_parse(res: Awaitable[T], dict_return: bool) -> Union[Dict[str, Any], T]:
|
27
|
+
"""
|
28
|
+
将Protobuf await返回值转换为dict或者原始值
|
29
|
+
Convert Protobuf await return value to dict or original value
|
30
|
+
"""
|
31
|
+
if dict_return:
|
32
|
+
return MessageToDict(
|
33
|
+
await res,
|
34
|
+
including_default_value_fields=True,
|
35
|
+
preserving_proto_field_name=True,
|
36
|
+
use_integers_for_enums=True,
|
37
|
+
)
|
38
|
+
else:
|
39
|
+
return await res
|