pycityagent 2.0.0a1__tar.gz → 2.0.0a2__tar.gz

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.
Files changed (71) hide show
  1. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/PKG-INFO +1 -1
  2. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/agent.py +1 -1
  3. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/sim/__init__.py +2 -0
  4. pycityagent-2.0.0a2/pycityagent/environment/sim/sim_env.py +145 -0
  5. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/simulator.py +110 -159
  6. pycityagent-2.0.0a2/pycityagent/environment/utils/__init__.py +10 -0
  7. pycityagent-2.0.0a2/pycityagent/environment/utils/base64.py +16 -0
  8. pycityagent-2.0.0a2/pycityagent/environment/utils/const.py +242 -0
  9. pycityagent-2.0.0a2/pycityagent/environment/utils/port.py +11 -0
  10. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/simulation/simulation.py +153 -87
  11. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/workflow/__init__.py +5 -3
  12. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/workflow/block.py +31 -4
  13. pycityagent-2.0.0a2/pycityagent/workflow/trigger.py +150 -0
  14. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pyproject.toml +1 -1
  15. pycityagent-2.0.0a1/pycityagent/environment/utils/__init__.py +0 -8
  16. pycityagent-2.0.0a1/pycityagent/workflow/trigger.py +0 -66
  17. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/README.md +0 -0
  18. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/__init__.py +0 -0
  19. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/config.py +0 -0
  20. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/economy/__init__.py +0 -0
  21. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/economy/econ_client.py +0 -0
  22. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/__init__.py +0 -0
  23. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/interact/__init__.py +0 -0
  24. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/interact/interact.py +0 -0
  25. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/message/__init__.py +0 -0
  26. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/sence/__init__.py +0 -0
  27. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/sence/static.py +0 -0
  28. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/sidecar/__init__.py +0 -0
  29. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/sidecar/sidecarv2.py +0 -0
  30. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/sim/aoi_service.py +0 -0
  31. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/sim/client.py +0 -0
  32. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/sim/clock_service.py +0 -0
  33. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/sim/economy_services.py +0 -0
  34. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/sim/lane_service.py +0 -0
  35. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/sim/light_service.py +0 -0
  36. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/sim/person_service.py +0 -0
  37. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/sim/road_service.py +0 -0
  38. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/sim/social_service.py +0 -0
  39. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/utils/geojson.py +0 -0
  40. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/utils/grpc.py +0 -0
  41. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/utils/map_utils.py +0 -0
  42. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/environment/utils/protobuf.py +0 -0
  43. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/llm/__init__.py +0 -0
  44. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/llm/embedding.py +0 -0
  45. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/llm/llm.py +0 -0
  46. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/llm/llmconfig.py +0 -0
  47. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/llm/utils.py +0 -0
  48. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/memory/__init__.py +0 -0
  49. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/memory/const.py +0 -0
  50. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/memory/memory.py +0 -0
  51. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/memory/memory_base.py +0 -0
  52. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/memory/profile.py +0 -0
  53. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/memory/self_define.py +0 -0
  54. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/memory/state.py +0 -0
  55. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/memory/utils.py +0 -0
  56. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/message/__init__.py +0 -0
  57. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/simulation/__init__.py +0 -0
  58. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/simulation/interview.py +0 -0
  59. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/simulation/survey/__init__.py +0 -0
  60. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/simulation/survey/manager.py +0 -0
  61. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/simulation/survey/models.py +0 -0
  62. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/simulation/ui/__init__.py +0 -0
  63. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/simulation/ui/interface.py +0 -0
  64. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/utils/__init__.py +0 -0
  65. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/utils/decorators.py +0 -0
  66. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/utils/parsers/__init__.py +0 -0
  67. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/utils/parsers/code_block_parser.py +0 -0
  68. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/utils/parsers/json_parser.py +0 -0
  69. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/utils/parsers/parser_base.py +0 -0
  70. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/workflow/prompt.py +0 -0
  71. {pycityagent-2.0.0a1 → pycityagent-2.0.0a2}/pycityagent/workflow/tool.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pycityagent
3
- Version: 2.0.0a1
3
+ Version: 2.0.0a2
4
4
  Summary: LLM-based城市环境agent构建库
5
5
  License: MIT
6
6
  Author: Yuwei Yan
@@ -126,7 +126,7 @@ class Agent(ABC):
126
126
  dialog = []
127
127
 
128
128
  # 添加系统提示
129
- system_prompt = f"你是一个名叫{self._name}的{self._type.value}。请以第一人称的方式回答问题,保持回答简洁明了。"
129
+ system_prompt = f"请以第一人称的方式回答问题,保持回答简洁明了。"
130
130
  dialog.append({"role": "system", "content": system_prompt})
131
131
 
