pycityagent 1.1.9__py3-none-any.whl → 1.1.11__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.
@@ -13,83 +13,7 @@ from citystreetview import (
13
13
  )
14
14
  from .brainfc import BrainFunction
15
15
  from .static import POI_TYPE_DICT, LEVEL_ONE_PRE
16
-
17
- def point_on_line_given_distance(start_node, end_node, distance):
18
- """
19
- Given two points (start_point and end_point) defining a line, and a distance s to travel along the line,
20
- return the coordinates of the point reached after traveling s units along the line, starting from start_point.
21
-
22
- Args:
23
- start_point (tuple): Tuple of (x, y) representing the starting point on the line.
24
- end_point (tuple): Tuple of (x, y) representing the ending point on the line.
25
- distance (float): Distance to travel along the line, starting from start_point.
26
-
27
- Returns:
28
- tuple: Tuple of (x, y) representing the new point reached after traveling s units along the line.
29
- """
30
-
31
- x1, y1 = start_node['x'], start_node['y']
32
- x2, y2 = end_node['x'], end_node['y']
33
-
34
- # Calculate the slope m and the y-intercept b of the line
35
- if x1 == x2:
36
- # Vertical line, distance is only along the y-axis
37
- return (x1, y1 + distance if distance >= 0 else y1 - abs(distance))
38
- else:
39
- m = (y2 - y1) / (x2 - x1)
40
- b = y1 - m * x1
41
-
42
- # Calculate the direction vector (dx, dy) along the line
43
- dx = (x2 - x1) / math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
44
- dy = (y2 - y1) / math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
45
-
46
- # Scale the direction vector by the given distance
47
- scaled_dx = dx * distance
48
- scaled_dy = dy * distance
49
-
50
- # Calculate the new point's coordinates
51
- x = x1 + scaled_dx
52
- y = y1 + scaled_dy
53
-
54
- return [x, y]
55
-
56
- def get_xy_in_lane(nodes, distance, direction:str='front'):
57
- temp_sum = 0
58
- remain_s = 0
59
- if direction == 'front':
60
- # 顺道路方向前进
61
- if distance == 0:
62
- return [nodes[0]['x'], nodes[0]['y']]
63
- key_index = 0 # first node
64
- for i in range(1, len(nodes)):
65
- x1, y1 = nodes[i-1]['x'], nodes[i-1]['y']
66
- x2, y2 = nodes[i]['x'], nodes[i]['y']
67
- temp_sum += math.sqrt((x2 - x1)**2 + (y2-y1)**2)
68
- if temp_sum > distance:
69
- remain_s = distance - (temp_sum - math.sqrt((x2 - x1)**2 + (y2-y1)**2))
70
- break;
71
- key_index += 1
72
- if remain_s < 0.5:
73
- return [nodes[key_index]['x'], nodes[key_index]['y']]
74
- longlat = point_on_line_given_distance(nodes[key_index], nodes[key_index+1], remain_s)
75
- return longlat
76
- else:
77
- # 逆道路方向前进
78
- if distance == 0:
79
- return [nodes[-1]['x'], nodes[-1]['y']]
80
- key_index = len(nodes)-1 # last node
81
- for i in range(len(nodes)-1, 0, -1):
82
- x1, y1 = nodes[i]['x'], nodes[i]['y']
83
- x2, y2 = nodes[i-1]['x'], nodes[i-1]['y']
84
- temp_sum += math.sqrt((x2 - x1)**2 + (y2-y1)**2)
85
- if temp_sum > distance:
86
- remain_s = distance - (temp_sum - math.sqrt((x2 - x1)**2 + (y2-y1)**2))
87
- break;
88
- key_index -= 1
89
- if remain_s < 0.5:
90
- return [nodes[key_index]['x'], nodes[key_index]['y']]
91
- longlat = point_on_line_given_distance(nodes[key_index], nodes[key_index-1], remain_s)
92
- return longlat
16
+ from ..utils import point_on_line_given_distance, get_xy_in_lane
93
17
 
94
18
  class SencePlug:
95
19
  """
@@ -101,7 +25,6 @@ class SencePlug:
101
25
  - out (str): the output target in sence, the sence result will be insert to Sence.plug_buffer[out]
102
26
  """
103
27
  def __init__(self, user_func, out:str) -> None:
