pycityagent 2.0.0a43__cp312-cp312-macosx_11_0_arm64.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 +23 -0
- pycityagent/agent.py +833 -0
- pycityagent/cli/wrapper.py +44 -0
- pycityagent/economy/__init__.py +5 -0
- pycityagent/economy/econ_client.py +355 -0
- pycityagent/environment/__init__.py +7 -0
- pycityagent/environment/interact/__init__.py +0 -0
- pycityagent/environment/interact/interact.py +198 -0
- pycityagent/environment/message/__init__.py +0 -0
- pycityagent/environment/sence/__init__.py +0 -0
- pycityagent/environment/sence/static.py +416 -0
- pycityagent/environment/sidecar/__init__.py +8 -0
- pycityagent/environment/sidecar/sidecarv2.py +109 -0
- pycityagent/environment/sim/__init__.py +29 -0
- pycityagent/environment/sim/aoi_service.py +39 -0
- pycityagent/environment/sim/client.py +126 -0
- pycityagent/environment/sim/clock_service.py +44 -0
- pycityagent/environment/sim/economy_services.py +192 -0
- pycityagent/environment/sim/lane_service.py +111 -0
- pycityagent/environment/sim/light_service.py +122 -0
- pycityagent/environment/sim/person_service.py +295 -0
- pycityagent/environment/sim/road_service.py +39 -0
- pycityagent/environment/sim/sim_env.py +145 -0
- pycityagent/environment/sim/social_service.py +59 -0
- pycityagent/environment/simulator.py +331 -0
- pycityagent/environment/utils/__init__.py +14 -0
- pycityagent/environment/utils/base64.py +16 -0
- pycityagent/environment/utils/const.py +244 -0
- pycityagent/environment/utils/geojson.py +24 -0
- pycityagent/environment/utils/grpc.py +57 -0
- pycityagent/environment/utils/map_utils.py +157 -0
- pycityagent/environment/utils/port.py +11 -0
- pycityagent/environment/utils/protobuf.py +41 -0
- pycityagent/llm/__init__.py +11 -0
- pycityagent/llm/embeddings.py +231 -0
- pycityagent/llm/llm.py +377 -0
- pycityagent/llm/llmconfig.py +13 -0
- pycityagent/llm/utils.py +6 -0
- pycityagent/memory/__init__.py +13 -0
- pycityagent/memory/const.py +43 -0
- pycityagent/memory/faiss_query.py +302 -0
- pycityagent/memory/memory.py +448 -0
- pycityagent/memory/memory_base.py +170 -0
- pycityagent/memory/profile.py +165 -0
- pycityagent/memory/self_define.py +165 -0
- pycityagent/memory/state.py +173 -0
- pycityagent/memory/utils.py +28 -0
- pycityagent/message/__init__.py +3 -0
- pycityagent/message/messager.py +88 -0
- pycityagent/metrics/__init__.py +6 -0
- pycityagent/metrics/mlflow_client.py +147 -0
- pycityagent/metrics/utils/const.py +0 -0
- pycityagent/pycityagent-sim +0 -0
- pycityagent/pycityagent-ui +0 -0
- pycityagent/simulation/__init__.py +8 -0
- pycityagent/simulation/agentgroup.py +580 -0
- pycityagent/simulation/simulation.py +634 -0
- pycityagent/simulation/storage/pg.py +184 -0
- pycityagent/survey/__init__.py +4 -0
- pycityagent/survey/manager.py +54 -0
- pycityagent/survey/models.py +120 -0
- pycityagent/utils/__init__.py +11 -0
- pycityagent/utils/avro_schema.py +109 -0
- pycityagent/utils/decorators.py +99 -0
- pycityagent/utils/parsers/__init__.py +13 -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/utils/pg_query.py +92 -0
- pycityagent/utils/survey_util.py +53 -0
- pycityagent/workflow/__init__.py +26 -0
- pycityagent/workflow/block.py +211 -0
- pycityagent/workflow/prompt.py +79 -0
- pycityagent/workflow/tool.py +240 -0
- pycityagent/workflow/trigger.py +163 -0
- pycityagent-2.0.0a43.dist-info/LICENSE +21 -0
- pycityagent-2.0.0a43.dist-info/METADATA +235 -0
- pycityagent-2.0.0a43.dist-info/RECORD +81 -0
- pycityagent-2.0.0a43.dist-info/WHEEL +5 -0
- pycityagent-2.0.0a43.dist-info/entry_points.txt +3 -0
- pycityagent-2.0.0a43.dist-info/top_level.txt +3 -0
@@ -0,0 +1,331 @@
|
|
1
|
+
"""Simulator: 城市模拟器类及其定义"""
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
import logging
|
5
|
+
import os
|
6
|
+
from datetime import datetime, timedelta
|
7
|
+
from typing import Any, Optional, Union, cast
|
8
|
+
|
9
|
+
from mosstool.type import TripMode
|
10
|
+
from mosstool.util.format_converter import coll2pb
|
11
|
+
from pycitydata.map import Map as SimMap
|
12
|
+
from pycityproto.city.map.v2 import map_pb2 as map_pb2
|
13
|
+
from pycityproto.city.person.v2 import person_pb2 as person_pb2
|
14
|
+
from pycityproto.city.person.v2 import person_service_pb2 as person_service
|
15
|
+
from pymongo import MongoClient
|
16
|
+
from shapely.geometry import Point
|
17
|
+
|
18
|
+
from .sim import CityClient, ControlSimEnv
|
19
|
+
from .utils.const import *
|
20
|
+
|
21
|
+
logger = logging.getLogger("pycityagent")
|
22
|
+
|
23
|
+
|
24
|
+
class Simulator:
|
25
|
+
"""
|
26
|
+
- 模拟器主类
|
27
|
+
- Simulator Class
|
28
|
+
"""
|
29
|
+
|
30
|
+
def __init__(self, config: dict, secure: bool = False) -> None:
|
31
|
+
self.config = config
|
32
|
+
"""
|
33
|
+
- 模拟器配置
|
34
|
+
- simulator config
|
35
|
+
"""
|
36
|
+
_mongo_uri, _mongo_db, _mongo_coll, _map_cache_dir = (
|
37
|
+
config["map_request"]["mongo_uri"],
|
38
|
+
config["map_request"]["mongo_db"],
|
39
|
+
config["map_request"]["mongo_coll"],
|
40
|
+
config["map_request"]["cache_dir"],
|
41
|
+
)
|
42
|
+
_mongo_client = MongoClient(_mongo_uri)
|
43
|
+
os.makedirs(_map_cache_dir, exist_ok=True)
|
44
|
+
_map_pb_path = os.path.join(_map_cache_dir, f"{_mongo_db}.{_mongo_coll}.pb") # type: ignore
|
45
|
+
_map_pb = map_pb2.Map()
|
46
|
+
if os.path.exists(_map_pb_path):
|
47
|
+
with open(_map_pb_path, "rb") as f:
|
48
|
+
_map_pb.ParseFromString(f.read())
|
49
|
+
else:
|
50
|
+
_map_pb = coll2pb(_mongo_client[_mongo_db][_mongo_coll], _map_pb)
|
51
|
+
with open(_map_pb_path, "wb") as f:
|
52
|
+
f.write(_map_pb.SerializeToString())
|
53
|
+
|
54
|
+
if "simulator" in config:
|
55
|
+
if "server" not in config["simulator"]:
|
56
|
+
self._sim_env = sim_env = ControlSimEnv(
|
57
|
+
task_name=config["simulator"].get("task", "citysim"),
|
58
|
+
map_file=_map_pb_path,
|
59
|
+
start_step=config["simulator"].get("start_step", 0),
|
60
|
+
total_step=2147000000,
|
61
|
+
log_dir=config["simulator"].get("log_dir", "./log"),
|
62
|
+
min_step_time=config["simulator"].get("min_step_time", 1000),
|
63
|
+
simuletgo_addr=config["simulator"].get("server", None),
|
64
|
+
)
|
65
|
+
|
66
|
+
# using local client
|
67
|
+
self._client = CityClient(sim_env.simuletgo_addr, secure=False)
|
68
|
+
"""
|
69
|
+
- 模拟器grpc客户端
|
70
|
+
- grpc client of simulator
|
71
|
+
"""
|
72
|
+
else:
|
73
|
+
self._client = CityClient(config["simulator"]["server"], secure=False)
|
74
|
+
else:
|
75
|
+
logger.warning(
|
76
|
+
"No simulator config found, no simulator client will be used"
|
77
|
+
)
|
78
|
+
self.map = SimMap(
|
79
|
+
mongo_uri=_mongo_uri,
|
80
|
+
mongo_db=_mongo_db,
|
81
|
+
mongo_coll=_mongo_coll,
|
82
|
+
cache_dir=_map_cache_dir,
|
83
|
+
)
|
84
|
+
"""
|
85
|
+
- 模拟器地图对象
|
86
|
+
- Simulator map object
|
87
|
+
"""
|
88
|
+
|
89
|
+
self.time: int = 0
|
90
|
+
"""
|
91
|
+
- 模拟城市当前时间
|
92
|
+
- The current time of simulator
|
93
|
+
"""
|
94
|
+
self.poi_cate = POI_CATG_DICT
|
95
|
+
self.map_x_gap = None
|
96
|
+
self.map_y_gap = None
|
97
|
+
self._bbox: tuple[float, float, float, float] = (-1, -1, -1, -1)
|
98
|
+
self._lock = asyncio.Lock()
|
99
|
+
# poi id dict
|
100
|
+
self.poi_id_2_aoi_id: dict[int, int] = {
|
101
|
+
poi["id"]: poi["aoi_id"] for _, poi in self.map.pois.items()
|
102
|
+
}
|
103
|
+
|
104
|
+
# * Agent相关
|
105
|
+
def find_agents_by_area(self, req: dict, status=None):
|
106
|
+
"""
|
107
|
+
通过区域范围查找agent/person
|
108
|
+
Get agents/persons in the provided area
|
109
|
+
|
110
|
+
Args:
|
111
|
+
- req (dict): 用于描述区域的请求 https://cityproto.sim.fiblab.net/#city.person.1.GetPersonByLongLatBBoxRequest
|
112
|
+
- 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
|
113
|
+
|
114
|
+
Returns:
|
115
|
+
- https://cityproto.sim.fiblab.net/#city.person.1.GetPersonByLongLatBBoxResponse
|
116
|
+
"""
|
117
|
+
loop = asyncio.get_event_loop()
|
118
|
+
resp = loop.run_until_complete(
|
119
|
+
self._client.person_service.GetPersonByLongLatBBox(req=req)
|
120
|
+
)
|
121
|
+
loop.close()
|
122
|
+
if status == None:
|
123
|
+
return resp
|
124
|
+
else:
|
125
|
+
motions = []
|
126
|
+
for agent in resp.motions: # type: ignore
|
127
|
+
if agent.status in status:
|
128
|
+
motions.append(agent)
|
129
|
+
resp.motions = motions # type: ignore
|
130
|
+
return resp
|
131
|
+
|
132
|
+
def get_poi_categories(
|
133
|
+
self,
|
134
|
+
center: Optional[Union[tuple[float, float], Point]] = None,
|
135
|
+
radius: Optional[float] = None,
|
136
|
+
) -> list[str]:
|
137
|
+
categories: list[str] = []
|
138
|
+
if center is None:
|
139
|
+
center = (0, 0)
|
140
|
+
_pois: list[dict] = self.map.query_pois( # type:ignore
|
141
|
+
center=center,
|
142
|
+
radius=radius,
|
143
|
+
return_distance=False,
|
144
|
+
)
|
145
|
+
for poi in _pois:
|
146
|
+
catg = poi["category"]
|
147
|
+
categories.append(catg.split("|")[-1])
|
148
|
+
return list(set(categories))
|
149
|
+
|
150
|
+
async def get_time(
|
151
|
+
self, format_time: bool = False, format: str = "%H:%M:%S"
|
152
|
+
) -> Union[int, str]:
|
153
|
+
"""
|
154
|
+
获取模拟器当前时间 Get current time of simulator
|
155
|
+
默认返回以00:00:00为始的, 以s为单位的时间(int)
|
156
|
+
支持格式化时间
|
157
|
+
|
158
|
+
Args:
|
159
|
+
- format_time (bool): 是否格式化 format or not
|
160
|
+
- format (str): 格式化模板,默认为"Hour:Minute:Second" the formation
|
161
|
+
|
162
|
+
Returns:
|
163
|
+
- time Union[int, str]: 时间 time in second(int) or formatted time(str)
|
164
|
+
"""
|
165
|
+
t_sec = await self._client.clock_service.Now({})
|
166
|
+
t_sec = cast(dict[str, int], t_sec)
|
167
|
+
self.time = t_sec["t"]
|
168
|
+
if format_time:
|
169
|
+
current_date = datetime.now().date()
|
170
|
+
start_of_day = datetime.combine(current_date, datetime.min.time())
|
171
|
+
current_time = start_of_day + timedelta(seconds=t_sec["t"])
|
172
|
+
formatted_time = current_time.strftime(format)
|
173
|
+
return formatted_time
|
174
|
+
else:
|
175
|
+
# BUG: 返回的time是float类型
|
176
|
+
return t_sec["t"]
|
177
|
+
|
178
|
+
async def get_simulator_day(self) -> int:
|
179
|
+
"""
|
180
|
+
获取模拟器到第几日
|
181
|
+
"""
|
182
|
+
t_sec = await self._client.clock_service.Now({})
|
183
|
+
t_sec = cast(dict[str, int], t_sec)
|
184
|
+
day = t_sec["t"] // 86400
|
185
|
+
return day
|
186
|
+
|
187
|
+
async def get_simulator_second_from_start_of_day(self) -> int:
|
188
|
+
"""
|
189
|
+
获取模拟器从00:00:00到当前的秒数
|
190
|
+
"""
|
191
|
+
t_sec = await self._client.clock_service.Now({})
|
192
|
+
t_sec = cast(dict[str, int], t_sec)
|
193
|
+
return t_sec["t"] % 86400
|
194
|
+
|
195
|
+
async def get_person(self, person_id: int) -> dict:
|
196
|
+
return await self._client.person_service.GetPerson(
|
197
|
+
req={"person_id": person_id}
|
198
|
+
) # type:ignore
|
199
|
+
|
200
|
+
async def add_person(self, person: Any) -> dict:
|
201
|
+
if isinstance(person, person_pb2.Person):
|
202
|
+
req = person_service.AddPersonRequest(person=person)
|
203
|
+
else:
|
204
|
+
req = person
|
205
|
+
return await self._client.person_service.AddPerson(req) # type:ignore
|
206
|
+
|
207
|
+
async def set_aoi_schedules(
|
208
|
+
self,
|
209
|
+
person_id: int,
|
210
|
+
target_positions: Union[
|
211
|
+
list[Union[int, tuple[int, int]]], Union[int, tuple[int, int]]
|
212
|
+
],
|
213
|
+
departure_times: Optional[list[float]] = None,
|
214
|
+
modes: Optional[list[TripMode]] = None,
|
215
|
+
):
|
216
|
+
cur_time = float(await self.get_time())
|
217
|
+
if not isinstance(target_positions, list):
|
218
|
+
target_positions = [target_positions]
|
219
|
+
if departure_times is None:
|
220
|
+
departure_times = [cur_time for _ in range(len(target_positions))]
|
221
|
+
else:
|
222
|
+
for _ in range(len(target_positions) - len(departure_times)):
|
223
|
+
departure_times.append(cur_time)
|
224
|
+
if modes is None:
|
225
|
+
modes = [
|
226
|
+
TripMode.TRIP_MODE_DRIVE_ONLY for _ in range(len(target_positions))
|
227
|
+
]
|
228
|
+
else:
|
229
|
+
for _ in range(len(target_positions) - len(modes)):
|
230
|
+
modes.append(TripMode.TRIP_MODE_DRIVE_ONLY)
|
231
|
+
_schedules = []
|
232
|
+
for target_pos, _time, _mode in zip(target_positions, departure_times, modes):
|
233
|
+
if isinstance(target_pos, int):
|
234
|
+
if target_pos >= POI_START_ID:
|
235
|
+
poi_id = target_pos
|
236
|
+
end = {
|
237
|
+
"aoi_position": {
|
238
|
+
"aoi_id": self.poi_id_2_aoi_id[poi_id],
|
239
|
+
"poi_id": poi_id,
|
240
|
+
}
|
241
|
+
}
|
242
|
+
else:
|
243
|
+
aoi_id = target_pos
|
244
|
+
end = {
|
245
|
+
"aoi_position": {
|
246
|
+
"aoi_id": aoi_id,
|
247
|
+
}
|
248
|
+
}
|
249
|
+
else:
|
250
|
+
aoi_id, poi_id = target_pos
|
251
|
+
end = {"aoi_position": {"aoi_id": aoi_id, "poi_id": poi_id}}
|
252
|
+
# activity = ""
|
253
|
+
trips = [
|
254
|
+
{
|
255
|
+
"mode": _mode,
|
256
|
+
"end": end,
|
257
|
+
"departure_time": _time,
|
258
|
+
},
|
259
|
+
]
|
260
|
+
_schedules.append(
|
261
|
+
{"trips": trips, "loop_count": 1, "departure_time": _time}
|
262
|
+
)
|
263
|
+
req = {"person_id": person_id, "schedules": _schedules}
|
264
|
+
await self._client.person_service.SetSchedule(req)
|
265
|
+
|
266
|
+
async def reset_person_position(
|
267
|
+
self,
|
268
|
+
person_id: int,
|
269
|
+
aoi_id: Optional[int] = None,
|
270
|
+
poi_id: Optional[int] = None,
|
271
|
+
lane_id: Optional[int] = None,
|
272
|
+
s: Optional[float] = None,
|
273
|
+
):
|
274
|
+
reset_position = {}
|
275
|
+
if aoi_id is not None:
|
276
|
+
reset_position["aoi_position"] = {"aoi_id": aoi_id}
|
277
|
+
if poi_id is not None:
|
278
|
+
reset_position["aoi_position"]["poi_id"] = poi_id
|
279
|
+
logger.debug(
|
280
|
+
f"Setting person {person_id} pos to AoiPosition {reset_position}"
|
281
|
+
)
|
282
|
+
await self._client.person_service.ResetPersonPosition(
|
283
|
+
{"person_id": person_id, "position": reset_position}
|
284
|
+
)
|
285
|
+
elif lane_id is not None:
|
286
|
+
reset_position["lane_position"] = {
|
287
|
+
"lane_id": lane_id,
|
288
|
+
"s": 0.0,
|
289
|
+
}
|
290
|
+
if s is not None:
|
291
|
+
reset_position["lane_position"]["s"] = s
|
292
|
+
logger.debug(
|
293
|
+
f"Setting person {person_id} pos to LanePosition {reset_position}"
|
294
|
+
)
|
295
|
+
await self._client.person_service.ResetPersonPosition(
|
296
|
+
{"person_id": person_id, "position": reset_position}
|
297
|
+
)
|
298
|
+
else:
|
299
|
+
logger.debug(
|
300
|
+
f"Neither aoi or lane pos provided for person {person_id} position reset!!"
|
301
|
+
)
|
302
|
+
|
303
|
+
def get_around_poi(
|
304
|
+
self,
|
305
|
+
center: Union[tuple[float, float], Point],
|
306
|
+
radius: float,
|
307
|
+
poi_type: Union[str, list[str]],
|
308
|
+
) -> list[dict]:
|
309
|
+
if isinstance(poi_type, str):
|
310
|
+
poi_type = [poi_type]
|
311
|
+
transformed_poi_type = []
|
312
|
+
for t in poi_type:
|
313
|
+
if t not in self.poi_cate:
|
314
|
+
transformed_poi_type.append(t)
|
315
|
+
else:
|
316
|
+
transformed_poi_type += self.poi_cate[t]
|
317
|
+
poi_type_set = set(transformed_poi_type)
|
318
|
+
# 获取半径内的poi
|
319
|
+
_pois: list[dict] = self.map.query_pois( # type:ignore
|
320
|
+
center=center,
|
321
|
+
radius=radius,
|
322
|
+
return_distance=False,
|
323
|
+
)
|
324
|
+
# 过滤掉不满足类别前缀的poi
|
325
|
+
pois = []
|
326
|
+
for poi in _pois:
|
327
|
+
catg = poi["category"]
|
328
|
+
if catg.split("|")[-1] not in poi_type_set:
|
329
|
+
continue
|
330
|
+
pois.append(poi)
|
331
|
+
return pois
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import base64
|
2
|
+
|
3
|
+
__all__ = ["encode_to_base64"]
|
4
|
+
|
5
|
+
|
6
|
+
def encode_to_base64(input_string: str) -> str:
|
7
|
+
# 将字符串转换为字节
|
8
|
+
input_bytes = input_string.encode("utf-8")
|
9
|
+
|
10
|
+
# 将字节编码为Base64
|
11
|
+
base64_bytes = base64.b64encode(input_bytes)
|
12
|
+
|
13
|
+
# 将Base64字节转换为字符串
|
14
|
+
base64_string = base64_bytes.decode("utf-8")
|
15
|
+
|
16
|
+
return base64_string
|
@@ -0,0 +1,244 @@
|
|
1
|
+
from mosstool.map._map_util.const import AOI_START_ID, POI_START_ID
|
2
|
+
|
3
|
+
POI_CATG_DICT = {
|
4
|
+
"户外活动": [
|
5
|
+
"bandstand",
|
6
|
+
"beach_resort",
|
7
|
+
"bird_hide",
|
8
|
+
"bleachers",
|
9
|
+
"firepit",
|
10
|
+
"fishing",
|
11
|
+
"garden",
|
12
|
+
"nature_reserve",
|
13
|
+
"park",
|
14
|
+
"picnic_table",
|
15
|
+
"playground",
|
16
|
+
"resort",
|
17
|
+
"summer_camp",
|
18
|
+
"swimming_area",
|
19
|
+
"water_park",
|
20
|
+
"wildlife_hide",
|
21
|
+
],
|
22
|
+
"室内娱乐场所": [
|
23
|
+
"adult_gaming_centre",
|
24
|
+
"amusement_arcade",
|
25
|
+
"bowling_alley",
|
26
|
+
"disc_golf_course",
|
27
|
+
"escape_game",
|
28
|
+
"fitness_centre",
|
29
|
+
"fitness_station",
|
30
|
+
"golf_course",
|
31
|
+
"miniature_golf",
|
32
|
+
"sauna",
|
33
|
+
"tanning_salon",
|
34
|
+
"trampoline_park",
|
35
|
+
"bar",
|
36
|
+
"biergarten",
|
37
|
+
"cafe",
|
38
|
+
"fast_food",
|
39
|
+
"food_court",
|
40
|
+
"ice_cream",
|
41
|
+
"pub",
|
42
|
+
"restaurant",
|
43
|
+
"arts_centre",
|
44
|
+
"brothel",
|
45
|
+
"casino",
|
46
|
+
"cinema",
|
47
|
+
"community_centre",
|
48
|
+
"conference_centre",
|
49
|
+
"events_venue",
|
50
|
+
"exhibition_centre",
|
51
|
+
"fountain",
|
52
|
+
"gambling",
|
53
|
+
"love_hotel",
|
54
|
+
"music_venue",
|
55
|
+
"nightclub",
|
56
|
+
"planetarium",
|
57
|
+
"public_bookcase",
|
58
|
+
"social_centre",
|
59
|
+
"stage",
|
60
|
+
"stripclub",
|
61
|
+
"studio",
|
62
|
+
"swingerclub",
|
63
|
+
"theatre",
|
64
|
+
],
|
65
|
+
"体育设施": [
|
66
|
+
"horse_riding",
|
67
|
+
"ice_rink",
|
68
|
+
"marina",
|
69
|
+
"pitch",
|
70
|
+
"sports_centre",
|
71
|
+
"sports_hall",
|
72
|
+
"stadium",
|
73
|
+
"track",
|
74
|
+
"swimming_pool",
|
75
|
+
],
|
76
|
+
"水上活动": [
|
77
|
+
"beach_resort",
|
78
|
+
"ice_rink",
|
79
|
+
"marina",
|
80
|
+
"slipway",
|
81
|
+
"swimming_area",
|
82
|
+
"swimming_pool",
|
83
|
+
"water_park",
|
84
|
+
],
|
85
|
+
"自然与野生动物观赏": [
|
86
|
+
"bird_hide",
|
87
|
+
"nature_reserve",
|
88
|
+
"wildlife_hide",
|
89
|
+
"hunting_stand",
|
90
|
+
],
|
91
|
+
"儿童游乐区": ["playground", "summer_camp", "miniature_golf", "dog_park"],
|
92
|
+
"餐饮服务": [
|
93
|
+
"bar",
|
94
|
+
"biergarten",
|
95
|
+
"cafe",
|
96
|
+
"fast_food",
|
97
|
+
"food_court",
|
98
|
+
"ice_cream",
|
99
|
+
"pub",
|
100
|
+
"restaurant",
|
101
|
+
],
|
102
|
+
"教育机构": [
|
103
|
+
"college",
|
104
|
+
"dancing_school",
|
105
|
+
"driving_school",
|
106
|
+
"first_aid_school",
|
107
|
+
"kindergarten",
|
108
|
+
"language_school",
|
109
|
+
"library",
|
110
|
+
"surf_school",
|
111
|
+
"toy_library",
|
112
|
+
"research_institute",
|
113
|
+
"training",
|
114
|
+
"music_school",
|
115
|
+
"school",
|
116
|
+
"traffic_park",
|
117
|
+
"university",
|
118
|
+
],
|
119
|
+
"交通设施": [
|
120
|
+
"bicycle_parking",
|
121
|
+
"bicycle_repair_station",
|
122
|
+
"bicycle_rental",
|
123
|
+
"bicycle_wash",
|
124
|
+
"boat_rental",
|
125
|
+
"boat_sharing",
|
126
|
+
"bus_station",
|
127
|
+
"car_rental",
|
128
|
+
"car_sharing",
|
129
|
+
"car_wash",
|
130
|
+
"compressed_air",
|
131
|
+
"vehicle_inspection",
|
132
|
+
"charging_station",
|
133
|
+
"driver_training",
|
134
|
+
"ferry_terminal",
|
135
|
+
"fuel",
|
136
|
+
"grit_bin",
|
137
|
+
"motorcycle_parking",
|
138
|
+
"parking",
|
139
|
+
"parking_entrance",
|
140
|
+
"parking_space",
|
141
|
+
"taxi",
|
142
|
+
"weighbridge",
|
143
|
+
],
|
144
|
+
"金融服务": [
|
145
|
+
"atm",
|
146
|
+
"payment_terminal",
|
147
|
+
"bank",
|
148
|
+
"bureau_de_change",
|
149
|
+
"money_transfer",
|
150
|
+
"payment_centre",
|
151
|
+
],
|
152
|
+
"医疗保健": [
|
153
|
+
"baby_hatch",
|
154
|
+
"clinic",
|
155
|
+
"dentist",
|
156
|
+
"doctors",
|
157
|
+
"hospital",
|
158
|
+
"nursing_home",
|
159
|
+
"pharmacy",
|
160
|
+
"social_facility",
|
161
|
+
"veterinary",
|
162
|
+
],
|
163
|
+
"文化艺术": [
|
164
|
+
"arts_centre",
|
165
|
+
"brothel",
|
166
|
+
"casino",
|
167
|
+
"cinema",
|
168
|
+
"community_centre",
|
169
|
+
"conference_centre",
|
170
|
+
"events_venue",
|
171
|
+
"exhibition_centre",
|
172
|
+
"fountain",
|
173
|
+
"gambling",
|
174
|
+
"love_hotel",
|
175
|
+
"music_venue",
|
176
|
+
"nightclub",
|
177
|
+
"planetarium",
|
178
|
+
"public_bookcase",
|
179
|
+
"social_centre",
|
180
|
+
"stage",
|
181
|
+
"stripclub",
|
182
|
+
"studio",
|
183
|
+
"swingerclub",
|
184
|
+
"theatre",
|
185
|
+
],
|
186
|
+
"公共服务": [
|
187
|
+
"bbq",
|
188
|
+
"bench",
|
189
|
+
"dog_toilet",
|
190
|
+
"dressing_room",
|
191
|
+
"drinking_water",
|
192
|
+
"give_box",
|
193
|
+
"lounge",
|
194
|
+
"mailroom",
|
195
|
+
"parcel_locker",
|
196
|
+
"shelter",
|
197
|
+
"shower",
|
198
|
+
"telephone",
|
199
|
+
"toilets",
|
200
|
+
"water_point",
|
201
|
+
"watering_place",
|
202
|
+
"sanitary_dump_station",
|
203
|
+
"recycling",
|
204
|
+
"waste_basket",
|
205
|
+
"waste_disposal",
|
206
|
+
"waste_transfer_station",
|
207
|
+
"post_box",
|
208
|
+
"post_depot",
|
209
|
+
"post_office",
|
210
|
+
"courthouse",
|
211
|
+
"fire_station",
|
212
|
+
"police",
|
213
|
+
"prison",
|
214
|
+
"ranger_station",
|
215
|
+
"townhall",
|
216
|
+
],
|
217
|
+
"其他特殊用途": [
|
218
|
+
"animal_boarding",
|
219
|
+
"animal_breeding",
|
220
|
+
"animal_shelter",
|
221
|
+
"animal_training",
|
222
|
+
"baking_oven",
|
223
|
+
"clock",
|
224
|
+
"crematorium",
|
225
|
+
"dive_centre",
|
226
|
+
"funeral_hall",
|
227
|
+
"grave_yard",
|
228
|
+
"hunting_stand",
|
229
|
+
"internet_cafe",
|
230
|
+
"kitchen",
|
231
|
+
"kneipp_water_cure",
|
232
|
+
"lounger",
|
233
|
+
"marketplace",
|
234
|
+
"monastery",
|
235
|
+
"mortuary",
|
236
|
+
"photo_booth",
|
237
|
+
"place_of_mourning",
|
238
|
+
"place_of_worship",
|
239
|
+
"public_bath",
|
240
|
+
"public_building",
|
241
|
+
"refugee_site",
|
242
|
+
"vending_machine",
|
243
|
+
],
|
244
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
__all__ = ["wrap_feature_collection"]
|
2
|
+
|
3
|
+
|
4
|
+
def wrap_feature_collection(features: list[dict], name: str):
|
5
|
+
"""
|
6
|
+
将 GeoJSON Feature 集合包装为 FeatureCollection
|
7
|
+
Wrap GeoJSON Feature collection as FeatureCollection
|
8
|
+
|
9
|
+
Args:
|
10
|
+
- features: GeoJSON Feature 集合。GeoJSON Feature collection.
|
11
|
+
- name: FeatureCollection 名称。FeatureCollection name.
|
12
|
+
|
13
|
+
Returns:
|
14
|
+
- dict: GeoJSON FeatureCollection
|
15
|
+
"""
|
16
|
+
return {
|
17
|
+
"type": "FeatureCollection",
|
18
|
+
"name": name,
|
19
|
+
"crs": {
|
20
|
+
"type": "name",
|
21
|
+
"properties": {"name": "urn:ogc:def:crs:OGC:1.3:CRS84"},
|
22
|
+
},
|
23
|
+
"features": features,
|
24
|
+
}
|
@@ -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)
|