point.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. from fastapi import APIRouter, Request, Depends, Query, HTTPException, status,Path
  4. from common.security import valid_access_token
  5. from fastapi.responses import JSONResponse,Response
  6. from fastapi.responses import StreamingResponse
  7. from sqlalchemy.orm import Session
  8. from sqlalchemy import and_, or_,text,literal
  9. from sqlalchemy.sql import func
  10. from sqlalchemy.future import select
  11. from common.auth_user import *
  12. from pydantic import BaseModel
  13. from typing import Any, Dict
  14. # import contextily as ctx
  15. # import geopandas as gpd
  16. # from matplotlib import pyplot as plt
  17. import io
  18. from database import get_db
  19. from typing import List
  20. from models import *
  21. from utils import *
  22. from utils.ry_system_util import *
  23. from utils.video_util import *
  24. from collections import defaultdict
  25. import traceback
  26. from concurrent.futures import ThreadPoolExecutor, as_completed
  27. from multiprocessing import Pool, cpu_count
  28. import json
  29. import time
  30. import math
  31. from shapely import wkb
  32. from shapely.geometry import Point
  33. router = APIRouter()
  34. def get_geom(db,iszjcj,pac):
  35. if iszjcj=='zj':
  36. pac = pac[:6]
  37. zjcjtable = 'tp_geojson_data_qx'
  38. elif iszjcj=='cj':
  39. pac = pac[:9]
  40. zjcjtable = 'tp_geojson_data_zj'
  41. sql = f"""select name, ST_AsBinary(geometry) as geometry from {zjcjtable} where pac like '{pac}%'"""
  42. result = db.execute(sql)
  43. infos = result.fetchall()
  44. return infos
  45. def get_contains(db,iszjcj,pac,lon,lat):
  46. if iszjcj=='zj':
  47. pac = pac[:6]
  48. zjcjtable = 'tp_geojson_data_qx'
  49. elif iszjcj=='cj':
  50. pac = pac[:9]
  51. zjcjtable = 'tp_geojson_data_zj'
  52. sql = f"""select 1 from {zjcjtable} where pac like '{pac}%' and ST_Intersects(geometry, ST_SRID(POINT({lon}, {lat}), 4326))"""
  53. result = db.execute(sql)
  54. infos = result.fetchall()
  55. return len(infos)
  56. @router.post("/get_info")
  57. @router.get("/get_info")
  58. async def get_infos(
  59. body = Depends(remove_xss_json),
  60. # zoom_level: float = Query(..., description="Zoom level for clustering"),
  61. # latitude_min: float = Query(..., description="Minimum latitude"),
  62. # latitude_max: float = Query(..., description="Maximum latitude"),
  63. # longitude_min: float = Query(..., description="Minimum longitude"),
  64. # longitude_max: float = Query(..., description="Maximum longitude"),
  65. # dict_value: str = Query(None),
  66. # option:str = Query(None),
  67. db: Session = Depends(get_db)
  68. ):
  69. try:
  70. # 根据缩放级别动态调整分组粒度
  71. zoom_level = float(body['zoom_level'])
  72. zoom_levels = {
  73. 3: 10000, # 全国范围
  74. 4: 5000,
  75. 5: 2500,
  76. 6: 1250,
  77. 7: 825,
  78. 8: 412.5,
  79. 9: 256.25,
  80. 10: 178.125,
  81. 11: 69.0625,
  82. 12: 29.53125,
  83. 13: 13.765625,
  84. 14: 5.8828125,
  85. 15: 2.44140625,
  86. 16: 1.220703125,
  87. 17: 0.6103515625,
  88. 18: 0.30517578125
  89. }
  90. distance_threshold=zoom_levels[int(zoom_level-1)]
  91. # distance_threshold = 100000 / (2.2 ** zoom_level) # 例如:每缩放一级,距离阈值减半
  92. dict_value= body['dict_value'].split(',')
  93. latitude_min = float(body['latitude_min'])
  94. latitude_max = float(body['latitude_max'])
  95. longitude_min = float(body['longitude_min'])
  96. longitude_max = float(body['longitude_max'])
  97. option = body['option'].split(',')
  98. print("1",time.time())
  99. iszjcj = ''
  100. pac = ''
  101. if 'iszjcj'in body:
  102. iszjcj = body['iszjcj']
  103. pac = body['pac']
  104. videos = get_videos(db,dict_value,latitude_min,latitude_max,longitude_min,longitude_max,iszjcj)
  105. infos = get_points(db,option,latitude_min,latitude_max,longitude_min,longitude_max,iszjcj,pac)
  106. # 动态分组逻辑
  107. if 'iszjcj' in body:
  108. iszjcj = body['iszjcj']
  109. pac = body['pac']
  110. groups = group_points(videos + infos, distance_threshold,db,iszjcj,pac)
  111. else:
  112. groups = group_points(videos+infos, distance_threshold)
  113. print("4",time.time())
  114. return {"code": 200,
  115. "msg": "操作成功",
  116. "data": groups}
  117. except Exception as e:
  118. # 处理异常
  119. traceback.print_exc()
  120. raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
  121. @router.post("/get_details")
  122. @router.get("/get_details")
  123. async def get_details(
  124. body = Depends(remove_xss_json),
  125. # center_latitude: float = Query(..., description="网格中心点的纬度"),
  126. # center_longitude: float = Query(..., description="网格中心点的经度"),
  127. # zoom_level: float = Query(..., description="缩放级别"),
  128. db: Session = Depends(get_db)
  129. ):
  130. try:
  131. # 计算网格大小
  132. zoom_level = float(body['zoom_level'])
  133. zoom_levels = {
  134. 3: 10000, # 全国范围
  135. 4: 5000,
  136. 5: 2500,
  137. 6: 1250,
  138. 7: 825,
  139. 8: 412.5,
  140. 9: 256.25,
  141. 10: 178.125,
  142. 11: 69.0625,
  143. 12: 29.53125,
  144. 13: 13.765625,
  145. 14: 5.8828125,
  146. 15: 2.44140625,
  147. 16: 1.220703125,
  148. 17: 0.6103515625,
  149. 18: 0.30517578125
  150. }
  151. distance_threshold=zoom_levels[int(zoom_level-1)]
  152. # distance_threshold = 1000 / (1.5 ** zoom_level) # 例如:每缩放一级,距离阈值减半
  153. grid_size = calculate_grid_size(distance_threshold) # 地球半径为6371公里
  154. center_latitude = float(body['latitude'])
  155. center_longitude = float(body['longitude'])
  156. dict_value = body['dict_value'].split(',')
  157. option = body['option'].split(',')
  158. # 计算网格的经纬度范围
  159. latitude_min, latitude_max, longitude_min, longitude_max = get_grid_bounds_from_center(center_latitude, center_longitude, grid_size)
  160. videos = get_videos(db,dict_value,latitude_min,latitude_max,longitude_min,longitude_max)
  161. infos = get_points(db,option,latitude_min,latitude_max,longitude_min,longitude_max)
  162. return {"code": 200,
  163. "msg": "操作成功",
  164. "data": videos+infos }#{"videos":videos,"points":infos}}
  165. except Exception as e:
  166. # 处理异常
  167. traceback.print_exc()
  168. raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
  169. def calculate_grid_size(distance_threshold):
  170. # 假设地球半径为6371公里,将距离阈值转换为经纬度的差值
  171. # 这里假设纬度变化对距离的影响较小,仅根据经度计算网格大小
  172. earth_radius = 6371 # 地球半径,单位为公里
  173. grid_size = distance_threshold / earth_radius
  174. return grid_size
  175. def get_grid_key(latitude, longitude, grid_size):
  176. # 根据经纬度和网格大小计算网格键
  177. return (math.floor(latitude / grid_size), math.floor(longitude / grid_size))
  178. def get_grid_bounds_from_center(center_latitude, center_longitude, grid_size):
  179. half_grid_size = grid_size / 2
  180. min_latitude = center_latitude - half_grid_size
  181. max_latitude = center_latitude + half_grid_size
  182. min_longitude = center_longitude - half_grid_size
  183. max_longitude = center_longitude + half_grid_size
  184. return min_latitude, max_latitude, min_longitude, max_longitude
  185. def calculate_distance(point1, point2):
  186. # 使用 Haversine 公式计算两点之间的距离
  187. from math import radians, sin, cos, sqrt, atan2
  188. R = 6371 # 地球半径(公里)
  189. lat1, lon1 = radians(point1.latitude), radians(point1.longitude)
  190. lat2, lon2 = radians(point2.latitude), radians(point2.longitude)
  191. dlat = lat2 - lat1
  192. dlon = lon2 - lon1
  193. a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2
  194. c = 2 * atan2(sqrt(a), sqrt(1 - a))
  195. return R * c
  196. def group_points(points, distance_threshold,db=get_db(),iszjcj='',pac=''):
  197. grid_size = calculate_grid_size(distance_threshold)
  198. grid = defaultdict(lambda:{"count":0}) #,"list":[]
  199. groups = []
  200. tmp = defaultdict(list)
  201. for point in points:
  202. grid_key = get_grid_key(float(point.latitude), float(point.longitude), grid_size)
  203. lovalue = str(point.latitude)+str(point.longitude)
  204. if lovalue not in tmp['%s-%s'%grid_key]:
  205. tmp['%s-%s'%grid_key].append(lovalue)
  206. grid['%s-%s'%grid_key]['count']+=1
  207. if grid['%s-%s'%grid_key]['count']>1 and len(tmp['%s-%s'%grid_key])<3:
  208. grid['%s-%s'%grid_key]['dataType'] = ''
  209. grid['%s-%s'%grid_key]['id'] = ""
  210. if len(tmp['%s-%s'%grid_key])<2:
  211. grid['%s-%s'%grid_key]['name'] = '多数据点位'
  212. grid['%s-%s' % grid_key]['type'] ='1'
  213. else:
  214. grid['%s-%s' % grid_key]['name'] = '聚合点位'
  215. grid['%s-%s' % grid_key]['type'] = '3'
  216. # grid['%s-%s'%grid_key]['latitude'] = float(point.latitude) #(grid_key[0] + 0.5) * grid_size
  217. # grid['%s-%s'%grid_key]['longitude'] = float(point.longitude) #(grid_key[1] + 0.5) * grid_size
  218. elif grid['%s-%s'%grid_key]['count']==1:
  219. if point.dataType=='video':
  220. grid['%s-%s' % grid_key]['id'] = point.gbIndexCode
  221. else:
  222. grid['%s-%s' % grid_key]['id'] = point.id
  223. grid['%s-%s'%grid_key]['dataType'] = point.dataType
  224. grid['%s-%s'%grid_key]['infoType'] = point.infoType
  225. grid['%s-%s'%grid_key]['name'] = point.name
  226. grid['%s-%s'%grid_key]['type'] ='2'
  227. grid['%s-%s'%grid_key]['latitude'] = float(point.latitude)
  228. grid['%s-%s'%grid_key]['longitude'] = float(point.longitude)
  229. groups = list(grid.values())
  230. if iszjcj!='':
  231. geom = get_geom(db, iszjcj, pac)
  232. result = []
  233. for group in groups:
  234. for name, wkb_bytes in geom:
  235. poly = wkb.loads(wkb_bytes)
  236. lon, lat = group['longitude'], group['latitude']
  237. pt = Point(lat, lon)
  238. # print("contains?", poly.contains(pt))
  239. if poly.contains(pt):
  240. result.append(group)
  241. groups = result
  242. return groups
  243. def new_group_points(points, distance_threshold, db=get_db(), iszjcj='', pac=''):
  244. grid = defaultdict(lambda: {"count": 0}) # ,"list":[]
  245. if iszjcj != '':
  246. grid_size = calculate_grid_size(distance_threshold)
  247. grid1 = defaultdict(lambda: {"count": 0})
  248. for point in points:
  249. grid_key = get_grid_key(float(point.latitude), float(point.longitude), grid_size)
  250. grid1['%s-%s'%grid_key+'-'+str(point.dataType)]['dataType'] = point.dataType
  251. grid1['%s-%s'%grid_key+'-'+str(point.dataType)]['name'] = point.dict_label
  252. grid1['%s-%s'%grid_key+'-'+str(point.dataType)]['latitude'] = float(point.latitude)
  253. grid1['%s-%s'%grid_key+'-'+str(point.dataType)]['longitude'] = float(point.longitude)
  254. grid1['%s-%s' % grid_key+'-'+str(point.dataType)]['count'] += 1
  255. points = list(grid1.values())
  256. geom = get_geom(db, iszjcj, pac)
  257. for name, wkb_bytes in geom:
  258. poly = wkb.loads(wkb_bytes)
  259. if len(geom)==0:
  260. return []
  261. for point in points:
  262. lon, lat = float(point['longitude']),float(point['latitude'])
  263. pt = Point(lat, lon)
  264. # print(poly.contains(pt))
  265. if poly.contains(pt):
  266. if grid[str(point['dataType'])]['count']==0:
  267. grid[str(point['dataType'])]['dataType'] = point['dataType']
  268. grid[str(point['dataType'])]['name'] = point['name']
  269. grid[str(point['dataType'])]['count']+= point['count']
  270. else:
  271. for point in points:
  272. if grid[str(point.dataType)]['count']==0:
  273. grid[str(point.dataType)]['dataType'] = point.dataType
  274. grid[str(point.dataType)]['name'] = point.dict_label
  275. grid[str(point.dataType)]['count']+=1
  276. groups = list(grid.values())
  277. return groups
  278. def get_videos(db:Session,dict_value,latitude_min,latitude_max,longitude_min,longitude_max,iszjcj=''):
  279. que = True
  280. if len(dict_value)>0:
  281. videolist = []
  282. for value in dict_value:
  283. tag_info = get_dict_data_info(db, 'video_type', value)
  284. if tag_info:
  285. if tag_info.dict_label == '全量视频':
  286. break
  287. else:
  288. videolist += [i.video_code for i in tag_get_video_tag_list(db, value)]
  289. else:
  290. que = TPVideoInfo.gbIndexCode.in_(videolist)
  291. if iszjcj!='':
  292. pass
  293. # 查询分组
  294. query = (
  295. select(
  296. TPVideoInfo.gbIndexCode,
  297. TPVideoInfo.latitude,
  298. TPVideoInfo.longitude,
  299. TPVideoInfo.name,
  300. TPVideoInfo.status,
  301. literal('video').label("dataType"),
  302. literal('video').label("infoType")
  303. )
  304. .select_from(TPVideoInfo).where(
  305. and_(
  306. TPVideoInfo.latitude >= latitude_min,
  307. TPVideoInfo.latitude <= latitude_max,
  308. TPVideoInfo.longitude >= longitude_min,
  309. TPVideoInfo.longitude <= longitude_max,
  310. TPVideoInfo.longitude > 0,
  311. TPVideoInfo.latitude > 0, que
  312. )
  313. )
  314. .order_by(TPVideoInfo.status.asc())
  315. )
  316. result = db.execute(query)
  317. videos = result.fetchall()
  318. return videos
  319. def get_points(db:Session,option,latitude_min,latitude_max,longitude_min,longitude_max,iszjcj='',pac=''):
  320. # 使用参数化查询避免 SQL 注入
  321. if isinstance(option, list):
  322. option = tuple(option)
  323. # if iszjcj=='zj':
  324. # zd = ',T2.name as pacname,T2.pac,T2.parent_pac'
  325. # pac = pac[:6]
  326. # zjcjtable = f"""select * from tp_geojson_data_qx where pac like '{pac}%'"""
  327. # que = f' JOIN ({zjcjtable}) T2 on ST_Intersects(T2.geometry, ST_SRID(POINT(A.longitude, A.latitude), 4326))'
  328. # elif iszjcj=='cj':
  329. # pac = pac[:9]
  330. # zjcjtable = f"""select * from tp_geojson_data_zj where pac like '{pac}%'"""
  331. # que = f'LEFT JOIN ({zjcjtable}) T2 on ST_Intersects(T2.geometry, ST_SRID(POINT(A.longitude, A.latitude), 4326))'
  332. # else:
  333. que=''
  334. query = text(f"""
  335. SELECT
  336. A.`name`,A.`id`,A.dataType,A.longitude,A.latitude,A.infoType
  337. FROM (
  338. SELECT
  339. *,
  340. ROW_NUMBER() OVER (PARTITION BY longitude, latitude, `name`
  341. ORDER BY longitude, latitude, `name`) AS rn
  342. FROM
  343. `point_data`
  344. WHERE
  345. longitude > 0
  346. AND latitude BETWEEN :latitude_min AND :latitude_max
  347. AND longitude BETWEEN :longitude_min AND :longitude_max
  348. AND dataType IN :option
  349. ) AS A {que}
  350. WHERE rn = 1
  351. """)
  352. # 执行查询并传递参数
  353. result = db.execute(query, {
  354. 'latitude_min': latitude_min,
  355. 'latitude_max': latitude_max,
  356. 'longitude_min': longitude_min,
  357. 'longitude_max': longitude_max,
  358. 'option': option
  359. })
  360. infos = result.fetchall()
  361. return infos
  362. def notloncations_get_points(db:Session,option):
  363. # 使用参数化查询避免 SQL 注入
  364. if isinstance(option, list):
  365. option = tuple(option)
  366. query = text("""
  367. SELECT
  368. A.`name`,A.`id`,A.dataType,A.longitude,A.latitude,A.infoType,B.dict_label
  369. FROM (
  370. SELECT
  371. *,
  372. ROW_NUMBER() OVER (PARTITION BY longitude, latitude, `name`
  373. ORDER BY longitude, latitude, `name`) AS rn
  374. FROM
  375. `point_data`
  376. WHERE
  377. longitude > 0
  378. AND dataType IN :option
  379. ) AS A JOIN
  380. (SELECT dict_value as dataType,dict_label FROM sys_dict_data where dict_type='point_type') B on A.dataType=B.dataType
  381. WHERE rn = 1
  382. """)
  383. # 执行查询并传递参数
  384. result = db.execute(query, {
  385. 'option': option
  386. })
  387. infos = result.fetchall()
  388. return infos
  389. @router.post("/get_geojson")
  390. async def get_geojson(
  391. body = Depends(remove_xss_json),
  392. db: Session = Depends(get_db)
  393. ):
  394. try:
  395. # 根据缩放级别动态调整分组粒度
  396. latitude_min = float(body['latitude_min'])
  397. latitude_max = float(body['latitude_max'])
  398. longitude_min = float(body['longitude_min'])
  399. longitude_max = float(body['longitude_max'])
  400. if latitude_min<-90 or latitude_max>90 :
  401. return JSONResponse(status_code=500, content={"code": 500, "msg": "Latitude must be within [-90.000000, 90.000000]"})
  402. if longitude_min<-180 or longitude_max>180 :
  403. return JSONResponse(status_code=500, content={"code": 500, "msg": "Longitude must be within [-180.000000, 180.000000]"})
  404. table_name = 'tp_geojson_data_zj'
  405. option = body['option']
  406. if 'cj' == option:
  407. table_name = 'tp_geojson_data_cj_sq'
  408. sql = f"""SELECT id,
  409. name,
  410. pac,
  411. ST_AsGeoJSON(geometry) AS geojson,
  412. properties
  413. FROM {table_name}
  414. WHERE ST_Intersects(
  415. geometry,
  416. ST_GeomFromText(
  417. 'POLYGON(({latitude_min} {longitude_min},
  418. {latitude_max} {longitude_min},
  419. {latitude_max} {longitude_max},
  420. {latitude_min} {longitude_max},
  421. {latitude_min} {longitude_min}))',
  422. 4326
  423. )
  424. );"""
  425. result = db.execute(sql)
  426. features = result.fetchall()
  427. return {"code": 200,
  428. "msg": "操作成功","type":"FeatureCollection",
  429. "features": features}
  430. except Exception as e:
  431. # 处理异常
  432. traceback.print_exc()
  433. raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
  434. @router.post("/get_geojson_new")
  435. async def get_geojson(
  436. body = Depends(remove_xss_json),
  437. db: Session = Depends(get_db)
  438. ):
  439. try:
  440. # 根据缩放级别动态调整分组粒度
  441. pac = body['area_code']
  442. table_name = 'tp_geojson_data_zj'
  443. option = body['option']
  444. if 'cj' == option:
  445. table_name = 'tp_geojson_data_cj_sq'
  446. pac = pac[:9]
  447. else:
  448. pac = pac[:6]
  449. sql = f"""SELECT
  450. ST_AsGeoJSON(geometry) AS geometry,
  451. properties
  452. FROM {table_name}
  453. WHERE parent_pac = '{pac}';"""
  454. def gen():
  455. # 1. 写头
  456. yield '{"type":"FeatureCollection","features":['
  457. first = True
  458. # 2. 逐行流式
  459. for geom, prop_json in db.execute(sql): # 迭代器,不 fetchall
  460. if not first:
  461. yield ","
  462. feature = {
  463. "geometry": json.loads(geom),
  464. "properties": {
  465. "PAC": json.loads(prop_json)['PAC'],
  466. "NAME": json.loads(prop_json)['NAME']
  467. }
  468. }
  469. yield json.dumps(feature, ensure_ascii=False)
  470. first = False
  471. # 3. 写尾
  472. yield "]}"
  473. return StreamingResponse(gen(), media_type="application/json",
  474. headers={"Content-Disposition": "attachment; filename=data.geojson"})
  475. except Exception as e:
  476. # 处理异常
  477. traceback.print_exc()
  478. raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
  479. @router.post("/count/get_count_point_info")
  480. async def get_counts(
  481. body = Depends(remove_xss_json),
  482. # zoom_level: float = Query(..., description="Zoom level for clustering"),
  483. # latitude_min: float = Query(..., description="Minimum latitude"),
  484. # latitude_max: float = Query(..., description="Maximum latitude"),
  485. # longitude_min: float = Query(..., description="Minimum longitude"),
  486. # longitude_max: float = Query(..., description="Maximum longitude"),
  487. # dict_value: str = Query(None),
  488. # option:str = Query(None),
  489. db: Session = Depends(get_db)
  490. ):
  491. try:
  492. # 根据缩放级别动态调整分组粒度
  493. zoom_levels = {'qx': 256.25,
  494. 'zj': 178.125,
  495. 'cj': 69.0625}
  496. distance_threshold = zoom_levels['qx']
  497. option = body['option'].split(',')
  498. print("1",time.time())
  499. iszjcj = ''
  500. pac = ''
  501. infos = notloncations_get_points(db,option)
  502. # 动态分组逻辑
  503. if 'iszjcj' in body:
  504. iszjcj = body['iszjcj']
  505. pac = body['pac']
  506. if iszjcj!='':
  507. distance_threshold = zoom_levels[iszjcj]
  508. groups = new_group_points( infos, distance_threshold,db,iszjcj,pac)
  509. else:
  510. groups = new_group_points(infos, distance_threshold)
  511. print("4",time.time())
  512. return {"code": 200,
  513. "msg": "操作成功",
  514. "data": groups}
  515. except Exception as e:
  516. # 处理异常
  517. traceback.print_exc()
  518. raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
  519. # @router.post("/get_map_img")
  520. # async def get_map_img(
  521. # body: Dict[str, Any] = Depends(remove_xss_json),
  522. # db: Session = Depends(get_db),
  523. # ):
  524. # """
  525. # 输入:
  526. # {
  527. # "latitude_min": 27.0,
  528. # "latitude_max": 30.0,
  529. # "longitude_min": 118.0,
  530. # "longitude_max": 121.0,
  531. # "option": "zj" // "zj" 或 "cj"
  532. # }
  533. # 返回:PNG 图片
  534. # """
  535. # try:
  536. # # 1. 参数提取与合法性校验
  537. # lat_min = float(body["latitude_min"])
  538. # lat_max = float(body["latitude_max"])
  539. # lon_min = float(body["longitude_min"])
  540. # lon_max = float(body["longitude_max"])
  541. # option = body.get("option", "zj")
  542. #
  543. # if not (-90 <= lat_min <= 90 and -90 <= lat_max <= 90):
  544. # raise ValueError("Latitude must be within [-90, 90]")
  545. # if not (-180 <= lon_min <= 180 and -180 <= lon_max <= 180):
  546. # raise ValueError("Longitude must be within [-180, 180]")
  547. #
  548. # table_name = "tp_geojson_data_zj" if option != "cj" else "tp_geojson_data_cj_sq"
  549. #
  550. # # 2. 构造 SQL
  551. # sql = text(
  552. # f"""
  553. # SELECT id, name, pac, ST_AsGeoJSON(geometry) AS geojson, properties
  554. # FROM {table_name}
  555. # WHERE ST_Intersects(
  556. # geometry,
  557. # ST_GeomFromText(
  558. # 'POLYGON(({lon_min} {lat_min},
  559. # {lon_max} {lat_min},
  560. # {lon_max} {lat_max},
  561. # {lon_min} {lat_max},
  562. # {lon_min} {lat_min}))',
  563. # 4326
  564. # )
  565. # );
  566. # """
  567. # )
  568. #
  569. # rows = db.execute(sql).fetchall()
  570. # if not rows:
  571. # raise HTTPException(
  572. # status_code=status.HTTP_404_NOT_FOUND,
  573. # detail="No data within the given bbox."
  574. # )
  575. #
  576. # # 3. 组装 GeoDataFrame
  577. # features = [
  578. # {**json.loads(r.geojson), "properties": json.loads(r.properties)}
  579. # for r in rows
  580. # ]
  581. # gdf = gpd.GeoDataFrame.from_features(features, crs="EPSG:4326")
  582. #
  583. # # 4. 绘图
  584. # fig, ax = plt.subplots(figsize=(6, 6), dpi=150)
  585. # gdf.to_crs(epsg=3857).plot(ax=ax, alpha=0.5, edgecolor="black")
  586. # ctx.add_basemap(ax, source=ctx.providers.Stamen.TonerLite, crs=gdf.to_crs(epsg=3857).crs)
  587. # ax.set_axis_off()
  588. # plt.tight_layout(pad=0)
  589. #
  590. # # 5. 保存成字节流
  591. # buf = io.BytesIO()
  592. # fig.savefig(buf, format="png")
  593. # buf.seek(0)
  594. # plt.close(fig)
  595. #
  596. # # 6. 返回图片
  597. # return StreamingResponse(buf, media_type="image/png")
  598. #
  599. # except Exception as e:
  600. # raise HTTPException(
  601. # status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
  602. # detail=str(e)
  603. # )
  604. # TILE_EXTENT = 4096 # 标准 MVT 精度
  605. # # ---------- 纯 Python 坐标转换 ----------
  606. # def lonlat2xy(lon: float, lat: float) -> tuple[float, float]:
  607. # """4326 -> 3857"""
  608. # x = lon * 20037508.34 / 180
  609. # y = math.log(math.tan((90 + lat) * math.pi / 360)) / (math.pi / 180)
  610. # y = y * 20037508.34 / 180
  611. # return x, y
  612. #
  613. # import mercantile
  614. # from mapbox_vector_tile import encode
  615. # from shapely.geometry import shape
  616. # @router.get("/tile/{option}/{z}/{x}/{y}.pbf")
  617. # async def get_tile(
  618. # option: str = Path(..., regex="^(zj|cj)$"),
  619. # z: int = Path(..., ge=0, le=22),
  620. # x: int = Path(..., ge=0),
  621. # y: int = Path(..., ge=0),
  622. # db: Session = Depends(get_db),
  623. # ):
  624. # """
  625. # 根据 Slippy Map 标准 XYZ 返回 MVT 二进制。
  626. # 前端 layer.url = '/tile/zj/{z}/{x}/{y}.pbf'
  627. # """
  628. # table = "tp_geojson_data_zj" if option == "zj" else "tp_geojson_data_cj_sq"
  629. #
  630. # # 1. 计算瓦片 bbox (4326)
  631. # tile_bounds = mercantile.bounds(mercantile.Tile(x, y, z))
  632. # xmin, ymin, xmax, ymax = tile_bounds.west, tile_bounds.south, tile_bounds.east, tile_bounds.north
  633. # print(xmin, ymin, xmax, ymax)
  634. # # 2. 查相交要素
  635. # sql = text(
  636. # f"""
  637. # SELECT id, name, pac, properties,
  638. # ST_AsGeoJSON(geometry) AS geojson
  639. # FROM {table}
  640. # WHERE ST_Intersects(
  641. # geometry,
  642. # ST_GeomFromText(
  643. # 'POLYGON((
  644. # {ymin} {xmin},
  645. # {ymin} {xmax},
  646. # {ymax} {xmax},
  647. # {ymax} {xmin},
  648. # {ymin} {xmin}))',
  649. # 4326
  650. # )
  651. # );
  652. # """
  653. # )
  654. # rows = db.execute(sql).fetchall()
  655. # if not rows:
  656. # raise HTTPException(status_code=204)
  657. #
  658. # # 3. 构造 MVT features
  659. # features: List[Dict[str, Any]] = []
  660. # bounds_3857 = mercantile.xy_bounds(mercantile.Tile(x, y, z))
  661. # bx, by, bw, bh = bounds_3857.left, bounds_3857.bottom, \
  662. # bounds_3857.right - bounds_3857.left, \
  663. # bounds_3857.top - bounds_3857.bottom
  664. #
  665. # for r in rows:
  666. # # 直接用原始 GeoJSON,不做任何坐标变换
  667. # geo = json.loads(r.geojson)
  668. # features.append({
  669. # "geometry": geo, # 必须是 4326 坐标
  670. # "properties": json.loads(r.properties),
  671. # })
  672. #
  673. # # 4. 生成 MVT
  674. # mvt_bytes = encode([
  675. # {
  676. # "name": "layer",
  677. # "features": features,
  678. # "extent": TILE_EXTENT,
  679. # }
  680. # ])
  681. #
  682. # return Response(content=mvt_bytes, media_type="application/x-protobuf")