104
- # TODO: 添加合法性检查
105
28
  self.user_func = user_func
106
29
  self.out = out
107
30
 
@@ -220,10 +143,15 @@ class Sence(BrainFunction):
220
143
  self.sence_buffer['time'] = self._agent._simulator.time
221
144
 
222
145
  # * pois
223
- if self._sence_contents == None or 'poi' in self._sence_contents:
224
- self.sence_buffer['pois'] = await self.PerceivePoi()
225
- self.sence_buffer['poi_time_walk'] = sorted(self.sence_buffer['pois'], key=lambda x:x[2])
226
- self.sence_buffer['poi_time_drive'] = sorted(self.sence_buffer['pois'], key=lambda x:x[4])
146
+ if self._sence_contents == None or 'poi' in self._sence_contents or 'poi_verbose' in self._sence_contents:
147
+ if self._sence_contents == None:
148
+ self.sence_buffer['pois'] = await self.PerceivePoi()
149
+ elif 'poi_verbose' in self._sence_contents:
150
+ self.sence_buffer['pois'] = await self.PerceivePoi_Verbose()
151
+ self.sence_buffer['poi_time_walk'] = sorted(self.sence_buffer['pois'], key=lambda x:x[2])
152
+ self.sence_buffer['poi_time_drive'] = sorted(self.sence_buffer['pois'], key=lambda x:x[4])
153
+ else:
154
+ self.sence_buffer['pois'] = await self.PerceivePoi()
227
155
 
228
156
  # * reachable positions
229
157
  if self._sence_contents == None or 'position' in self._sence_contents:
@@ -248,7 +176,7 @@ class Sence(BrainFunction):
248
176
 
249
177
  # * streetview
250
178
  if self._sence_contents == None or 'streetview' in self._sence_contents:
251
- if self.enable_streeview:
179
+ if self.enable_streeview and 'lane_position' in self._agent.motion['position'].keys():
252
180
  self.sence_buffer['streetview'] = self.PerceiveStreetView()
253
181
  else:
254
182
  self.sence_buffer['streetview'] = None
@@ -261,15 +189,15 @@ class Sence(BrainFunction):
261
189
  self.sence_buffer['user_messages'] = []
262
190
 
263
191
  # * agent message
264
- if self._sence_contents == None or 'agent_message' in self._sence_contents:
265
- self.sence_buffer['social_messages'] = await self.PerceiveMessageFromPerson()
192
+ # if self._sence_contents == None or 'agent_message' in self._sence_contents:
193
+ # self.sence_buffer['social_messages'] = await self.PerceiveMessageFromPerson()
266
194
 
267
195
  # * 插件感知
268
- if len(self.plugs) > 0:
269
- for plug in self.plugs:
270
- out_key = plug.out
271
- out = plug.user_func(self.sence_buffer)
272
- self.plug_buffer[out_key] = out
196
+ # if len(self.plugs) > 0:
197
+ # for plug in self.plugs:
198
+ # out_key = plug.out
199
+ # out = plug.user_func(self.sence_buffer)
200
+ # self.plug_buffer[out_key] = out
273
201
 
274
202
  # * AOI and POI Related
275
203
  async def PerceiveAoi(self, only_person:bool=False):
@@ -426,11 +354,45 @@ class Sence(BrainFunction):
426
354
  'longlat': longlat,
427
355
  'type': type}]
428
356
  return positions
429
-
357
+
430
358
  async def PerceivePoi(self, radius:int=None, category:str=None):