132
132
  # 添加记忆上下文
@@ -12,6 +12,7 @@ from .lane_service import LaneService
12
12
  from .road_service import RoadService
13
13
  from .social_service import SocialService
14
14
  from .light_service import LightService
15
+ from .sim_env import ControlSimEnv
15
16
 
16
17
  __all__ = [
17
18
  "CityClient",
@@ -24,4 +25,5 @@ __all__ = [
24
25
  "EconomyPersonService",
25
26
  "EconomyOrgService",
26
27
  "LightService",
28
+ "ControlSimEnv",
27
29
  ]
@@ -0,0 +1,145 @@
1
+ import logging
2
+ import os
3
+ import time
4
+ import atexit
5
+ from subprocess import DEVNULL, Popen
6
+ from typing import Optional
7
+
8
+ from pycitydata.map import Map
9
+
10
+ from ..utils import encode_to_base64, find_free_port
11
+
12
+ __all__ = ["ControlSimEnv"]
13
+
14
+
15
+ def _generate_yaml_config(map_file: str, start_step: int, total_step: int) -> str:
16
+ map_file = os.path.abspath(map_file)
17
+ return f"""
18
+ input:
19
+ # 地图
20
+ map:
21
+ file: "{map_file}"
22
+
23
+ control:
24
+ step:
25
+ # 模拟器起始步
26
+ start: {start_step}
27
+ # 模拟总步数,结束步为起始步+总步数
28
+ total: {total_step}
29
+ # 每步的时间间隔
30
+ interval: 1
31
+ skip_overtime_trip_when_init: true
32
+ enable_platoon: false
33
+ enable_indoor: false
34
+ prefer_fixed_light: true
35
+ enable_collision_avoidance: false # 计算性能下降10倍,需要保证subloop>=5
36
+ enable_go_astray: true # 引入串行的路径规划调用,计算性能下降(幅度不确定)
37
+ lane_change_model: earliest # mobil (主动变道+强制变道,默认值) earliest (总是尽可能早地变道)
38
+
39
+ output:
40
+ """
41
+
42
+
43
+ class ControlSimEnv:
44
+ def __init__(
45
+ self,
46
+ task_name: str,
47
+ map_file: str,
48
+ # person_file,
49
+ start_step: int,
50
+ total_step: int,
51
+ log_dir: str,
52
+ min_step_time: int = 1000,
53
+ timeout: int = 5,
54
+ simuletgo_addr: Optional[str] = None,
55
+ ):
56
+ self._task_name = task_name
57
+ self._map_file = map_file
58
+ # self._person_file = person_file
59
+ self._start_step = start_step
60
+ self._total_step = total_step
61
+ self._log_dir = log_dir
62
+ self._min_step_time = min_step_time
63
+ self._timeout = timeout
64
+ self.m = Map(pb_path=map_file)
65
+ """
66
+ 地图数据
67
+ """
68
+
69
+ # 检查二进制文件是否存在
70
+ # simulet-go: ~/.local/bin/simulet-go
71
+ self._simuletgo_path = os.path.expanduser("~/.local/bin/simulet-go")
72
+ if not os.path.exists(os.path.expanduser(self._simuletgo_path)):
73
+ raise FileNotFoundError("simulet-go not found, please install it first")
74
+
75
+ self._simuletgo_config = _generate_yaml_config(map_file, start_step, total_step)
76
+ self.simuletgo_port = None
77
+ self._simuletgo_proc = None
78
+ self._traffic_client = None
79
+
80
+ os.makedirs(log_dir, exist_ok=True)
81
+
82
+ self.simuletgo_addr = self.reset(simuletgo_addr)
83
+
84
+ def reset(
85
+ self,
86
+ simuletgo_addr: Optional[str] = None,
87
+ ):
88
+ """
89
+ Args:
90
+ - simuletgo_addr: str, simulet-go的地址。如果为None,则启动一个新的simulet-go
91
+ """
92
+
93
+ # 三个地址必须同时为None或者同时不为None
94
+ if simuletgo_addr is None:
95
+ # 1. 启动simulet-go
96
+ # ./simulet-go -config-data configbase64 -job test -syncer http://localhost:53001 -listen :51102
97
+ assert self.simuletgo_port is None
98
+ assert self._simuletgo_proc is None
99
+ self.simuletgo_port = find_free_port()
100
+ config_base64 = encode_to_base64(self._simuletgo_config)
101
+ self._simuletgo_proc = Popen(
102
+ [
103
+ self._simuletgo_path,
104
+ "-config-data",
105
+ config_base64,
106
+ "-job",
107
+ self._task_name,
108
+ "-listen",
109
+ f":{self.simuletgo_port}",
110
+ "-run.min_step_time",
111
+ f"{self._min_step_time}",
112
+ "-output",
113
+ self._log_dir,
114
+ "-cache",
115
+ "",
116
+ "-log.level",
117
+ "error",
118
+ ],
119
+ # 忽略输出
120
+ # stdout=DEVNULL,
121
+ )
122
+ logging.info(
123
+ f"start simulet-go at localhost:{self.simuletgo_port}, PID={self._simuletgo_proc.pid}"
124
+ )
125
+ simuletgo_addr = f"http://localhost:{self.simuletgo_port}"
126
+ atexit.register(self.close)
127
+ time.sleep(1)
128
+ elif simuletgo_addr is not None:
129
+ pass
130
+ else:
131
+ # raise ValueError(
132
+ # "simuletgo_addr, syncer_addr, routing_addr must be all None or all not None"
133
+ # )
134
+ pass
135
+ return simuletgo_addr
136
+
137
+ def close(self):
138
+ if self._simuletgo_proc is not None:
139
+ self._simuletgo_proc.terminate()
140
+ simuletgo_code = self._simuletgo_proc.wait()
141
+ logging.info(f"simulet-go exit with code {simuletgo_code}")
142
+
143
+ self.simuletgo_port = None
144
+ self._simuletgo_proc = None
145
+ self._traffic_client = None
@@ -2,15 +2,24 @@
2
2
 
3
3
  import asyncio
4
4
  import logging
5
+ import os
6
+ from collections import defaultdict
7
+ from collections.abc import Sequence
5
8
  from datetime import datetime, timedelta
6
9
  from typing import Any, Optional, Tuple, Union, cast
7
10
 
8
11
  from mosstool.type import TripMode
12
+ from mosstool.util.format_converter import coll2pb
9
13
  from pycitydata.map import Map as SimMap
14
+ from pycityproto.city.map.v2 import map_pb2 as map_pb2
10
15
  from pycityproto.city.person.v2 import person_pb2 as person_pb2
11
16
  from pycityproto.city.person.v2 import person_service_pb2 as person_service
17
+ from pymongo import MongoClient
18
+ from shapely.geometry import Point
19
+ from shapely.strtree import STRtree
12
20
 
13
- from .sim import CityClient
21
+ from .sim import CityClient, ControlSimEnv
22
+ from .utils.const import *
14
23
 
15
24
 
16
25
  class Simulator:
@@ -25,18 +34,52 @@ class Simulator:
25
34
  - 模拟器配置
26
35
  - simulator config
27
36
  """
37
+ _mongo_uri, _mongo_db, _mongo_coll, _map_cache_dir = (
38
+ config["map_request"]["mongo_uri"],
39
+ config["map_request"]["mongo_db"],
40
+ config["map_request"]["mongo_coll"],
41
+ config["map_request"]["cache_dir"],
42
+ )
43
+ _mongo_client = MongoClient(_mongo_uri)
44
+ os.makedirs(_map_cache_dir, exist_ok=True)
45
+ _map_pb_path = os.path.join(_map_cache_dir, f"{_mongo_db}.{_mongo_coll}.pb") # type: ignore
46
+ _map_pb = map_pb2.Map()
47
+ if os.path.exists(_map_pb_path):
48
+ with open(_map_pb_path, "rb") as f:
49
+ _map_pb.ParseFromString(f.read())
50
+ else:
51
+ _map_pb = coll2pb(_mongo_client[_mongo_db][_mongo_coll], _map_pb)
52
+ with open(_map_pb_path, "wb") as f:
53
+ f.write(_map_pb.SerializeToString())
28
54
 
29
- self._client = CityClient(self.config["simulator"]["server"], secure=secure)
30
- """
31
- - 模拟器grpc客户端
32
- - grpc client of simulator
33
- """
55
+ if 'simulator' in config:
56
+ if 'server' not in config["simulator"]:
57
+ self._sim_env = sim_env = ControlSimEnv(
58
+ task_name=config["simulator"].get("task", "citysim"),
59
+ map_file=_map_pb_path,
60
+ start_step=config["simulator"].get("start_step", 0),
61
+ total_step=2147000000,
62
+ log_dir=config["simulator"].get("log_dir", "./log"),
63
+ min_step_time=config["simulator"].get("min_step_time", 1000),
64
+ simuletgo_addr=config["simulator"].get("server", None),
65
+ )
66
+
67
+ # using local client
68
+ self._client = CityClient(sim_env.simuletgo_addr, secure=False)
69
+ """
70
+ - 模拟器grpc客户端
71
+ - grpc client of simulator
72
+ """
73
+ else:
74
+ self._client = CityClient(config['simulator']['server'], secure=False)
75
+ else:
76
+ logging.warning("No simulator config found, no simulator client will be used")
34
77
 
35
78
  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"],
79
+ mongo_uri=_mongo_uri,
80
+ mongo_db=_mongo_db,
81
+ mongo_coll=_mongo_coll,
82
+ cache_dir=_map_cache_dir,
40
83
  )
41
84
  """
42
85
  - 模拟器地图对象
@@ -54,24 +97,14 @@ class Simulator:
54
97
  - 模拟城市当前时间
55
98
  - The current time of simulator
56
99
  """
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
- }
100
+ self.poi_cate = POI_CATG_DICT
70
101
  self.map_x_gap = None
71
102
  self.map_y_gap = None
72
- self._bbox: Tuple[float, float, float, float] = (-1, -1, -1, -1)
103
+ self._bbox: tuple[float, float, float, float] = (-1, -1, -1, -1)
73
104
  self.poi_matrix_centers = []
74
105
  self._lock = asyncio.Lock()
106
+ # poi STRtree
107
+ self.set_poi_tree()
75
108
 
76
109
  # * Agent相关
77
110
  def FindAgentsByArea(self, req: dict, status=None):
@@ -101,146 +134,36 @@ class Simulator:
101
134
  resp.motions = motions # type: ignore
102
135
  return resp
103
136
 
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(
137
+ def set_poi_tree(
193
138
  self,
194
- center: Tuple[float, float],
195
- radius: int = 2500,
196
- rows: int = 5,
197
- cols: int = 5,
198
139
  ):
199
140
  """
200
- 获取以center为中心的正方形区域内的poi集合
201
-
202
- Args:
203
- - center (Tuple[float, float]): 中心位置点
204
- - radius (int): 半径
141
+ 初始化pois_tree
205
142
  """
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)
143
+ poi_geos = []
144
+ tree_id_2_poi_and_catg: dict[int, tuple[dict, str]] = {}
145
+ for tree_id, poi in enumerate(self.map.pois.values()):
146
+ tree_id_2_poi_and_catg[tree_id] = (poi, poi["category"])
147
+ poi_geos.append(Point([poi["position"][k] for k in ["x", "y"]]))
148
+ self.tree_id_2_poi_and_catg = tree_id_2_poi_and_catg
149
+ self.pois_tree = STRtree(poi_geos)
242
150
 
243
- return matrix, matrix_type, poi_total_number, poi_type_number
151
+ def GetPoiCategories(
152
+ self,
153
+ center: Optional[Union[tuple[float, float], Point]] = None,
154
+ radius: Optional[float] = None,
155
+ ) -> list[str]:
156
+ if center is not None and radius is not None:
157
+ if not isinstance(center, Point):
158
+ center = Point(center)
159
+ indices = self.pois_tree.query(center.buffer(radius))
160
+ else:
161
+ indices = list(self.tree_id_2_poi_and_catg.keys())
162
+ categories = []
163
+ for index in indices:
164
+ _, catg = self.tree_id_2_poi_and_catg[index]
165
+ categories.append(catg.split("|")[-1])
166
+ return list(set(categories))
244
167
 
245
168
  async def GetTime(
246
169
  self, format_time: bool = False, format: str = "%H:%M:%S"
@@ -367,3 +290,31 @@ class Simulator:
367
290
  logging.debug(
368
291
  f"Neither aoi or lane pos provided for person {person_id} position reset!!"
369
292
  )
293
+
294
+ def GetAroundPoi(
295
+ self,
296
+ center: Union[tuple[float, float], Point],
297
+ radius: float,
298
+ poi_type: Union[str, list[str]],
299
+ ):
300
+ if not isinstance(poi_type, Sequence):
301
+ poi_type = [poi_type]
302
+ transformed_poi_type = []
303
+ for t in poi_type:
304
+ if t not in self.poi_cate:
305
+ transformed_poi_type.append(t)
306
+ else:
307
+ transformed_poi_type += self.poi_cate[t]
308
+ poi_type_set = set(transformed_poi_type)
309
+ if not isinstance(center, Point):
310
+ center = Point(center)
311
+ # 获取半径内的poi
312
+ indices = self.pois_tree.query(center.buffer(radius))
313
+ # 过滤掉不满足类别前缀的poi
314
+ pois = []
315
+ for index in indices:
316
+ poi, catg = self.tree_id_2_poi_and_catg[index]
317
+ if catg.split("|")[-1] not in poi_type_set:
318
+ continue
319
+ pois.append(poi)
320
+ return pois
@@ -0,0 +1,10 @@
1
+ """
2
+ 实用工具
3
+ utilities
4
+ """
5
+
6
+ from .geojson import wrap_feature_collection
7
+ from .base64 import encode_to_base64
8
+ from .port import find_free_port
9
+
10
+ __all__ = ["wrap_feature_collection","find_free_port","find_free_port",]
@@ -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