video_location.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. # -*- coding: utf-8 -*-
  2. """
  3. 最终交付版 - Python后端GeoJSON处理器 (v11.2)
  4. 功能:
  5. - 完全基于您提供的、投影方向正确的 project_lonlat_to_pixel 函数。
  6. - 核心函数 process_geojson_for_frontend 直接输出一个“干净”的、可供前端JS绘制的【线段列表】。
  7. - 包含一个本地OpenCV预览,用于即时验证返回给前端的数据。
  8. """
  9. import numpy as np
  10. from scipy.spatial.transform import Rotation as R
  11. import json
  12. ## -------------------------------------------------------------------------- ##
  13. ## 1. 核心算法函数 (基于您的版本)
  14. ## -------------------------------------------------------------------------- ##
  15. EARTH_RADIUS = 6378137.0
  16. def lonlat_to_enu(lon, lat, center_lon, center_lat):
  17. delta_lon = np.radians(lon - center_lon)
  18. delta_lat = np.radians(lat - center_lat)
  19. x = EARTH_RADIUS * delta_lon * np.cos(np.radians(center_lat))
  20. y = EARTH_RADIUS * delta_lat
  21. return np.array([x, y, 0])
  22. def project_lonlat_to_pixel(target_lon, target_lat, resolution_w, resolution_h,
  23. cam_lon, cam_lat, cam_height, p, t, hfov, north_p_value=0.0):
  24. """
  25. [最终正确版] 反向投影函数:从经纬度计算像素坐标(u, v)。
  26. - 正确处理 cz < 0 的情况,通过创建一个方向正确但位置在无穷远的虚拟点来实现线段裁剪。
  27. """
  28. # 步骤 1 & 2: ... (这部分代码不变)
  29. target_enu = lonlat_to_enu(target_lon, target_lat, cam_lon, cam_lat)
  30. cam_pos_enu = np.array([0, 0, cam_height])
  31. vec_enu = target_enu - cam_pos_enu
  32. norm = np.linalg.norm(vec_enu)
  33. if norm < 1e-6:
  34. return None, None, False
  35. vec_enu /= norm
  36. # 步骤 3: ... (这部分代码不变)
  37. p_corrected = p - north_p_value
  38. r_base = R.from_euler('x', -90, degrees=True)
  39. r_pan = R.from_euler('z', p_corrected, degrees=True)
  40. r_tilt = R.from_euler('x', -t, degrees=True)
  41. total_rotation = r_pan * r_tilt * r_base
  42. inverse_rotation = total_rotation.inv()
  43. vec_cam = inverse_rotation.apply(vec_enu)
  44. cx, cy, cz = vec_cam
  45. center_u = resolution_w / 2
  46. center_v = resolution_h / 2
  47. # ===================== [ 核心修正逻辑 V3.0 ] =====================
  48. # 如果点在相机后方 (cz <= 0)
  49. if cz <= 1e-9:
  50. # 我们不再尝试投影它。
  51. # 而是根据 cx, cy 的方向,在屏幕中心的基础上,向该方向延伸一个巨大的距离,
  52. # 创建一个“无穷远”的虚拟点。这个点的方向是正确的。
  53. # 这个巨大的数值确保了该点一定在屏幕外。
  54. MEGA_DISTANCE = 1_000_000.0
  55. u = center_u + cx * MEGA_DISTANCE
  56. v = center_v + cy * MEGA_DISTANCE
  57. return u, v, False # 这个点本身是不可见的
  58. # =================================================================
  59. # 步骤 4: (正常投影逻辑) 如果点在相机前方,则正常计算
  60. f_x = resolution_w / (2 * np.tan(np.radians(hfov) / 2))
  61. f_y = f_x
  62. u = center_u + f_x * (cx / cz)
  63. v = center_v + f_y * (cy / cz)
  64. is_visible = (0 <= u < resolution_w) and (0 <= v < resolution_h)
  65. return u, v, is_visible
  66. ## -------------------------------------------------------------------------- ##
  67. ## 2. GeoJSON解析 与 线段裁剪算法
  68. ## -------------------------------------------------------------------------- ##
  69. def parse_geojson_coordinates(geojson_data):
  70. coordinates = []
  71. try:
  72. if geojson_data.get('type') == 'FeatureCollection': geometry = geojson_data['features'][0]['geometry']
  73. else: geometry = geojson_data
  74. if geometry.get('type') == 'Polygon':
  75. exterior_ring = geometry['coordinates'][0]
  76. coordinates = [(coord[0], coord[1]) for coord in exterior_ring]
  77. print(f"成功从GeoJSON中提取了 {len(coordinates)} 个边界点。")
  78. return coordinates
  79. except (KeyError, IndexError, TypeError): pass
  80. return []
  81. INSIDE, LEFT, RIGHT, BOTTOM, TOP = 0, 1, 2, 4, 8
  82. def compute_outcode(x, y, xmin, ymin, xmax, ymax):
  83. code = INSIDE
  84. if x < xmin: code |= LEFT
  85. elif x > xmax: code |= RIGHT
  86. if y < ymin: code |= BOTTOM
  87. elif y > ymax: code |= TOP
  88. return code
  89. def cohen_sutherland_clip(x1, y1, x2, y2, xmin, ymin, xmax, ymax):
  90. code1 = compute_outcode(x1, y1, xmin, ymin, xmax, ymax); code2 = compute_outcode(x2, y2, xmin, ymin, xmax, ymax)
  91. accept = False
  92. while True:
  93. if not (code1 | code2): accept = True; break
  94. elif code1 & code2: break
  95. else:
  96. x, y = 0.0, 0.0; code_out = code1 if code1 else code2
  97. if code_out & TOP:
  98. if y2 != y1: x = x1 + (x2 - x1) * (ymax - y1) / (y2 - y1)
  99. else: x = x1
  100. y = ymax
  101. elif code_out & BOTTOM:
  102. if y2 != y1: x = x1 + (x2 - x1) * (ymin - y1) / (y2 - y1)
  103. else: x = x1
  104. y = ymin
  105. elif code_out & RIGHT:
  106. if x2 != x1: y = y1 + (y2 - y1) * (xmax - x1) / (x2 - x1)
  107. else: y = y1
  108. x = xmax
  109. elif code_out & LEFT:
  110. if x2 != x1: y = y1 + (y2 - y1) * (xmin - x1) / (x2 - x1)
  111. else: y = y1
  112. x = xmin
  113. if code_out == code1: x1, y1 = x, y; code1 = compute_outcode(x1, y1, xmin, ymin, xmax, ymax)
  114. else: x2, y2 = x, y; code2 = compute_outcode(x2, y2, xmin, ymin, xmax, ymax)
  115. if accept: return [[int(x1), int(y1)], [int(x2), int(y2)]]
  116. return None
  117. ## -------------------------------------------------------------------------- ##
  118. ## 3. 核心处理函数
  119. ## -------------------------------------------------------------------------- ##
  120. def process_geojson_for_frontend(geojson_data, camera_params, calibrated_params):
  121. geo_coords = parse_geojson_coordinates(geojson_data)
  122. if not geo_coords: return []
  123. if len(geo_coords) > 1 and geo_coords[0] != geo_coords[-1]:
  124. geo_coords.append(geo_coords[0]) # 确保多边形闭合
  125. full_params = {**camera_params, **calibrated_params}
  126. raw_pixel_points = []
  127. # 1. Batch convert all geographic points to pixel points
  128. for lon, lat in geo_coords:
  129. u, v, is_visible = project_lonlat_to_pixel(
  130. lon, lat,
  131. resolution_w=camera_params['resolution_w'], resolution_h=camera_params['resolution_h'],
  132. cam_lon=camera_params['cam_lon'], cam_lat=camera_params['cam_lat'],
  133. cam_height=calibrated_params['cam_height'], p=camera_params['p'],
  134. t=camera_params['t'], hfov=calibrated_params['hfov'],
  135. north_p_value=camera_params['north_p_value']
  136. )
  137. raw_pixel_points.append([int(u), int(v)])
  138. visible_segments = []
  139. screen_w = camera_params['resolution_w']
  140. screen_h = camera_params['resolution_h']
  141. for i in range(len(raw_pixel_points) - 1):
  142. p1 = raw_pixel_points[i]
  143. p2 = raw_pixel_points[i+1]
  144. if p1[0] == float('inf') or p2[0] == float('inf'): continue
  145. u1, v1 = p1; u2, v2 = p2
  146. clipped_segment = cohen_sutherland_clip(u1, v1, u2, v2, 0, 0, screen_w - 1, screen_h - 1)
  147. if clipped_segment:
  148. visible_segments.append(clipped_segment)
  149. return visible_segments
  150. ## -------------------------------------------------------------------------- ##
  151. ## 4. 主程序
  152. ## -------------------------------------------------------------------------- ##
  153. if __name__ == '__main__':
  154. geojson_string = """
  155. {
  156. "type": "FeatureCollection",
  157. "features": [
  158. {
  159. "type": "Feature",
  160. "properties": {
  161. "name": "",
  162. "id": "m-7d43a6d0-a7bd-4fc1-84b2-deab51757f8e",
  163. "type": "polygon",
  164. "style": {
  165. "color": "#29cf34",
  166. "opacity": 0.5,
  167. "outline": true,
  168. "outlineWidth": 3,
  169. "outlineColor": "#ffffff",
  170. "clampToGround": true,
  171. "materialType": "Color",
  172. "label": {
  173. "text": "",
  174. "font_size": 18,
  175. "color": "#ffffff",
  176. "outline": true,
  177. "outlineColor": "#000000",
  178. "pixelOffsetY": -10,
  179. "distanceDisplayCondition": true,
  180. "distanceDisplayCondition_far": 500000,
  181. "distanceDisplayCondition_near": 0
  182. }
  183. },
  184. "options": {}
  185. },
  186. "geometry": {
  187. "type": "Polygon",
  188. "coordinates": [
  189. [
  190. [
  191. 112.894539,
  192. 28.221327,
  193. 0
  194. ],
  195. [
  196. 112.894712,
  197. 28.221333,
  198. 0
  199. ],
  200. [
  201. 112.894709,
  202. 28.221267,
  203. 0
  204. ],
  205. [
  206. 112.894533,
  207. 28.221267,
  208. 0
  209. ]
  210. ]
  211. ]
  212. }
  213. }
  214. ]
  215. }
  216. """
  217. geojson_data = json.loads(geojson_string)
  218. calibrated_params = {"cam_height": 28.0000, "hfov": 61.0000}
  219. camera_params = {
  220. "resolution_w": 1280, "resolution_h": 720,
  221. "cam_lon": 112.893799, "cam_lat": 28.221701,
  222. "north_p_value": 39.0, "p": 271.9, "t": 26.2
  223. }
  224. final_screen_segments = process_geojson_for_frontend(geojson_data, camera_params, calibrated_params)
  225. print(json.dumps(final_screen_segments, indent=2))