431
359
  """
432
360
  POI感知
433
361
  Sence POI
362
+ Args:
363
+ - radius: 感知范围, 默认使用统一感知半径. Sence raduis, default use basic radius
364
+ - category: 6位数字类型编码, 如果为None则获取所有类型POI. 6-digit coding which represents the poi type, if None, then sence all type of poi
365
+ Returns:
366
+ - List[Tuple[Any, float]]: poi列表, 每个元素为(poi, 距离). poi list, each element is (poi, distance).
367
+ """
368
+ radius_ = self._sence_radius
369
+ if radius != None:
370
+ radius_ = radius
371
+ if category != None:
372
+ category_prefix = category
373
+ resp = self._agent._simulator.map.query_pois(
374
+ center=(self._agent.motion['position']['xy_position']['x'], self._agent.motion['position']['xy_position']['y']),
375
+ radius=radius_,
376
+ category_prefix=category_prefix
377
+ )
378
+ else:
379
+ resp = []
380
+ for category_prefix in LEVEL_ONE_PRE:
381
+ resp += self._agent._simulator.map.query_pois(
382
+ center=(self._agent.motion['position']['xy_position']['x'], self._agent.motion['position']['xy_position']['y']),
383
+ radius=radius_,
384
+ category_prefix=category_prefix
385
+ )
386
+ # * 从六位代码转变为具体类型
387
+ for poi in resp:
388
+ cate_str = poi[0]['category']
389
+ poi[0]['category'] = POI_TYPE_DICT[cate_str]
390
+ return resp
391
+
392
+ async def PerceivePoi_Verbose(self, radius:int=None, category:str=None):
393
+ """
394
+ POI感知
395
+ Sence POI
434
396
 
435
397
  Args:
436
398
  - radius: 感知范围, 默认使用统一感知半径. Sence raduis, default use basic radius
@@ -570,9 +532,6 @@ class Sence(BrainFunction):
570
532
  # * StreetView Related
571
533
  def PerceiveStreetView(
572
534
  self,
573
- # engine:str="baidumap",
574
- heading:str="front",
575
- save:bool=False,
576
535
  save_dir:str=None
577
536
  ):
578
537
  """
@@ -600,52 +559,27 @@ class Sence(BrainFunction):
600
559
 
601
560
  coords = [(self._agent.motion['position']['longlat_position']['longitude'], self._agent.motion['position']['longlat_position']['latitude'])]
602
561
 
603
- if heading == "front":
604
- heading_direction = self._agent.motion['direction']
605
- elif heading == "back":
606
- heading_direction += 180
607
- elif heading == "left":
608
- heading_direction += -90
609
- elif heading == "right":
610
- heading_direction += 90
611
- else:
612
- print("Wrong HEADING, Use FRONT")
613
- persp = []
614
- if self._engine == "baidumap":
615
- points = wgs842bd09mc(coords, self._baiduAK)
616
- sv = BaiduStreetView.search(points[0][0], points[0][1])
617
- eq = Equirectangular(sv)
618
- persp.append(eq.get_perspective(120, heading_direction-90, 20, 256, 512))
619
- persp.append(eq.get_perspective(120, heading_direction, 20, 256, 512))
620
- persp.append(eq.get_perspective(120, heading_direction+90, 20, 256, 512))
621
- persp.append(eq.get_perspective(120, heading_direction+180, 20, 256, 512))
622
- if save:
623
- date_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
624
- sv.panorama.save("{}/{}_{}_panorama_{}.jpg".format(save_dir, self._agent.name, date_time))
625
- for i in range(len(persp)):
626
- persp[i].save("{}/{}_{}_persp_{}.jpg".format(save_dir, self._agent.name, date_time, i))
627
- return persp
628
- elif self._engine == "googlemap":
629
- sv = GoogleStreetView.search(
630
- points[0][0],
631
- points[0][1],
632
- proxies=self._googleProxy,
633
- cache_dir=save_dir
634
- )
635
- eq = Equirectangular(sv)
636
- persp = eq.get_perspective(120, heading_direction, 20, 256, 512)
637
- if save:
638
- date_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
639
- sv.panorama.save("{}/{}_{}_panorama_{}.jpg".format(save_dir, self._agent.name, date_time))
640
- for i in range(len(persp)):
641
- persp[i].save("{}/{}_{}_persp_{}.jpg".format(save_dir, self._agent.name, date_time, i))
642
- return persp
643
-
644
- # * Person Related
645
- def PerceivePersonCircule(self, raduis=30):
646
- """Person环形感知"""
647
- print("Not Implemented")
648
- pass
562
+ heading_direction = self._agent.motion['direction']
563
+ try:
564
+ if self._engine == "baidumap":
565
+ points = wgs842bd09mc(coords, self._baiduAK)
566
+ sv = BaiduStreetView.search(points[0][0], points[0][1])
567
+ eq = Equirectangular(sv)
568
+ persp = eq.get_perspective(120, heading_direction, 0, 300, 2100)
569
+ return persp
570
+ elif self._engine == "googlemap":
571
+ sv = GoogleStreetView.search(
572
+ points[0][0],
573
+ points[0][1],
574
+ proxies=self._googleProxy,
575
+ cache_dir=save_dir
576
+ )
577
+ eq = Equirectangular(sv)
578
+ persp = eq.get_perspective(120, heading_direction, 0, 300, 2100)
579
+ return persp
580
+ except Exception as e:
581
+ print(f"Can't get streetview, error message: {e}")
582
+ return []
649
583
 
