SpatialWebAgent commited on
Commit
b04ba00
·
verified ·
1 Parent(s): 0b8e6eb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1 -413
app.py CHANGED
@@ -10,414 +10,6 @@ from openai import OpenAI
10
  import numpy as np
11
  import os
12
 
13
- api_key = os.getenv('api_key')
14
- client = OpenAI(
15
- api_key=api_key
16
- )
17
-
18
- model = "gpt-4o"
19
-
20
- north = ["north", "N'", "North", "NORTH"]
21
- south = ["south", "S'", "South", "SOUTH"]
22
- east = ["east", "E'", "East", "EAST"]
23
- west = ["west", "W'", "West", "WEST"]
24
- northeast = ["north-east", "NE'", "north east", "NORTH-EAST", "North East", "NORTH EAST"]
25
- southeast = ["south-east", "SE'", "south east", "SOUTH-EAST", "South East", "SOUTH EAST"]
26
- northwest = ["north-west", "NW'", "north west", "NORTH-WEST", "North West", "NORTH WEST"]
27
- southwest = ["south-west", "SW'", "south west", "SOUTH-WEST", "South West", "SOUTH WEST"]
28
- center = ["center","central", "downtown","midtown"]
29
-
30
-
31
-
32
-
33
- def to_standard_2d_list(data):
34
- arr = np.array(data)
35
-
36
- # 强制变成一维后 reshape,前提是元素总数是2的倍数
37
- flat = arr.flatten()
38
- if flat.size % 2 != 0:
39
- raise ValueError("元素个数不是2的倍数,不能 reshape 成 [N, 2] 格式")
40
-
41
- return flat.reshape(-1, 2).tolist()
42
-
43
-
44
- def get_geojson(ent, arr, centroid):
45
- poly_json = {}
46
- poly_json['type'] = 'FeatureCollection'
47
- poly_json['features'] = []
48
- coordinates= []
49
- coordinates.append(arr)
50
- poly_json['features'].append({
51
- 'type':'Feature',
52
- 'id': ent,
53
- 'properties': {
54
- 'centroid': centroid
55
- },
56
- 'geometry': {
57
- 'type':'Polygon',
58
- 'coordinates': coordinates
59
- }
60
- })
61
- return poly_json
62
-
63
-
64
- def get_coordinates(ent):
65
- request_url = 'https://nominatim.openstreetmap.org/search.php?q= ' +ent +'&polygon_geojson=1&accept-language=en&format=jsonv2'
66
- headers = {
67
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3 Safari/605.1.15"
68
- }
69
- page = requests.get(request_url, headers=headers, verify=False)
70
- json_content = json.loads(page.content)
71
- all_coordinates = json_content[0]['geojson']['coordinates'][0]
72
- centroid = (float(json_content[0]['lon']), float(json_content[0]['lat']))
73
- for p in all_coordinates:
74
- p2 = (p[0], p[1])
75
- angle = geoutil.calculate_bearing(centroid, p2)
76
- p.append(angle)
77
-
78
- geojson = get_geojson(ent, all_coordinates, centroid)
79
-
80
- return geojson['features'][0]['geometry']['coordinates'][0], geojson['features'][0]['properties']['centroid']
81
-
82
- def get_coordinates(location):
83
- request_url = f'https://nominatim.openstreetmap.org/search.php?q={location}&polygon_geojson=1&accept-language=en&format=jsonv2'
84
-
85
- print(request_url)
86
- headers = {"User-Agent": "Mozilla/5.0"}
87
- response = requests.get(request_url, headers=headers, verify=False)
88
- json_content = json.loads(response.content)
89
- # print(json_content)
90
- if json_content[0]['geojson']['type'] == 'Polygon':
91
- coordinates = json_content[0]['geojson']['coordinates'][0]
92
- elif json_content[0]['geojson']['type'] == 'Point':
93
- coordinates = json_content[0]['geojson']['coordinates']
94
- else:
95
- print(json_content[0]['geojson']['type'])
96
- centroid = (float(json_content[0]['lon']), float(json_content[0]['lat']))
97
- return (coordinates, centroid)
98
-
99
-
100
- # level3
101
- def get_directional_coordinates_by_angle(coordinates, centroid, direction, minimum, maximum):
102
- # minimum = 157
103
- # maximum = 202
104
-
105
- direction_coordinates = []
106
- for p in coordinates:
107
- angle = geoutil.calculate_bearing(centroid, p)
108
- p2 = (p[0], p[1], angle)
109
- if direction in geo_level1.east:
110
- if angle >= minimum or angle <= maximum:
111
- direction_coordinates.append(p2)
112
-
113
- else:
114
- if angle >= minimum and angle <= maximum:
115
- direction_coordinates.append(p2)
116
- # print(type(direction_coordinates[0]))
117
- # if(direction in geo_level1.west):
118
- # direction_coordinates.sort(key=lambda k: k[2], reverse=True)
119
-
120
- return direction_coordinates
121
- def get_level3(level3):
122
- digits = re.findall('[0-9]+', level3)[0]
123
- unit = re.findall('[A-Za-z]+', level3)[0]
124
- return digits, unit
125
-
126
- def get_direction_coordinates(coordinates, centroid, level1):
127
- min_max = geo_level1.get_min_max(level1)
128
- if min_max is not None:
129
- coord = get_directional_coordinates_by_angle(coordinates, centroid, level1, min_max[0], min_max[1])
130
- return coord
131
- return coordinates
132
- def sort_west(poly1, poly2, centroid):
133
- coords1 = mapping(poly1)["features"][0]["geometry"]["coordinates"]
134
- coords2 = mapping(poly2)["features"][0]["geometry"]["coordinates"]
135
- coord1 = []
136
- coord2 = []
137
- coord = []
138
- for c in coords1:
139
- pol = list(c[::-1])
140
- coord1.extend(pol)
141
- for c in coords2:
142
- pol = list(c[::-1])
143
- coord2.extend(pol)
144
- coo1 = []
145
- coo2 = []
146
- for p in coord1:
147
- angle = geoutil.calculate_bearing(centroid, p)
148
- if angle >= 157 and angle <= 202:
149
- coo1.append((p[0], p[1], angle))
150
- for p in coord2:
151
- angle = geoutil.calculate_bearing(centroid, p)
152
- if angle >= 157 and angle <= 202:
153
- coo2.append((p[0], p[1], angle))
154
- coo1.extend(coo2)
155
- return coo1
156
-
157
-
158
- def get_level3_coordinates(coordinates, level_3, level1):
159
- distance, unit = get_level3(level_3)
160
- kms = geoutil.get_kilometers(distance, unit)
161
- coord = []
162
-
163
- coords0, center = coordinates
164
-
165
- if not isinstance(coords0, list) or len(coords0) < 3:
166
-
167
- # 从原始点出发,根据方向移动距离 kms 得到新圆心
168
- lat_km = 111.32
169
- lon_km = 111.32 * np.cos(np.radians(center[1]))
170
-
171
- dx = dy = 0
172
-
173
- if level1 is not None:
174
- if level1 in geo_level1.east:
175
- dx = kms / lon_km
176
- elif level1 in geo_level1.west:
177
- dx = -kms / lon_km
178
- elif level1 in geo_level1.north:
179
- dy = kms / lat_km
180
- elif level1 in geo_level1.south:
181
- dy = -kms / lat_km
182
- # 你也可以支持 northeast、southwest 等复合方向
183
-
184
- new_center = (center[0] + dx, center[1] + dy)
185
-
186
- # 用固定半径画个圆(例如半径2km)
187
- r_km = 1 # 半径设为1km,你也可以设为其他值
188
-
189
- circle_points = []
190
- for theta in np.linspace(0, 360, num=100):
191
- theta_rad = np.radians(theta)
192
- d_lat = (np.sin(theta_rad) * r_km) / lat_km
193
- d_lon = (np.cos(theta_rad) * r_km) / lon_km
194
- circle_points.append((new_center[0] + d_lon, new_center[1] + d_lat))
195
-
196
- # 输出中心(使用新圆心)
197
- if circle_points:
198
- center_point = MultiPoint(circle_points).centroid
199
- center = (center_point.x, center_point.y)
200
- else:
201
- center = new_center
202
-
203
- return circle_points, center
204
-
205
- # 正常 polygon 流程
206
- poly1 = Polygon(coords0)
207
- polygon1 = gpd.GeoSeries(poly1)
208
-
209
- # 生成环形区域
210
- poly2 = polygon1.buffer(0.0095 * kms, join_style=2)
211
- poly3 = polygon1.buffer(0.013 * kms, join_style=2)
212
- poly = poly3.difference(poly2)
213
-
214
- # 获取坐标
215
- coords = mapping(poly)["features"][0]["geometry"]["coordinates"]
216
- for c in coords:
217
- pol = list(c[::-1])
218
- coord.extend(pol)
219
-
220
- # 方向裁剪
221
- if level1 is not None:
222
- coord = get_direction_coordinates(coord, coordinates[1], level1)
223
- if level1 in geo_level1.west:
224
- coord = sort_west(poly3, poly2, coordinates[1])
225
-
226
- # 计算质心
227
- if coord:
228
- center_point = MultiPoint(coord).centroid
229
- center = (center_point.x, center_point.y)
230
- else:
231
- center = coordinates[1]
232
-
233
- return coord, center
234
- # level 3 end
235
-
236
- # between
237
- def get_between_coordinates(coordinates1, coordinates2):
238
- """
239
- 计算两个区域之间的中间点,并生成一个等面积的圆形区域。
240
- 如果某个输入仅为点(坐标长度 < 3),则其面积设为 0;
241
- 如果两个输入都是点,则默认半径为 2km。
242
- :param coordinates1: 第一个区域的边界坐标和中心点
243
- :param coordinates2: 第二个区域的边界坐标和中心点
244
- :return: 圆形区域的坐标集和圆心
245
- """
246
-
247
- def is_valid_polygon(coords):
248
- return isinstance(coords, list) and len(coords) >= 3
249
-
250
- coords1, center1 = coordinates1
251
- coords2, center2 = coordinates2
252
-
253
- # 判断输入是否为合法多边形(>=3个点)
254
- if is_valid_polygon(coords1):
255
- poly1 = Polygon(coords1)
256
- area1 = poly1.area
257
- else:
258
- area1 = 0
259
-
260
- if is_valid_polygon(coords2):
261
- poly2 = Polygon(coords2)
262
- area2 = poly2.area
263
- else:
264
- area2 = 0
265
-
266
- # 计算中心点(两个中心的中点)
267
- midpoint = (
268
- (center1[0] + center2[0]) / 2,
269
- (center1[1] + center2[1]) / 2
270
- )
271
-
272
- # 如果两个区域都是点,则使用默认半径 2km
273
- if area1 == 0 and area2 == 0:
274
- r_km = 2
275
- else:
276
- avg_area = (area1 + area2) / 2
277
- r_km = np.sqrt(avg_area / np.pi) * 111.32 # 近似 km 半径
278
-
279
- # 经纬度距离换算因子
280
- lat_km = 111.32
281
- lon_km = 111.32 * np.cos(np.radians(midpoint[1]))
282
-
283
- # 生成圆形区域坐标(100个点)
284
- circle_points = []
285
- for theta in np.linspace(0, 360, num=100):
286
- theta_rad = np.radians(theta)
287
- d_lat = (np.sin(theta_rad) * r_km) / lat_km
288
- d_lon = (np.cos(theta_rad) * r_km) / lon_km
289
- circle_points.append((midpoint[0] + d_lon, midpoint[1] + d_lat))
290
-
291
- return circle_points, midpoint
292
- # between end
293
-
294
-
295
- def llmapi(text):
296
- system_prompt = (
297
- "你是一个资深的地理学家,你的任务是通过给定的一段自然语言,来选择正确的定位函数顺序以及他们的输入。\n"
298
- "你能选择的定位函数有:\n"
299
- "1. 相对定位(Relative Positioning):输入为地点坐标,方位,距离。输出为距离‘距离’输入的地点坐标的‘方位’的坐标。\n"
300
- "2. 中间定位(Between Positioning):输入为两个地点的坐标,输出为���个地点坐标的中点。\n"
301
- "请先进行思维链(CoT)推理,并最终用 JSON 格式输出你的答案,用 `<<<JSON>>>` 和 `<<<END>>>` 包裹起来。\n"
302
- "请确保所有输入仅包含:地点名称(字符串)、索引(整数)、方位(字符串,必须是英文)或距离(字符串,带单位),不允许返回诸如 'Chatswood 南4 km的坐标' 这样的内容。\n"
303
- "每个步骤编号都有 id 记录,然后如果某个输入是之前步骤的输出,那么输入对应步骤的 id。\n"
304
- "所有方向必须使用英文(如 south, west, northeast, etc.)。\n"
305
- "示例输出:\n"
306
- "<<<JSON>>>\n"
307
- "[{\"id\": 1, \"function\": \"Relative\", \"inputs\": [\"Chatswood\", \"south\", \"4 km\"]},"
308
- "{\"id\": 2, \"function\": \"Relative\", \"inputs\": [\"North Sydney\", \"west\", \"2 km\"]},"
309
- "{\"id\": 3, \"function\": \"Between\", \"inputs\": [1, 2]},"
310
- "{\"id\": 4, \"function\": \"Relative\", \"inputs\": [3, \"southwest\", \"5 km\"]}]\n"
311
- "<<<END>>>")
312
-
313
- messages = [
314
- {"role": "system", "content": system_prompt},
315
- {"role": "user", "content": text},
316
- ]
317
-
318
- chat_completion = client.chat.completions.create(
319
- messages=messages,
320
- model=model,
321
- )
322
-
323
- result = chat_completion.choices[0].message.content
324
- json_match = re.search(r'<<<JSON>>>\n(.*?)\n<<<END>>>', result, re.DOTALL)
325
-
326
- if json_match:
327
- # print(json.loads(json_match.group(1)))
328
- return json.loads(json_match.group(1))
329
- else:
330
- raise ValueError("LLM 输出未包含预期的 JSON 格式数据。")
331
- def llmapi(text):
332
- system_prompt = (
333
- "You are an experienced geographer. Your task is to determine the correct sequence of positioning functions and their inputs based on a given piece of natural language.\n"
334
- "The positioning functions you can choose from are:\n"
335
- "1. Relative Positioning: Inputs is (location coordinate or location name, direction, and distance). Outputs the coordinates that are in the given 'direction' and 'distance' from the input location.\n"
336
- "2. Between Positioning: Inputs is (location 1 coordinates or location 1 name, location 2 coordinates or location 2 name). Outputs the midpoint coordinate between the two locations.\n"
337
- "You can only use the given functions, and the inputs to the functions must obey the above properties. The given functions can be combined to solve complex situations."
338
- "First, perform chain-of-thought (CoT) reasoning, and finally output your answer in JSON format, wrapped between `<<<JSON>>>` and `<<<END>>>`.\n"
339
- "Make sure all inputs only include: location names (strings), step indices (integers), directions (strings, must be in English), or distances (strings with units). Do not return expressions like 'the coordinate 4 km south of Chatswood'.\n"
340
- "Each step must have an 'id'. If the input of a step is the output of a previous step, use that step’s 'id' as the input.\n"
341
- "All directions must be in English (e.g., south, west, northeast, etc.).\n"
342
- "Example output:\n"
343
- "<<<JSON>>>\n"
344
- "[{\"id\": 1, \"function\": \"Relative\", \"inputs\": [\"Chatswood\", \"south\", \"4 km\"]},"
345
- "{\"id\": 2, \"function\": \"Relative\", \"inputs\": [\"North Sydney\", \"west\", \"2 km\"]},"
346
- "{\"id\": 3, \"function\": \"Between\", \"inputs\": [1, 2]},"
347
- "{\"id\": 4, \"function\": \"Relative\", \"inputs\": [3, \"southwest\", \"5 km\"]}]\n"
348
- "<<<END>>>")
349
-
350
- messages = [
351
- {"role": "system", "content": system_prompt},
352
- {"role": "user", "content": text},
353
- ]
354
-
355
- chat_completion = client.chat.completions.create(
356
- messages=messages,
357
- model=model,
358
- )
359
-
360
- result = chat_completion.choices[0].message.content
361
- print(result)
362
- json_match = re.search(r'<<<JSON>>>\n(.*?)\n<<<END>>>', result, re.DOTALL)
363
-
364
- if json_match:
365
- return json.loads(json_match.group(1))
366
- else:
367
- raise ValueError("LLM 输出未包含预期的 JSON 格式数据。")
368
-
369
-
370
-
371
-
372
-
373
- def execute_steps(steps):
374
- data = {}
375
-
376
- for step in steps:
377
- step_id = step['id']
378
- function = step['function']
379
- inputs = step['inputs']
380
- # print('-' * 50)
381
- # print(function)
382
- # print(inputs)
383
-
384
-
385
- resolved_inputs = []
386
- for inp in inputs:
387
- if isinstance(inp, int):
388
- resolved_inputs.append(data[inp])
389
- else:
390
- resolved_inputs.append(inp)
391
- if function == "Relative":
392
- location, direction, distance = resolved_inputs
393
- if isinstance(location, str):
394
- location = get_coordinates(location)
395
-
396
- location = [to_standard_2d_list(location[0])] + list(location[1:])
397
- location = [[[151.214901,-33.859175]], (151.214901,-33.859175)]
398
- result = get_level3_coordinates(location, distance, direction)
399
- data[step_id] = result
400
-
401
- elif function == "Between":
402
-
403
-
404
- location1, location2 = resolved_inputs
405
- # print(location1)
406
- # print(111)
407
- # print(location2)
408
- if isinstance(location1, str):
409
- location1 = get_coordinates(location1)
410
-
411
- location1 = [to_standard_2d_list(location1[0])] + list(location1[1:])
412
- if isinstance(location2, str):
413
-
414
- location2 = get_coordinates(location2)
415
- location2 = [to_standard_2d_list(location2[0])] + list(location2[1:])
416
- result = get_between_coordinates(location1, location2)
417
-
418
- data[step_id] = result
419
-
420
- return data
421
 
422
 
423
  def process_api(input_text):
@@ -429,12 +21,8 @@ def process_api(input_text):
429
  # "result": f"Processed: {nlp(input_text).to_json()}",
430
  # "timestamp": time.time()
431
  # }
432
- parsed_steps = llmapi(input_text)
433
- result = execute_steps(parsed_steps)
434
- coords = result[(max(result.keys()))]
435
 
436
- geojson = get_geojson(None, coords[0], coords[1])
437
- return geojson
438
 
439
  request_url = 'https://nominatim.openstreetmap.org/search.php?q=Glebe&polygon_geojson=1&accept-language=en&format=jsonv2'
440
  headers = {
 
10
  import numpy as np
11
  import os
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
14
 
15
  def process_api(input_text):
 
21
  # "result": f"Processed: {nlp(input_text).to_json()}",
22
  # "timestamp": time.time()
23
  # }
 
 
 
24
 
25
+ return input_text.upper()
 
26
 
27
  request_url = 'https://nominatim.openstreetmap.org/search.php?q=Glebe&polygon_geojson=1&accept-language=en&format=jsonv2'
28
  headers = {