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.
- pycityagent/__init__.py +2 -1
- pycityagent/ac/__init__.py +1 -1
- pycityagent/ac/citizen_actions/trip.py +2 -3
- pycityagent/ac/hub_actions.py +216 -16
- pycityagent/agent.py +2 -0
- pycityagent/agent_citizen.py +10 -2
- pycityagent/agent_func.py +93 -13
- pycityagent/agent_group.py +84 -0
- pycityagent/brain/brain.py +1 -0
- pycityagent/brain/memory.py +15 -15
- pycityagent/brain/scheduler.py +1 -1
- pycityagent/brain/sence.py +74 -140
- pycityagent/hubconnector/__init__.py +1 -1
- pycityagent/hubconnector/hubconnector.py +353 -8
- pycityagent/simulator.py +149 -7
- pycityagent/urbanllm/urbanllm.py +305 -5
- pycityagent/utils.py +178 -0
- {pycityagent-1.1.9.dist-info → pycityagent-1.1.11.dist-info}/METADATA +11 -11
- {pycityagent-1.1.9.dist-info → pycityagent-1.1.11.dist-info}/RECORD +22 -20
- {pycityagent-1.1.9.dist-info → pycityagent-1.1.11.dist-info}/WHEEL +1 -1
- {pycityagent-1.1.9.dist-info → pycityagent-1.1.11.dist-info}/LICENSE +0 -0
- {pycityagent-1.1.9.dist-info → pycityagent-1.1.11.dist-info}/top_level.txt +0 -0
pycityagent/brain/sence.py
CHANGED
@@ -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.
|
225
|
-
|
226
|
-
|
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
|
-
|
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
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
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
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
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
|
"""
|
@@ -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
|
12
|
+
from geojson import Feature, FeatureCollection, Point, LineString
|
6
13
|
import PIL.Image as Image
|
7
|
-
import
|
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
|
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_ =
|
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
|