650
584
  async def PerceivePersonInLanes(self, lane_ids:list[int], only_id:bool=False):
651
585
  """
@@ -2,4 +2,4 @@
2
2
 
3
3
  from .hubconnector import *
4
4
 
5
- __all__ = [HubConnector]
5
+ __all__ = [HubConnector, Waypoint]
@@ -1,10 +1,332 @@
1
1
  """Apphub客户端定义"""
2
2
 
3
+ import base64
4
+ from dataclasses import dataclass
5
+ from io import BytesIO
6
+ from typing import Any, Dict, Optional, List
7
+
8
+ import requests
9
+ from requests.auth import HTTPBasicAuth
3
10
  from typing import Optional
4
11
  import geojson
5
- from pycitysim.apphub import AppHubClient, AgentMessage, UserMessage
12
+ from geojson import Feature, FeatureCollection, Point, LineString
6
13
  import PIL.Image as Image
7
- import traceback
14
+ import time
15
+
16
+ __all__ = ["HubConnector", "AppHubClient", "AgentMessage", "UserMessage", "Waypoint"]
17
+
18
+ def _image2base64(image: Image) -> str:
19
+ buffered = BytesIO()
20
+ image.save(buffered, format="PNG")
21
+ return base64.b64encode(buffered.getvalue()).decode("utf-8")
22
+
23
+ @dataclass
24
+ class AgentMessage:
25
+ id: int
26
+ """
27
+ 发送者Agent ID
28
+ Agent ID of sender
29
+ """
30
+ timestamp: int
31
+ """
32
+ 消息时间戳(单位:毫秒)
33
+ Message timestamp (milliseconds)
34
+ """
35
+ content: str
36
+ """
37
+ 消息内容
38
+ Message content
39
+ """
40
+ images: Optional[List[Image.Image]]
41
+ """
42
+ 消息图片
43
+ Message images
44
+ """
45
+ sub_messages: Optional[List["AgentMessage"]]
46
+
47
+ def to_dict(self):
48
+ d = {
49
+ "id": self.id,
50
+ "timestamp": self.timestamp,
51
+ "content": self.content,
52
+ }
53
+ if self.images is not None:
54
+ d["images"] = [_image2base64(image) for image in self.images]
55
+ if self.sub_messages is not None:
56
+ d["subMessages"] = [message.to_dict() for message in self.sub_messages]
57
+ return d
58
+
59
+ @dataclass
60
+ class Waypoint:
61
+ lnglat: List[float]
62
+ """
63
+ 坐标位置
64
+ """
65
+
66
+ t: int
67
+ """
68
+ 时间
69
+ """
70
+
71
+
72
+ @dataclass
73
+ class UserMessage:
74
+ id: int
75
+ """
76
+ 发送者User ID对应的Agent ID
77
+ Agent ID corresponding to the sender User ID
78
+ """
79
+ timestamp: int
80
+ """
81
+ 消息时间戳(单位:毫秒)
82
+ Message timestamp (milliseconds)
83
+ """
84
+ content: str
85
+ """
86
+ 消息内容
87
+ Message content
88
+ """
89
+
90
+
91
+ class AppHubClient:
92
+ """
93
+ AppHub客户端
94
+ AppHub client
95
+
96
+ # 操作流程
97
+ # Operating procedures
98
+
99
+ 1. 初始化AppHubClient,填入从前端获得的app_id和app_secret
100
+ 1. Initialize AppHubClient, fill in the app_id and app_secret obtained from the front end
101
+ 2. 调用bind_person/bind_org绑定模拟器内的人,获得agent_id
102
+ 2. Call bind_person/bind_org to bind the person in the simulator, and obtain the agent_id
103
+ 3. 调用update_agent_map更新agent的地图内容,以改变agent在地图上的显示
104
+ 3. Call update_agent_map to update the agent's map content, to change the agent's display on the map
105
+ 4. 调用update_agent_messages更新agent的消息内容,以改变agent的消息内容
106
+ 4. Call update_agent_messages to update the agent's message content, to change the agent's message content
107
+ 5. 定期调用fetch_user_message获取用户发送给agent的消息,并进行处理
108
+ 5. Call fetch_user_message regularly to obtain the messages sent by the user to the agent, and process them
109
+ 6. 如果不再使用agent,调用release_agent释放agent,以允许其他app绑定
110
+ 6. If the agent is no longer used, call release_agent to release the agent to allow other apps to bind.
111
+ """
112
+
113
+ def __init__(
114
+ self,
115
+ app_hub_url: str,
116
+ app_id: int,
117
+ app_secret: str,
118
+ proxies: Optional[dict] = None,
119
+ ):
120
+ """
121
+ Args:
122
+ - app_id: app ID,从前端注册页面获取。app ID, obtained from the frontend registration page.
123
+ - app_secret: app secret,从前端注册页面获取。app secret, obtained from the frontend registration page.
124
+ """
125
+ self.app_hub_url = app_hub_url
126
+ self.app_id = app_id
127
+ self.app_secret = app_secret
128
+ self.proxies = proxies
129
+ self.agents = {} # agent_id -> agent (bind body)
130
+
131
+ self._auth = HTTPBasicAuth(str(app_id), app_secret)
132
+
133
+ def _bind(self, foreign_id: int | None, type: str, name: str, avatar: Image) -> int:
134
+ if foreign_id == None:
135
+ foreign_id_ = -1
136
+ else:
137
+ foreign_id_ = foreign_id
138
+ body = {
139
+ "type": type,
140
+ "name": name,
141
+ "avatar": _image2base64(avatar),
142
+ "foreignID": foreign_id_,
143
+ }
144
+ res = requests.post(
145
+ self.app_hub_url + "/agents",
146
+ json=body,
147
+ auth=self._auth,
148
+ proxies=self.proxies,
149
+ timeout=5000
150
+ )
151
+ if res.status_code != 200:
152
+ raise Exception(f"[{res.status_code}] AppHub bind failed: " + res.text)
153
+ data = res.json()
154
+ agent_id = data["data"]["id"]
155
+ self.agents[agent_id] = body
156
+ return agent_id
157
+
158
+ def bind_person(self, person_id: int, name: str, avatar: Image):
159
+ """
160
+ 绑定模拟器内的人,只有绑定的person才能被访问
161
+ Bind person in the simulator. Only bound person can be accessed
162
+
163
+ Args:
164
+ - person_id: 模拟器内的人的ID。The ID of the person in the simulator.
165
+ - name: 人的名字(前端显示)。Person's name (displayed on the front end).
166
+ - avatar: 人的头像(前端显示)。Person's avatar (displayed on the front end).
167
+
168
+ Returns:
169
+ - agent_id: 绑定后的agent ID。the bound agent ID.
170
+
171
+ Raises:
172
+ - Exception: 绑定失败。Binding failed.
173
+ """
174
+ return self._bind(person_id, "person", name, avatar)
175
+
176
+ def bind_org(self, org_id: int, name: str, avatar: Image):
177
+ """
178
+ 绑定模拟器内的组织,只有绑定的org才能被访问
179
+ Bind the organization in the simulator. Only the bound org can be accessed
180
+
181
+ Args:
182
+ - org_id: 模拟器内的组织的ID。ID of the organization in the simulator
183
+ - name: 组织的名字(前端显示)。Organization's name (displayed on the front end).
184
+ - avatar: 组织的头像(前端显示)。Organization's avatar (displayed on the front end).
185
+
186
+ Returns:
187
+ - agent_id: 绑定后的agent ID。the bound agent ID.
188
+
189
+ Raises:
190
+ - Exception: 绑定失败。Binding failed.
191
+ """
192
+ return self._bind(org_id, "org", name, avatar)
193
+
194
+ def bind_func(self, name:str, avatar:Image):
195
+ """
196
+ 插入非模拟器相关的智能体——func类型智能体
197
+
198
+ Args:
199
+ - func_id (str): 该智能体的编号——唯一
200
+ - name (str): 该智能体的名字
201
+ - avatar (Image): 该智能体的头像
202
+ """
203
+ return self._bind(None, "func", name, avatar)
204
+
205
+ def release_agent(self, agent_id: int) -> bool:
206
+ """
207
+ 释放agent(person/org), 释放后agent将不再被访问并允许其他app绑定
208
+ Release the agent (person/org). After the release, the agent will no longer be accessed and allows other apps to bind.
209
+
210
+ Args:
211
+ - agent_id: agent的ID。ID of the agent.
212
+
213
+ Returns:
214
+ - bool: 是否成功。whether succeed.
215
+ """
216
+ res = requests.delete(
217
+ self.app_hub_url + "/agents/" + str(agent_id),
218
+ auth=self._auth,
219
+ proxies=self.proxies,
220
+ timeout=5000
221
+ )
222
+ return res.status_code == 200
223
+
224
+ def update_agent_map(
225
+ self,
226
+ agent_id: int,
227
+ lnglat: List[float],
228
+ geojsons: Optional[geojson.FeatureCollection] = None,
229
+ street_view: Optional[Image.Image] = None,
230
+ direction: Optional[float] = None,
231
+ popup: Optional[str] = None,
232
+ waypoints: Optional[List[Waypoint]] = None,
233
+ ):
234
+ """
235
+ 更新agent的地图内容
236
+ Update the agent’s map content
237
+
238
+ Args:
239
+ - agent_id: agent的ID。ID of the agent.
240
+ - lnglat: 经纬度,格式为 [lng, lat]。Latitude and longitude in [lng, lat].
241
+ - geojsons: geojson FeatureCollection,用于在地图上显示点线面。geojson FeatureCollection, used to display points, lines and polygons on the map.
242
+ - street_view: 街景图片。Street view images.
243
+ - popup: 弹出框内容。Pop-up box content.
244
+
245
+ Raises:
246
+ - Exception: 更新失败。Updating failed.
247
+ """
248
+
249
+ body: Dict[str, Any] = {
250
+ "lnglat": lnglat,
251
+ }
252
+ if geojsons is not None:
253
+ body["geojsons"] = geojsons
254
+ if street_view is not None:
255
+ body["streetView"] = _image2base64(street_view)
256
+ if direction is not None:
257
+ body["direction"] = direction
258
+ if popup is not None:
259
+ body["popup"] = popup
260
+ if waypoints is not None:
261
+ body["waypoints"] = [{'lnglat': wp.lnglat, 't': wp.t} for wp in waypoints]
262
+ waypoints[0].lnglat
263
+
264
+ res = requests.put(
265
+ self.app_hub_url + "/agents/" + str(agent_id) + "/map",
266
+ json=body,
267
+ auth=self._auth,
268
+ proxies=self.proxies,
269
+ timeout=5000
270
+ )
271
+ if res.status_code != 200:
272
+ raise Exception(
273
+ f"[{res.status_code}] AppHub update map failed: " + res.text
274
+ )
275
+
276
+ def update_agent_messages(self, agent_id: int, messages: List[AgentMessage]):
277
+ """
278
+ 更新agent的消息
279
+ Update agent's messages
280
+
281
+ Args:
282
+ - agent_id: agent的ID。ID of the agent.
283
+ - messages: 消息列表(注意顺序,最新的消息在最后)。Message list (note the order, the latest message at the end).
284
+
285
+ Raises:
286
+ - Exception: 更新失败。Updating failed.
287
+ """
288
+ res = requests.put(
289
+ self.app_hub_url + "/agents/" + str(agent_id) + "/messages",
290
+ json={"messages": [message.to_dict() for message in messages]},
291
+ auth=self._auth,
292
+ proxies=self.proxies,
293
+ timeout=5000
294
+ )
295
+ if res.status_code != 200:
296
+ raise Exception(
297
+ f"[{res.status_code}] AppHub update messages failed: " + res.text
298
+ )
299
+
300
+ def fetch_user_messages(
301
+ self,
302
+ agent_id: int,
303
+ ) -> List[UserMessage]:
304
+ """
305
+ 获取用户发送给agent的消息
306
+ Fetch the message sent by the user to the agent
307
+
308
+ Args:
309
+ - agent_id: agent的ID。ID of the agent.
310
+
311
+ Returns:
312
+ - messages: 消息列表。Message list.
313
+
314
+ Raises:
315
+ - Exception: 获取失败。Fetching failed.
316
+ """
317
+ res = requests.post(
318
+ self.app_hub_url + "/agents/" + str(agent_id) + "/fetch-user-messages",
319
+ auth=self._auth,
320
+ proxies=self.proxies,
321
+ timeout=5000
322
+ )
323
+ if res.status_code != 200:
324
+ raise Exception(
325
+ f"[{res.status_code}] AppHub fetch messages failed: " + res.text
326
+ )
327
+ data = res.json()
328
+ return [UserMessage(**message) for message in data["data"]]
329
+
8
330
 
9
331
  class HubConnector:
10
332
  """
@@ -62,12 +384,11 @@ class HubConnector:
62
384
  Insert the Func Agent to AppHub
63
385
  """
64
386
  self._agent_id = self._client.bind_func(
65
- self._agent._id,
66
387
  self._agent._name,
67
388
  Image.open(self._profile_img)
68
389
  )
69
390
 
70
- def Update(self, messages:Optional[list[AgentMessage]]=None, streetview:Image.Image=None, longlat:list[float]=None, pop:str=None):
391
+ def Update(self, messages:Optional[list[AgentMessage]]=None, streetview:Image=None, longlat:list[float]=None, pop:str=None):
71
392
  """
72
393
  交互更新
73
394
  FrontEnd Related Update
@@ -79,7 +400,7 @@ class HubConnector:
79
400
  - messages (Optional[list[AgentMessage]]):
80
401
  - 需要传递到前端侧边栏的消息. Messsages that will be shown in the message bar in frontend
81
402
  - 默认为None(即当前无需传递消息). Default: None(i.e. No new messages need to be shown in frontend)
82
- - streetview (PIL.Image.Image):
403
+ - streetview (list[PIL.Image.Image]):
83
404
  - 街景图片. Streetview Image
84
405
  - 默认为None(即本次更新不展示街景). Default: None(i.e. No streetview in this update)
85
406
  - longlat (list(float)):
@@ -93,19 +414,27 @@ class HubConnector:
93
414
  print("AppHub: Not Bind Agent Yet")
94
415
  return
95
416
  else:
96
- pop_ = self._agent._name
417
+ pop_ = None
97
418
  if pop != None:
98
419
  pop_ = self._agent.agent_name + ": " + pop
99
420
  if longlat != None:
100
421
  longlat_ = longlat
422
+ t_size = len(self._agent._history_trajectory)
423
+ self._agent._history_trajectory.append(Waypoint([longlat[0], longlat[1]], t_size*5000))
101
424
  else:
102
425
  longlat_ = [self._agent.motion['position']['longlat_position']['longitude'], self._agent.motion['position']['longlat_position']['latitude']]
103
-
426
+
427
+ if 'direction' in self._agent.motion.keys():
428
+ direction = self._agent.motion['direction']
429
+ else:
430
+ direction = None
104
431
  self._client.update_agent_map(
105
432
  agent_id = self._agent_id,
106
433
  lnglat = longlat_,
434
+ direction=direction,
107
435
  street_view=streetview,
108
- popup=pop_
436
+ popup=pop_,
437
+ waypoints=self._agent._history_trajectory
109
438
  )
110
439
  if messages != None:
111
440
  self.messageBuffer += messages
@@ -114,6 +443,22 @@ class HubConnector:
114
443
  messages=self.messageBuffer
115
444
  )
116
445
 
446
+ def ShowGeo(self, geojsons: geojson.FeatureCollection):
447
+ """
448
+ - 发送地图展示要素
449
+
450
+ Args:
451
+ - geojsons (geojson.FeatureCollection): 需要展示的地图要素
452
+ """
453
+ if self._client == None:
454
+ print("AppHub: Not Bind Agent Yet")
455
+ return
456
+ self._client.update_agent_map(
457
+ agent_id=self._agent_id,
458
+ lnglat=[self._agent.motion['position']['longlat_position']['longitude'], self._agent.motion['position']['longlat_position']['latitude']],
459
+ geojsons=geojsons
460
+ )
461
+
117
462
  def GetMessageFromHub(self) -> list[UserMessage]:
118
463
  """
119
464
  获取前端messages