@ifc-lite/viewer 1.17.2 → 1.17.4

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.
Files changed (520) hide show
  1. package/.turbo/turbo-build.log +34 -24
  2. package/.turbo/turbo-typecheck.log +1 -42
  3. package/CHANGELOG.md +24 -0
  4. package/DESKTOP_CONTRACT_VERSION +1 -0
  5. package/dist/assets/arrow-CZ5kQ26f.js +20 -0
  6. package/dist/assets/basketViewActivator-BmnNtVfZ.js +1 -0
  7. package/dist/assets/bcf-DOG9_WPX.js +281 -0
  8. package/dist/assets/{browser-BDShTXzi.js → browser-C5TFR7sH.js} +1 -1
  9. package/dist/assets/cesium-ADbP7waU.css +1 -0
  10. package/dist/assets/cesium-DUOzBlqv.js +17817 -0
  11. package/dist/assets/drawing-2d-gWfpdfYe.js +257 -0
  12. package/dist/assets/epsg-index.generated-BjJrt_0S.js +1 -0
  13. package/dist/assets/exporters-ChAtBmlj.js +80367 -0
  14. package/dist/assets/geometry.worker-BQ0rzNo-.js +1 -0
  15. package/dist/assets/ids-B4jTqB1O.js +1 -0
  16. package/dist/assets/ifc-lite_bg-BX4E7TX8.wasm +0 -0
  17. package/dist/assets/index-Co8E2-FE.js +106013 -0
  18. package/dist/assets/index-DckuDqlv.css +1 -0
  19. package/dist/assets/lens-CSASnhAL.js +1 -0
  20. package/dist/assets/maplibre-gl-CGLcoNXc.js +811 -0
  21. package/dist/assets/native-bridge-BRvbckFQ.js +429 -0
  22. package/dist/assets/{arrow2-bb-jcVEo.js → parquet-CEXmQNRO.js} +2 -2
  23. package/dist/assets/sandbox-DZiNLNMk.js +5933 -0
  24. package/dist/assets/server-client-BV8zHZ7Y.js +626 -0
  25. package/dist/assets/tauri-core-stub-D8Fa-u43.js +1 -0
  26. package/dist/assets/tauri-dialog-stub-r7Wksg7o.js +1 -0
  27. package/dist/assets/tauri-fs-stub-BdeRC7aK.js +1 -0
  28. package/dist/assets/wasm-bridge-g01g7T9b.js +1 -0
  29. package/dist/assets/zip-DBEtpeu6.js +12 -0
  30. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_0.json +1 -0
  31. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_1.json +1 -0
  32. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_10.json +1 -0
  33. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_11.json +1 -0
  34. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_12.json +1 -0
  35. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_13.json +1 -0
  36. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_14.json +1 -0
  37. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_15.json +1 -0
  38. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_16.json +1 -0
  39. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_17.json +1 -0
  40. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_18.json +1 -0
  41. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_19.json +1 -0
  42. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_2.json +1 -0
  43. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_20.json +1 -0
  44. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_21.json +1 -0
  45. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_22.json +1 -0
  46. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_23.json +1 -0
  47. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_24.json +1 -0
  48. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_25.json +1 -0
  49. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_26.json +1 -0
  50. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_27.json +1 -0
  51. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_3.json +1 -0
  52. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_4.json +1 -0
  53. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_5.json +1 -0
  54. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_6.json +1 -0
  55. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_7.json +1 -0
  56. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_8.json +1 -0
  57. package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_9.json +1 -0
  58. package/dist/cesium/Assets/Images/bing_maps_credit.png +0 -0
  59. package/dist/cesium/Assets/Images/cesium_credit.png +0 -0
  60. package/dist/cesium/Assets/Images/google_earth_credit.png +0 -0
  61. package/dist/cesium/Assets/Images/ion-credit.png +0 -0
  62. package/dist/cesium/Assets/Textures/LensFlare/DirtMask.jpg +0 -0
  63. package/dist/cesium/Assets/Textures/LensFlare/StarBurst.jpg +0 -0
  64. package/dist/cesium/Assets/Textures/NaturalEarthII/0/0/0.jpg +0 -0
  65. package/dist/cesium/Assets/Textures/NaturalEarthII/0/1/0.jpg +0 -0
  66. package/dist/cesium/Assets/Textures/NaturalEarthII/1/0/0.jpg +0 -0
  67. package/dist/cesium/Assets/Textures/NaturalEarthII/1/0/1.jpg +0 -0
  68. package/dist/cesium/Assets/Textures/NaturalEarthII/1/1/0.jpg +0 -0
  69. package/dist/cesium/Assets/Textures/NaturalEarthII/1/1/1.jpg +0 -0
  70. package/dist/cesium/Assets/Textures/NaturalEarthII/1/2/0.jpg +0 -0
  71. package/dist/cesium/Assets/Textures/NaturalEarthII/1/2/1.jpg +0 -0
  72. package/dist/cesium/Assets/Textures/NaturalEarthII/1/3/0.jpg +0 -0
  73. package/dist/cesium/Assets/Textures/NaturalEarthII/1/3/1.jpg +0 -0
  74. package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/0.jpg +0 -0
  75. package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/1.jpg +0 -0
  76. package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/2.jpg +0 -0
  77. package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/3.jpg +0 -0
  78. package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/0.jpg +0 -0
  79. package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/1.jpg +0 -0
  80. package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/2.jpg +0 -0
  81. package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/3.jpg +0 -0
  82. package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/0.jpg +0 -0
  83. package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/1.jpg +0 -0
  84. package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/2.jpg +0 -0
  85. package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/3.jpg +0 -0
  86. package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/0.jpg +0 -0
  87. package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/1.jpg +0 -0
  88. package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/2.jpg +0 -0
  89. package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/3.jpg +0 -0
  90. package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/0.jpg +0 -0
  91. package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/1.jpg +0 -0
  92. package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/2.jpg +0 -0
  93. package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/3.jpg +0 -0
  94. package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/0.jpg +0 -0
  95. package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/1.jpg +0 -0
  96. package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/2.jpg +0 -0
  97. package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/3.jpg +0 -0
  98. package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/0.jpg +0 -0
  99. package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/1.jpg +0 -0
  100. package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/2.jpg +0 -0
  101. package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/3.jpg +0 -0
  102. package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/0.jpg +0 -0
  103. package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/1.jpg +0 -0
  104. package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/2.jpg +0 -0
  105. package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/3.jpg +0 -0
  106. package/dist/cesium/Assets/Textures/NaturalEarthII/tilemapresource.xml +14 -0
  107. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_mx.jpg +0 -0
  108. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_my.jpg +0 -0
  109. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_mz.jpg +0 -0
  110. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_px.jpg +0 -0
  111. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_py.jpg +0 -0
  112. package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_pz.jpg +0 -0
  113. package/dist/cesium/Assets/Textures/maki/airfield.png +0 -0
  114. package/dist/cesium/Assets/Textures/maki/airport.png +0 -0
  115. package/dist/cesium/Assets/Textures/maki/alcohol-shop.png +0 -0
  116. package/dist/cesium/Assets/Textures/maki/america-football.png +0 -0
  117. package/dist/cesium/Assets/Textures/maki/art-gallery.png +0 -0
  118. package/dist/cesium/Assets/Textures/maki/bakery.png +0 -0
  119. package/dist/cesium/Assets/Textures/maki/bank.png +0 -0
  120. package/dist/cesium/Assets/Textures/maki/bar.png +0 -0
  121. package/dist/cesium/Assets/Textures/maki/baseball.png +0 -0
  122. package/dist/cesium/Assets/Textures/maki/basketball.png +0 -0
  123. package/dist/cesium/Assets/Textures/maki/beer.png +0 -0
  124. package/dist/cesium/Assets/Textures/maki/bicycle.png +0 -0
  125. package/dist/cesium/Assets/Textures/maki/building.png +0 -0
  126. package/dist/cesium/Assets/Textures/maki/bus.png +0 -0
  127. package/dist/cesium/Assets/Textures/maki/cafe.png +0 -0
  128. package/dist/cesium/Assets/Textures/maki/camera.png +0 -0
  129. package/dist/cesium/Assets/Textures/maki/campsite.png +0 -0
  130. package/dist/cesium/Assets/Textures/maki/car.png +0 -0
  131. package/dist/cesium/Assets/Textures/maki/cemetery.png +0 -0
  132. package/dist/cesium/Assets/Textures/maki/cesium.png +0 -0
  133. package/dist/cesium/Assets/Textures/maki/chemist.png +0 -0
  134. package/dist/cesium/Assets/Textures/maki/cinema.png +0 -0
  135. package/dist/cesium/Assets/Textures/maki/circle-stroked.png +0 -0
  136. package/dist/cesium/Assets/Textures/maki/circle.png +0 -0
  137. package/dist/cesium/Assets/Textures/maki/city.png +0 -0
  138. package/dist/cesium/Assets/Textures/maki/clothing-store.png +0 -0
  139. package/dist/cesium/Assets/Textures/maki/college.png +0 -0
  140. package/dist/cesium/Assets/Textures/maki/commercial.png +0 -0
  141. package/dist/cesium/Assets/Textures/maki/cricket.png +0 -0
  142. package/dist/cesium/Assets/Textures/maki/cross.png +0 -0
  143. package/dist/cesium/Assets/Textures/maki/dam.png +0 -0
  144. package/dist/cesium/Assets/Textures/maki/danger.png +0 -0
  145. package/dist/cesium/Assets/Textures/maki/disability.png +0 -0
  146. package/dist/cesium/Assets/Textures/maki/dog-park.png +0 -0
  147. package/dist/cesium/Assets/Textures/maki/embassy.png +0 -0
  148. package/dist/cesium/Assets/Textures/maki/emergency-telephone.png +0 -0
  149. package/dist/cesium/Assets/Textures/maki/entrance.png +0 -0
  150. package/dist/cesium/Assets/Textures/maki/farm.png +0 -0
  151. package/dist/cesium/Assets/Textures/maki/fast-food.png +0 -0
  152. package/dist/cesium/Assets/Textures/maki/ferry.png +0 -0
  153. package/dist/cesium/Assets/Textures/maki/fire-station.png +0 -0
  154. package/dist/cesium/Assets/Textures/maki/fuel.png +0 -0
  155. package/dist/cesium/Assets/Textures/maki/garden.png +0 -0
  156. package/dist/cesium/Assets/Textures/maki/gift.png +0 -0
  157. package/dist/cesium/Assets/Textures/maki/golf.png +0 -0
  158. package/dist/cesium/Assets/Textures/maki/grocery.png +0 -0
  159. package/dist/cesium/Assets/Textures/maki/hairdresser.png +0 -0
  160. package/dist/cesium/Assets/Textures/maki/harbor.png +0 -0
  161. package/dist/cesium/Assets/Textures/maki/heart.png +0 -0
  162. package/dist/cesium/Assets/Textures/maki/heliport.png +0 -0
  163. package/dist/cesium/Assets/Textures/maki/hospital.png +0 -0
  164. package/dist/cesium/Assets/Textures/maki/ice-cream.png +0 -0
  165. package/dist/cesium/Assets/Textures/maki/industrial.png +0 -0
  166. package/dist/cesium/Assets/Textures/maki/land-use.png +0 -0
  167. package/dist/cesium/Assets/Textures/maki/laundry.png +0 -0
  168. package/dist/cesium/Assets/Textures/maki/library.png +0 -0
  169. package/dist/cesium/Assets/Textures/maki/lighthouse.png +0 -0
  170. package/dist/cesium/Assets/Textures/maki/lodging.png +0 -0
  171. package/dist/cesium/Assets/Textures/maki/logging.png +0 -0
  172. package/dist/cesium/Assets/Textures/maki/london-underground.png +0 -0
  173. package/dist/cesium/Assets/Textures/maki/marker-stroked.png +0 -0
  174. package/dist/cesium/Assets/Textures/maki/marker.png +0 -0
  175. package/dist/cesium/Assets/Textures/maki/minefield.png +0 -0
  176. package/dist/cesium/Assets/Textures/maki/mobilephone.png +0 -0
  177. package/dist/cesium/Assets/Textures/maki/monument.png +0 -0
  178. package/dist/cesium/Assets/Textures/maki/museum.png +0 -0
  179. package/dist/cesium/Assets/Textures/maki/music.png +0 -0
  180. package/dist/cesium/Assets/Textures/maki/oil-well.png +0 -0
  181. package/dist/cesium/Assets/Textures/maki/park.png +0 -0
  182. package/dist/cesium/Assets/Textures/maki/park2.png +0 -0
  183. package/dist/cesium/Assets/Textures/maki/parking-garage.png +0 -0
  184. package/dist/cesium/Assets/Textures/maki/parking.png +0 -0
  185. package/dist/cesium/Assets/Textures/maki/pharmacy.png +0 -0
  186. package/dist/cesium/Assets/Textures/maki/pitch.png +0 -0
  187. package/dist/cesium/Assets/Textures/maki/place-of-worship.png +0 -0
  188. package/dist/cesium/Assets/Textures/maki/playground.png +0 -0
  189. package/dist/cesium/Assets/Textures/maki/police.png +0 -0
  190. package/dist/cesium/Assets/Textures/maki/polling-place.png +0 -0
  191. package/dist/cesium/Assets/Textures/maki/post.png +0 -0
  192. package/dist/cesium/Assets/Textures/maki/prison.png +0 -0
  193. package/dist/cesium/Assets/Textures/maki/rail-above.png +0 -0
  194. package/dist/cesium/Assets/Textures/maki/rail-light.png +0 -0
  195. package/dist/cesium/Assets/Textures/maki/rail-metro.png +0 -0
  196. package/dist/cesium/Assets/Textures/maki/rail-underground.png +0 -0
  197. package/dist/cesium/Assets/Textures/maki/rail.png +0 -0
  198. package/dist/cesium/Assets/Textures/maki/religious-christian.png +0 -0
  199. package/dist/cesium/Assets/Textures/maki/religious-jewish.png +0 -0
  200. package/dist/cesium/Assets/Textures/maki/religious-muslim.png +0 -0
  201. package/dist/cesium/Assets/Textures/maki/restaurant.png +0 -0
  202. package/dist/cesium/Assets/Textures/maki/roadblock.png +0 -0
  203. package/dist/cesium/Assets/Textures/maki/rocket.png +0 -0
  204. package/dist/cesium/Assets/Textures/maki/school.png +0 -0
  205. package/dist/cesium/Assets/Textures/maki/scooter.png +0 -0
  206. package/dist/cesium/Assets/Textures/maki/shop.png +0 -0
  207. package/dist/cesium/Assets/Textures/maki/skiing.png +0 -0
  208. package/dist/cesium/Assets/Textures/maki/slaughterhouse.png +0 -0
  209. package/dist/cesium/Assets/Textures/maki/soccer.png +0 -0
  210. package/dist/cesium/Assets/Textures/maki/square-stroked.png +0 -0
  211. package/dist/cesium/Assets/Textures/maki/square.png +0 -0
  212. package/dist/cesium/Assets/Textures/maki/star-stroked.png +0 -0
  213. package/dist/cesium/Assets/Textures/maki/star.png +0 -0
  214. package/dist/cesium/Assets/Textures/maki/suitcase.png +0 -0
  215. package/dist/cesium/Assets/Textures/maki/swimming.png +0 -0
  216. package/dist/cesium/Assets/Textures/maki/telephone.png +0 -0
  217. package/dist/cesium/Assets/Textures/maki/tennis.png +0 -0
  218. package/dist/cesium/Assets/Textures/maki/theatre.png +0 -0
  219. package/dist/cesium/Assets/Textures/maki/toilets.png +0 -0
  220. package/dist/cesium/Assets/Textures/maki/town-hall.png +0 -0
  221. package/dist/cesium/Assets/Textures/maki/town.png +0 -0
  222. package/dist/cesium/Assets/Textures/maki/triangle-stroked.png +0 -0
  223. package/dist/cesium/Assets/Textures/maki/triangle.png +0 -0
  224. package/dist/cesium/Assets/Textures/maki/village.png +0 -0
  225. package/dist/cesium/Assets/Textures/maki/warehouse.png +0 -0
  226. package/dist/cesium/Assets/Textures/maki/waste-basket.png +0 -0
  227. package/dist/cesium/Assets/Textures/maki/water.png +0 -0
  228. package/dist/cesium/Assets/Textures/maki/wetland.png +0 -0
  229. package/dist/cesium/Assets/Textures/maki/zoo.png +0 -0
  230. package/dist/cesium/Assets/Textures/moonSmall.jpg +0 -0
  231. package/dist/cesium/Assets/Textures/pin.svg +1 -0
  232. package/dist/cesium/Assets/Textures/waterNormals.jpg +0 -0
  233. package/dist/cesium/Assets/Textures/waterNormalsSmall.jpg +0 -0
  234. package/dist/cesium/Assets/approximateTerrainHeights.json +1 -0
  235. package/dist/cesium/ThirdParty/Workers/package.json +1 -0
  236. package/dist/cesium/ThirdParty/Workers/zip-web-worker.js +1 -0
  237. package/dist/cesium/ThirdParty/basis_transcoder.wasm +0 -0
  238. package/dist/cesium/ThirdParty/draco_decoder.wasm +0 -0
  239. package/dist/cesium/ThirdParty/google-earth-dbroot-parser.js +1 -0
  240. package/dist/cesium/ThirdParty/wasm_splats_bg.wasm +0 -0
  241. package/dist/cesium/ThirdParty/zip-module.wasm +0 -0
  242. package/dist/cesium/Widgets/Animation/Animation.css +127 -0
  243. package/dist/cesium/Widgets/Animation/lighter.css +70 -0
  244. package/dist/cesium/Widgets/BaseLayerPicker/BaseLayerPicker.css +108 -0
  245. package/dist/cesium/Widgets/BaseLayerPicker/lighter.css +22 -0
  246. package/dist/cesium/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.css +102 -0
  247. package/dist/cesium/Widgets/CesiumInspector/CesiumInspector.css +113 -0
  248. package/dist/cesium/Widgets/CesiumWidget/CesiumWidget.css +119 -0
  249. package/dist/cesium/Widgets/CesiumWidget/lighter.css +14 -0
  250. package/dist/cesium/Widgets/FullscreenButton/FullscreenButton.css +8 -0
  251. package/dist/cesium/Widgets/Geocoder/Geocoder.css +70 -0
  252. package/dist/cesium/Widgets/Geocoder/lighter.css +17 -0
  253. package/dist/cesium/Widgets/I3SBuildingSceneLayerExplorer/I3SBuildingSceneLayerExplorer.css +27 -0
  254. package/dist/cesium/Widgets/Images/ImageryProviders/ArcGisMapServiceWorldHillshade.png +0 -0
  255. package/dist/cesium/Widgets/Images/ImageryProviders/ArcGisMapServiceWorldImagery.png +0 -0
  256. package/dist/cesium/Widgets/Images/ImageryProviders/ArcGisMapServiceWorldOcean.png +0 -0
  257. package/dist/cesium/Widgets/Images/ImageryProviders/azureAerial.png +0 -0
  258. package/dist/cesium/Widgets/Images/ImageryProviders/azureRoads.png +0 -0
  259. package/dist/cesium/Widgets/Images/ImageryProviders/bingAerial.png +0 -0
  260. package/dist/cesium/Widgets/Images/ImageryProviders/bingAerialLabels.png +0 -0
  261. package/dist/cesium/Widgets/Images/ImageryProviders/bingRoads.png +0 -0
  262. package/dist/cesium/Widgets/Images/ImageryProviders/blueMarble.png +0 -0
  263. package/dist/cesium/Widgets/Images/ImageryProviders/earthAtNight.png +0 -0
  264. package/dist/cesium/Widgets/Images/ImageryProviders/googleContour.png +0 -0
  265. package/dist/cesium/Widgets/Images/ImageryProviders/googleRoadmap.png +0 -0
  266. package/dist/cesium/Widgets/Images/ImageryProviders/googleSatellite.png +0 -0
  267. package/dist/cesium/Widgets/Images/ImageryProviders/googleSatelliteLabels.png +0 -0
  268. package/dist/cesium/Widgets/Images/ImageryProviders/mapQuestOpenStreetMap.png +0 -0
  269. package/dist/cesium/Widgets/Images/ImageryProviders/mapboxSatellite.png +0 -0
  270. package/dist/cesium/Widgets/Images/ImageryProviders/mapboxStreets.png +0 -0
  271. package/dist/cesium/Widgets/Images/ImageryProviders/mapboxTerrain.png +0 -0
  272. package/dist/cesium/Widgets/Images/ImageryProviders/naturalEarthII.png +0 -0
  273. package/dist/cesium/Widgets/Images/ImageryProviders/openStreetMap.png +0 -0
  274. package/dist/cesium/Widgets/Images/ImageryProviders/sentinel-2.png +0 -0
  275. package/dist/cesium/Widgets/Images/ImageryProviders/stadiaAlidadeSmooth.png +0 -0
  276. package/dist/cesium/Widgets/Images/ImageryProviders/stadiaAlidadeSmoothDark.png +0 -0
  277. package/dist/cesium/Widgets/Images/ImageryProviders/stamenToner.png +0 -0
  278. package/dist/cesium/Widgets/Images/ImageryProviders/stamenWatercolor.png +0 -0
  279. package/dist/cesium/Widgets/Images/NavigationHelp/Mouse.svg +84 -0
  280. package/dist/cesium/Widgets/Images/NavigationHelp/MouseLeft.svg +76 -0
  281. package/dist/cesium/Widgets/Images/NavigationHelp/MouseMiddle.svg +76 -0
  282. package/dist/cesium/Widgets/Images/NavigationHelp/MouseRight.svg +76 -0
  283. package/dist/cesium/Widgets/Images/NavigationHelp/Touch.svg +120 -0
  284. package/dist/cesium/Widgets/Images/NavigationHelp/TouchDrag.svg +129 -0
  285. package/dist/cesium/Widgets/Images/NavigationHelp/TouchRotate.svg +76 -0
  286. package/dist/cesium/Widgets/Images/NavigationHelp/TouchTilt.svg +135 -0
  287. package/dist/cesium/Widgets/Images/NavigationHelp/TouchZoom.svg +74 -0
  288. package/dist/cesium/Widgets/Images/TerrainProviders/CesiumWorldTerrain.png +0 -0
  289. package/dist/cesium/Widgets/Images/TerrainProviders/Ellipsoid.png +0 -0
  290. package/dist/cesium/Widgets/Images/TimelineIcons.png +0 -0
  291. package/dist/cesium/Widgets/Images/info-loading.gif +0 -0
  292. package/dist/cesium/Widgets/InfoBox/InfoBox.css +92 -0
  293. package/dist/cesium/Widgets/InfoBox/InfoBoxDescription.css +178 -0
  294. package/dist/cesium/Widgets/NavigationHelpButton/NavigationHelpButton.css +93 -0
  295. package/dist/cesium/Widgets/NavigationHelpButton/lighter.css +38 -0
  296. package/dist/cesium/Widgets/PerformanceWatchdog/PerformanceWatchdog.css +15 -0
  297. package/dist/cesium/Widgets/ProjectionPicker/ProjectionPicker.css +38 -0
  298. package/dist/cesium/Widgets/SceneModePicker/SceneModePicker.css +56 -0
  299. package/dist/cesium/Widgets/SelectionIndicator/SelectionIndicator.css +20 -0
  300. package/dist/cesium/Widgets/Timeline/Timeline.css +103 -0
  301. package/dist/cesium/Widgets/Timeline/lighter.css +23 -0
  302. package/dist/cesium/Widgets/VRButton/VRButton.css +8 -0
  303. package/dist/cesium/Widgets/Viewer/Viewer.css +107 -0
  304. package/dist/cesium/Widgets/VoxelInspector/VoxelInspector.css +16 -0
  305. package/dist/cesium/Widgets/lighter.css +237 -0
  306. package/dist/cesium/Widgets/lighterShared.css +46 -0
  307. package/dist/cesium/Widgets/shared.css +103 -0
  308. package/dist/cesium/Widgets/widgets.css +1342 -0
  309. package/dist/cesium/Workers/chunk-23ZQ2IVV.js +29 -0
  310. package/dist/cesium/Workers/chunk-2EQO3Q56.js +26 -0
  311. package/dist/cesium/Workers/chunk-2MJIIVP4.js +26 -0
  312. package/dist/cesium/Workers/chunk-2TE5NTVD.js +26 -0
  313. package/dist/cesium/Workers/chunk-2ZBHLJST.js +26 -0
  314. package/dist/cesium/Workers/chunk-5TJMAQVL.js +26 -0
  315. package/dist/cesium/Workers/chunk-6BD4U3VO.js +26 -0
  316. package/dist/cesium/Workers/chunk-7TVGLKQF.js +26 -0
  317. package/dist/cesium/Workers/chunk-BTSYJ5XU.js +26 -0
  318. package/dist/cesium/Workers/chunk-BXMEEOCS.js +63 -0
  319. package/dist/cesium/Workers/chunk-BYLCY7GP.js +29 -0
  320. package/dist/cesium/Workers/chunk-CTHM3W6I.js +26 -0
  321. package/dist/cesium/Workers/chunk-CUUSNIVQ.js +26 -0
  322. package/dist/cesium/Workers/chunk-E3JOOS3S.js +26 -0
  323. package/dist/cesium/Workers/chunk-E7KYDCM5.js +26 -0
  324. package/dist/cesium/Workers/chunk-EDVBB7SS.js +27 -0
  325. package/dist/cesium/Workers/chunk-EFBN7QNX.js +26 -0
  326. package/dist/cesium/Workers/chunk-EQ4YRVWL.js +26 -0
  327. package/dist/cesium/Workers/chunk-F6PRE7D6.js +26 -0
  328. package/dist/cesium/Workers/chunk-FC4ZZ65J.js +26 -0
  329. package/dist/cesium/Workers/chunk-FFBVWF2L.js +26 -0
  330. package/dist/cesium/Workers/chunk-GBAA6GVX.js +26 -0
  331. package/dist/cesium/Workers/chunk-ICALLYLG.js +26 -0
  332. package/dist/cesium/Workers/chunk-ILRYTWTP.js +26 -0
  333. package/dist/cesium/Workers/chunk-IRNLBSEJ.js +26 -0
  334. package/dist/cesium/Workers/chunk-IX4VMHEV.js +26 -0
  335. package/dist/cesium/Workers/chunk-L6QHHACZ.js +26 -0
  336. package/dist/cesium/Workers/chunk-LI2ZSORM.js +26 -0
  337. package/dist/cesium/Workers/chunk-LSLE2RL4.js +26 -0
  338. package/dist/cesium/Workers/chunk-M4HLDBCG.js +26 -0
  339. package/dist/cesium/Workers/chunk-MJHHSGEH.js +26 -0
  340. package/dist/cesium/Workers/chunk-NMVKML6W.js +26 -0
  341. package/dist/cesium/Workers/chunk-OCWJRAXS.js +26 -0
  342. package/dist/cesium/Workers/chunk-OIRKANTH.js +26 -0
  343. package/dist/cesium/Workers/chunk-OIT7J4IC.js +26 -0
  344. package/dist/cesium/Workers/chunk-OLZ3FYUM.js +26 -0
  345. package/dist/cesium/Workers/chunk-Q5BPHJQF.js +26 -0
  346. package/dist/cesium/Workers/chunk-QFM5DCMQ.js +26 -0
  347. package/dist/cesium/Workers/chunk-QKUIYMGC.js +28 -0
  348. package/dist/cesium/Workers/chunk-S44JILQT.js +26 -0
  349. package/dist/cesium/Workers/chunk-SLT4J352.js +26 -0
  350. package/dist/cesium/Workers/chunk-SQMIIXB7.js +26 -0
  351. package/dist/cesium/Workers/chunk-TJ4XLGBQ.js +26 -0
  352. package/dist/cesium/Workers/chunk-TNSUQXWK.js +27 -0
  353. package/dist/cesium/Workers/chunk-UBOGZS7F.js +26 -0
  354. package/dist/cesium/Workers/chunk-V3OSTMM6.js +26 -0
  355. package/dist/cesium/Workers/chunk-V7QEYVP3.js +26 -0
  356. package/dist/cesium/Workers/chunk-VUKYSU4H.js +26 -0
  357. package/dist/cesium/Workers/chunk-W37FE5GR.js +26 -0
  358. package/dist/cesium/Workers/chunk-WBOV35NL.js +26 -0
  359. package/dist/cesium/Workers/chunk-WPMZLB3Y.js +26 -0
  360. package/dist/cesium/Workers/chunk-WWWZVEEH.js +26 -0
  361. package/dist/cesium/Workers/chunk-XFIQ5DEQ.js +28 -0
  362. package/dist/cesium/Workers/chunk-XQHLGIO7.js +26 -0
  363. package/dist/cesium/Workers/chunk-XUSCFAVF.js +26 -0
  364. package/dist/cesium/Workers/chunk-YP7I5QBZ.js +26 -0
  365. package/dist/cesium/Workers/chunk-Z3QF2EHT.js +26 -0
  366. package/dist/cesium/Workers/combineGeometry.js +26 -0
  367. package/dist/cesium/Workers/createBoxGeometry.js +26 -0
  368. package/dist/cesium/Workers/createBoxOutlineGeometry.js +26 -0
  369. package/dist/cesium/Workers/createCircleGeometry.js +26 -0
  370. package/dist/cesium/Workers/createCircleOutlineGeometry.js +26 -0
  371. package/dist/cesium/Workers/createCoplanarPolygonGeometry.js +26 -0
  372. package/dist/cesium/Workers/createCoplanarPolygonOutlineGeometry.js +26 -0
  373. package/dist/cesium/Workers/createCorridorGeometry.js +26 -0
  374. package/dist/cesium/Workers/createCorridorOutlineGeometry.js +26 -0
  375. package/dist/cesium/Workers/createCylinderGeometry.js +26 -0
  376. package/dist/cesium/Workers/createCylinderOutlineGeometry.js +26 -0
  377. package/dist/cesium/Workers/createEllipseGeometry.js +26 -0
  378. package/dist/cesium/Workers/createEllipseOutlineGeometry.js +26 -0
  379. package/dist/cesium/Workers/createEllipsoidGeometry.js +26 -0
  380. package/dist/cesium/Workers/createEllipsoidOutlineGeometry.js +26 -0
  381. package/dist/cesium/Workers/createFrustumGeometry.js +26 -0
  382. package/dist/cesium/Workers/createFrustumOutlineGeometry.js +26 -0
  383. package/dist/cesium/Workers/createGeometry.js +26 -0
  384. package/dist/cesium/Workers/createGroundPolylineGeometry.js +26 -0
  385. package/dist/cesium/Workers/createPlaneGeometry.js +26 -0
  386. package/dist/cesium/Workers/createPlaneOutlineGeometry.js +26 -0
  387. package/dist/cesium/Workers/createPolygonGeometry.js +26 -0
  388. package/dist/cesium/Workers/createPolygonOutlineGeometry.js +26 -0
  389. package/dist/cesium/Workers/createPolylineGeometry.js +26 -0
  390. package/dist/cesium/Workers/createPolylineVolumeGeometry.js +26 -0
  391. package/dist/cesium/Workers/createPolylineVolumeOutlineGeometry.js +26 -0
  392. package/dist/cesium/Workers/createRectangleGeometry.js +26 -0
  393. package/dist/cesium/Workers/createRectangleOutlineGeometry.js +26 -0
  394. package/dist/cesium/Workers/createSimplePolylineGeometry.js +26 -0
  395. package/dist/cesium/Workers/createSphereGeometry.js +26 -0
  396. package/dist/cesium/Workers/createSphereOutlineGeometry.js +26 -0
  397. package/dist/cesium/Workers/createTaskProcessorWorker.js +26 -0
  398. package/dist/cesium/Workers/createVectorTileClampedPolylines.js +26 -0
  399. package/dist/cesium/Workers/createVectorTileGeometries.js +26 -0
  400. package/dist/cesium/Workers/createVectorTilePoints.js +26 -0
  401. package/dist/cesium/Workers/createVectorTilePolygons.js +26 -0
  402. package/dist/cesium/Workers/createVectorTilePolylines.js +26 -0
  403. package/dist/cesium/Workers/createVerticesFromCesium3DTilesTerrain.js +26 -0
  404. package/dist/cesium/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +26 -0
  405. package/dist/cesium/Workers/createVerticesFromHeightmap.js +26 -0
  406. package/dist/cesium/Workers/createVerticesFromQuantizedTerrainMesh.js +26 -0
  407. package/dist/cesium/Workers/createWallGeometry.js +26 -0
  408. package/dist/cesium/Workers/createWallOutlineGeometry.js +26 -0
  409. package/dist/cesium/Workers/decodeDraco.js +26 -0
  410. package/dist/cesium/Workers/decodeGoogleEarthEnterprisePacket.js +26 -0
  411. package/dist/cesium/Workers/decodeI3S.js +26 -0
  412. package/dist/cesium/Workers/gaussianSplatSorter.js +26 -0
  413. package/dist/cesium/Workers/gaussianSplatTextureGenerator.js +26 -0
  414. package/dist/cesium/Workers/incrementallyBuildTerrainPicker.js +26 -0
  415. package/dist/cesium/Workers/transcodeKTX2.js +56 -0
  416. package/dist/cesium/Workers/transferTypedArrayTest.js +26 -0
  417. package/dist/cesium/Workers/upsampleQuantizedTerrainMesh.js +26 -0
  418. package/dist/cesium/Workers/upsampleVerticesFromCesium3DTilesTerrain.js +26 -0
  419. package/dist/index.html +13 -2
  420. package/package.json +27 -19
  421. package/src/App.tsx +1 -17
  422. package/src/components/viewer/BCFPanel.tsx +46 -4
  423. package/src/components/viewer/CesiumOverlay.tsx +672 -0
  424. package/src/components/viewer/CesiumSettingsDialog.tsx +100 -0
  425. package/src/components/viewer/ChatPanel.tsx +54 -16
  426. package/src/components/viewer/CommandPalette.tsx +6 -1
  427. package/src/components/viewer/DesktopEntitlementBanner.tsx +74 -0
  428. package/src/components/viewer/ExportChangesButton.tsx +24 -4
  429. package/src/components/viewer/ExportDialog.tsx +38 -9
  430. package/src/components/viewer/HierarchyPanel.tsx +202 -6
  431. package/src/components/viewer/IDSPanel.tsx +52 -3
  432. package/src/components/viewer/KeyboardShortcutsDialog.tsx +1 -1
  433. package/src/components/viewer/MainToolbar.tsx +353 -27
  434. package/src/components/viewer/PropertiesPanel.tsx +306 -131
  435. package/src/components/viewer/ScriptPanel.tsx +34 -8
  436. package/src/components/viewer/Section2DPanel.tsx +3 -2
  437. package/src/components/viewer/SettingsPage.tsx +430 -0
  438. package/src/components/viewer/StatusBar.tsx +17 -1
  439. package/src/components/viewer/UpgradePage.tsx +6 -4
  440. package/src/components/viewer/ViewerLayout.tsx +47 -6
  441. package/src/components/viewer/Viewport.tsx +49 -8
  442. package/src/components/viewer/ViewportContainer.tsx +285 -32
  443. package/src/components/viewer/ViewportOverlays.tsx +129 -27
  444. package/src/components/viewer/hierarchy/treeDataBuilder.ts +2 -1
  445. package/src/components/viewer/properties/EpsgLookupDialog.tsx +418 -0
  446. package/src/components/viewer/properties/GeoreferencingPanel.tsx +703 -0
  447. package/src/components/viewer/properties/LocationMap.tsx +730 -0
  448. package/src/components/viewer/properties/ModelMetadataPanel.tsx +3 -70
  449. package/src/components/viewer/selectionHandlers.ts +4 -3
  450. package/src/components/viewer/useAnimationLoop.ts +4 -0
  451. package/src/components/viewer/useGeometryStreaming.ts +127 -40
  452. package/src/components/viewer/useMouseControls.ts +4 -1
  453. package/src/hooks/bcfIdLookup.ts +13 -11
  454. package/src/hooks/ids/idsColorSystem.ts +3 -8
  455. package/src/hooks/ingest/viewerModelIngest.ts +275 -0
  456. package/src/hooks/useIDS.ts +32 -17
  457. package/src/hooks/useIfc.ts +7 -1
  458. package/src/hooks/useIfcCache.ts +28 -15
  459. package/src/hooks/useIfcFederation.ts +59 -227
  460. package/src/hooks/useIfcLoader.ts +1656 -130
  461. package/src/hooks/useIfcServer.ts +0 -69
  462. package/src/lib/desktop/ClerkDesktopEntitlementSync.tsx +175 -0
  463. package/src/lib/desktop/desktopEntitlementEvents.ts +39 -0
  464. package/src/lib/desktop-entitlement.ts +45 -0
  465. package/src/lib/desktop-product.ts +124 -0
  466. package/src/lib/geo/cesium-bridge.ts +310 -0
  467. package/src/lib/geo/kmz-exporter.ts +112 -0
  468. package/src/lib/geo/reproject.ts +370 -0
  469. package/src/lib/lens/adapter.ts +3 -1
  470. package/src/lib/recent-files.ts +2 -1
  471. package/src/main.tsx +1 -0
  472. package/src/sdk/adapters/export-adapter.ts +14 -1
  473. package/src/sdk/adapters/viewer-adapter.ts +5 -9
  474. package/src/sdk/adapters/visibility-adapter.ts +6 -9
  475. package/src/services/analysis-extensions.ts +125 -0
  476. package/src/services/app-navigation.ts +13 -0
  477. package/src/services/bsdd.ts +53 -4
  478. package/src/services/cacheService.ts +1 -1
  479. package/src/services/desktop-cache.ts +43 -0
  480. package/src/services/desktop-export.ts +77 -0
  481. package/src/services/desktop-harness.ts +196 -0
  482. package/src/services/desktop-logger.ts +20 -0
  483. package/src/services/desktop-native-metadata.ts +207 -0
  484. package/src/services/desktop-panel-actions.ts +43 -0
  485. package/src/services/desktop-preferences.ts +44 -0
  486. package/src/services/file-dialog.ts +147 -0
  487. package/src/services/tauri-core-stub.ts +7 -0
  488. package/src/services/tauri-dialog-stub.ts +7 -0
  489. package/src/services/tauri-fs-stub.ts +7 -0
  490. package/src/store/basketVisibleSet.ts +3 -4
  491. package/src/store/globalId.ts +79 -0
  492. package/src/store/index.ts +41 -2
  493. package/src/store/slices/cesiumSlice.ts +122 -0
  494. package/src/store/slices/chatSlice.ts +5 -1
  495. package/src/store/slices/dataSlice.ts +139 -28
  496. package/src/store/slices/desktopEntitlementSlice.ts +86 -0
  497. package/src/store/slices/loadingSlice.ts +14 -2
  498. package/src/store/slices/modelSlice.ts +58 -3
  499. package/src/store/slices/mutationSlice.ts +178 -0
  500. package/src/store/slices/pinboardSlice.ts +4 -8
  501. package/src/store/types.ts +96 -2
  502. package/src/store.ts +1 -1
  503. package/src/utils/desktopModelSnapshot.ts +358 -0
  504. package/src/utils/ifcConfig.ts +6 -1
  505. package/src/utils/nativeSpatialDataStore.ts +250 -0
  506. package/src/utils/serverDataModel.ts +4 -0
  507. package/src/utils/spatialHierarchy.ts +10 -11
  508. package/vite.config.ts +41 -0
  509. package/dist/assets/Arrow.dom-BhOg9lpn.js +0 -20
  510. package/dist/assets/arrow2_bg-BlXl-cSQ.js +0 -1
  511. package/dist/assets/basketViewActivator-BRG5DBmM.js +0 -1
  512. package/dist/assets/desktop-cache-oPzaWXYE.js +0 -1
  513. package/dist/assets/geometry.worker-kgiT_Qhh.js +0 -1
  514. package/dist/assets/ifc-lite_bg-FNRmpSvM.wasm +0 -0
  515. package/dist/assets/index-B1Ecw4AU.js +0 -189756
  516. package/dist/assets/index-Ba4eoTe7.css +0 -1
  517. package/dist/assets/index-CrgYBjTn.js +0 -229
  518. package/dist/assets/module-6F3E5H7Y-tx0BadV3.js +0 -6
  519. package/dist/assets/native-bridge-Crsb7TKz.js +0 -111
  520. package/dist/assets/wasm-bridge-mJUhb7uk.js +0 -1
@@ -0,0 +1,730 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+
5
+ /**
6
+ * LocationMap — a compact MapLibre GL JS minimap that shows the model's
7
+ * real-world position derived from IfcMapConversion + IfcProjectedCRS.
8
+ *
9
+ * Features:
10
+ * - Place/drag pin on map to reposition the model
11
+ * - Search for places via Nominatim geocoding
12
+ * - Query terrain elevation at pin location
13
+ * - Apply pin position back to IfcMapConversion (eastings/northings/height)
14
+ * - Links to Google Maps, OpenStreetMap, and Google Earth (KMZ export)
15
+ */
16
+
17
+ import { useEffect, useRef, useState, useMemo, useCallback } from 'react';
18
+ import {
19
+ Map as MapIcon, ExternalLink, Loader2, MapPinOff, Globe2,
20
+ Search, Mountain, MapPin, X, Check,
21
+ } from 'lucide-react';
22
+ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
23
+ import type { MapConversion, ProjectedCRS } from '@ifc-lite/parser';
24
+ import type { CoordinateInfo, GeometryResult } from '@ifc-lite/geometry';
25
+ import { GLTFExporter } from '@ifc-lite/export';
26
+ import { reprojectToLatLon, reprojectFromLatLon, queryTerrainElevation, computeFootprintGeoJSON, type LatLon } from '@/lib/geo/reproject';
27
+ import { buildKmz, ifcAngleToKmlHeading } from '@/lib/geo/kmz-exporter';
28
+
29
+ // Lazy-load maplibre-gl to avoid bloating the initial bundle
30
+ let maplibrePromise: Promise<typeof import('maplibre-gl')> | null = null;
31
+ function loadMaplibre() {
32
+ if (!maplibrePromise) {
33
+ maplibrePromise = import('maplibre-gl');
34
+ }
35
+ return maplibrePromise;
36
+ }
37
+
38
+ /** Position picked on the map, ready to be applied to IfcMapConversion */
39
+ export interface PickedPosition {
40
+ easting: number;
41
+ northing: number;
42
+ terrainHeight: number | null;
43
+ }
44
+
45
+ export interface LocationMapProps {
46
+ mapConversion?: MapConversion;
47
+ projectedCRS?: ProjectedCRS;
48
+ /** Coordinate info from the model's GeometryResult (includes bounds and RTC offset) */
49
+ coordinateInfo?: CoordinateInfo;
50
+ /** Geometry result for KMZ export (optional — KMZ button hidden if not provided) */
51
+ geometryResult?: GeometryResult | null;
52
+ /** Whether the map is in edit mode (allows repositioning) */
53
+ editable?: boolean;
54
+ /** Called when the user applies a new position from the map */
55
+ onApplyPosition?: (position: PickedPosition) => void;
56
+ }
57
+
58
+ type MapState = 'idle' | 'loading' | 'ready' | 'error';
59
+
60
+ // Debounce helper
61
+ function useDebouncedValue<T>(value: T, delayMs: number): T {
62
+ const [debounced, setDebounced] = useState(value);
63
+ useEffect(() => {
64
+ const timer = setTimeout(() => setDebounced(value), delayMs);
65
+ return () => clearTimeout(timer);
66
+ }, [value, delayMs]);
67
+ return debounced;
68
+ }
69
+
70
+ /** Geocode a query string via Nominatim */
71
+ async function geocodeSearch(query: string): Promise<Array<{ lat: number; lon: number; display_name: string }>> {
72
+ if (!query.trim()) return [];
73
+ try {
74
+ const q = encodeURIComponent(query.trim());
75
+ const resp = await fetch(
76
+ `https://nominatim.openstreetmap.org/search?format=json&limit=5&q=${q}`,
77
+ { headers: { 'Accept-Language': 'en' } },
78
+ );
79
+ if (!resp.ok) return [];
80
+ const data = await resp.json();
81
+ return data.map((r: { lat: string; lon: string; display_name: string }) => ({
82
+ lat: parseFloat(r.lat),
83
+ lon: parseFloat(r.lon),
84
+ display_name: r.display_name,
85
+ }));
86
+ } catch {
87
+ return [];
88
+ }
89
+ }
90
+
91
+ /** Add or update the building footprint GeoJSON polygon on a MapLibre map */
92
+ function addFootprintToMap(map: InstanceType<typeof import('maplibre-gl').Map>, ring: [number, number][]) {
93
+ const geojson: GeoJSON.Feature = {
94
+ type: 'Feature',
95
+ properties: {},
96
+ geometry: {
97
+ type: 'Polygon',
98
+ coordinates: [ring],
99
+ },
100
+ };
101
+
102
+ if (map.getSource('building-footprint')) {
103
+ (map.getSource('building-footprint') as import('maplibre-gl').GeoJSONSource).setData(geojson);
104
+ return;
105
+ }
106
+
107
+ map.addSource('building-footprint', { type: 'geojson', data: geojson });
108
+
109
+ map.addLayer({
110
+ id: 'building-footprint-fill',
111
+ type: 'fill',
112
+ source: 'building-footprint',
113
+ paint: {
114
+ 'fill-color': '#14b8a6',
115
+ 'fill-opacity': ['interpolate', ['linear'], ['zoom'], 15, 0, 16, 0.15, 18, 0.25],
116
+ },
117
+ });
118
+
119
+ map.addLayer({
120
+ id: 'building-footprint-outline',
121
+ type: 'line',
122
+ source: 'building-footprint',
123
+ paint: {
124
+ 'line-color': '#0d9488',
125
+ 'line-width': ['interpolate', ['linear'], ['zoom'], 15, 0.5, 18, 2.5],
126
+ 'line-opacity': ['interpolate', ['linear'], ['zoom'], 15, 0, 16, 0.7, 18, 1],
127
+ },
128
+ });
129
+
130
+ }
131
+
132
+ /** Remove footprint layers and source from a MapLibre map */
133
+ function removeFootprintFromMap(map: InstanceType<typeof import('maplibre-gl').Map>) {
134
+ if (map.getLayer('building-footprint-outline')) map.removeLayer('building-footprint-outline');
135
+ if (map.getLayer('building-footprint-fill')) map.removeLayer('building-footprint-fill');
136
+ if (map.getSource('building-footprint')) map.removeSource('building-footprint');
137
+ }
138
+
139
+ export function LocationMap({
140
+ mapConversion, projectedCRS, coordinateInfo, geometryResult,
141
+ editable, onApplyPosition,
142
+ }: LocationMapProps) {
143
+ const containerRef = useRef<HTMLDivElement>(null);
144
+ const mapRef = useRef<InstanceType<typeof import('maplibre-gl').Map> | null>(null);
145
+ const markerRef = useRef<InstanceType<typeof import('maplibre-gl').Marker> | null>(null);
146
+ const pickedMarkerRef = useRef<InstanceType<typeof import('maplibre-gl').Marker> | null>(null);
147
+ const editableRef = useRef(editable);
148
+
149
+ // Keep editableRef in sync; clean up edit-only state when leaving edit mode
150
+ useEffect(() => {
151
+ editableRef.current = editable;
152
+ if (!editable) {
153
+ setSearchOpen(false);
154
+ setSearchQuery('');
155
+ setSearchResults([]);
156
+ setSearchLoading(false);
157
+ pickedMarkerRef.current?.remove();
158
+ pickedMarkerRef.current = null;
159
+ setPickedLatLon(null);
160
+ setProjectedCoords(null);
161
+ setPickedElevation(null);
162
+ setElevationLoading(false);
163
+ }
164
+ }, [editable]);
165
+
166
+ const [mapState, setMapState] = useState<MapState>('idle');
167
+ const [latLon, setLatLon] = useState<LatLon | null>(null);
168
+ const [error, setError] = useState<string | null>(null);
169
+
170
+ // Picked position state (user-placed pin)
171
+ const [pickedLatLon, setPickedLatLon] = useState<LatLon | null>(null);
172
+ const [pickedElevation, setPickedElevation] = useState<number | null>(null);
173
+ const [elevationLoading, setElevationLoading] = useState(false);
174
+ const [projectedCoords, setProjectedCoords] = useState<{ easting: number; northing: number } | null>(null);
175
+
176
+ // Search state
177
+ const [searchOpen, setSearchOpen] = useState(false);
178
+ const [searchQuery, setSearchQuery] = useState('');
179
+ const [searchResults, setSearchResults] = useState<Array<{ lat: number; lon: number; display_name: string }>>([]);
180
+ const [searchLoading, setSearchLoading] = useState(false);
181
+ const debouncedQuery = useDebouncedValue(searchQuery, 400);
182
+
183
+ // Building footprint state (bounding box polygon in WGS84)
184
+ const [footprint, setFootprint] = useState<[number, number][] | null>(null);
185
+ const [styleVersion, setStyleVersion] = useState(0);
186
+ const footprintRef = useRef<[number, number][] | null>(null);
187
+
188
+ // Compute building footprint from bounding box
189
+ useEffect(() => {
190
+ if (!mapConversion || !projectedCRS || !coordinateInfo) {
191
+ setFootprint(null);
192
+ footprintRef.current = null;
193
+ return;
194
+ }
195
+
196
+ let cancelled = false;
197
+
198
+ computeFootprintGeoJSON(mapConversion, projectedCRS, coordinateInfo).then(fp => {
199
+ if (cancelled) return;
200
+ setFootprint(fp);
201
+ footprintRef.current = fp;
202
+ });
203
+
204
+ return () => { cancelled = true; };
205
+ }, [mapConversion, projectedCRS, coordinateInfo]);
206
+
207
+ // Geocode search
208
+ useEffect(() => {
209
+ if (!debouncedQuery.trim()) {
210
+ setSearchResults([]);
211
+ setSearchLoading(false);
212
+ return;
213
+ }
214
+ let cancelled = false;
215
+ setSearchLoading(true);
216
+ geocodeSearch(debouncedQuery).then(results => {
217
+ if (!cancelled) {
218
+ setSearchResults(results);
219
+ setSearchLoading(false);
220
+ }
221
+ });
222
+ return () => { cancelled = true; };
223
+ }, [debouncedQuery]);
224
+
225
+ // Reproject model position to lat/lon
226
+ useEffect(() => {
227
+ if (!mapConversion || !projectedCRS) {
228
+ setLatLon(null);
229
+ setError(null);
230
+ return;
231
+ }
232
+
233
+ let cancelled = false;
234
+ setMapState('loading');
235
+ setError(null);
236
+
237
+ reprojectToLatLon(mapConversion, projectedCRS, coordinateInfo).then(result => {
238
+ if (cancelled) return;
239
+ if (result) {
240
+ setLatLon(result);
241
+ setMapState('ready');
242
+ } else {
243
+ setLatLon(null);
244
+ setError('Could not resolve projection — EPSG code may be unsupported');
245
+ setMapState('error');
246
+ }
247
+ });
248
+
249
+ return () => { cancelled = true; };
250
+ }, [mapConversion, projectedCRS, coordinateInfo]);
251
+
252
+ // When a picked position changes, reverse-project and query elevation
253
+ useEffect(() => {
254
+ if (!pickedLatLon || !projectedCRS) {
255
+ setProjectedCoords(null);
256
+ setPickedElevation(null);
257
+ return;
258
+ }
259
+
260
+ let cancelled = false;
261
+ setProjectedCoords(null);
262
+
263
+ // Reverse-project to get IfcMapConversion eastings/northings
264
+ // Accounts for model local geometry offset, rotation, and scale
265
+ reprojectFromLatLon(pickedLatLon, projectedCRS, mapConversion, coordinateInfo).then(coords => {
266
+ if (!cancelled) setProjectedCoords(coords);
267
+ });
268
+
269
+ // Query terrain elevation
270
+ setElevationLoading(true);
271
+ queryTerrainElevation(pickedLatLon).then(elev => {
272
+ if (!cancelled) {
273
+ setPickedElevation(elev);
274
+ setElevationLoading(false);
275
+ }
276
+ });
277
+
278
+ return () => { cancelled = true; };
279
+ }, [pickedLatLon, projectedCRS, mapConversion, coordinateInfo]);
280
+
281
+ // Place or move the picked marker on the map
282
+ const updatePickedMarker = useCallback((pos: LatLon, maplibregl: typeof import('maplibre-gl')) => {
283
+ if (!mapRef.current) return;
284
+ if (pickedMarkerRef.current) {
285
+ pickedMarkerRef.current.setLngLat([pos.lon, pos.lat]);
286
+ } else {
287
+ const el = document.createElement('div');
288
+ el.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#7c3aed" stroke="white" stroke-width="2"><path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z"/><circle cx="12" cy="10" r="3"/></svg>`;
289
+ el.style.cursor = 'grab';
290
+ const marker = new maplibregl.Marker({ element: el, draggable: true })
291
+ .setLngLat([pos.lon, pos.lat])
292
+ .addTo(mapRef.current);
293
+ marker.on('dragend', () => {
294
+ const lngLat = marker.getLngLat();
295
+ setPickedLatLon({ lat: lngLat.lat, lon: lngLat.lng });
296
+ });
297
+ pickedMarkerRef.current = marker;
298
+ }
299
+ }, []);
300
+
301
+ // Handle map click to place pin (reads editable from ref to avoid stale closure)
302
+ const handleMapClick = useCallback((e: { lngLat: { lat: number; lng: number } }) => {
303
+ if (!editableRef.current) return;
304
+ const pos = { lat: e.lngLat.lat, lon: e.lngLat.lng };
305
+ setPickedLatLon(pos);
306
+ loadMaplibre().then(ml => updatePickedMarker(pos, ml));
307
+ }, [updatePickedMarker]);
308
+
309
+ // Handle search result selection
310
+ const handleSearchSelect = useCallback((result: { lat: number; lon: number; display_name: string }) => {
311
+ const pos = { lat: result.lat, lon: result.lon };
312
+ setPickedLatLon(pos);
313
+ setSearchOpen(false);
314
+ setSearchQuery('');
315
+ setSearchResults([]);
316
+
317
+ loadMaplibre().then(ml => {
318
+ updatePickedMarker(pos, ml);
319
+ mapRef.current?.flyTo({ center: [pos.lon, pos.lat], zoom: 16, duration: 1200 });
320
+ });
321
+ }, [updatePickedMarker]);
322
+
323
+ // Handle apply position (waits for elevation to finish loading)
324
+ const handleApply = useCallback(() => {
325
+ if (!projectedCoords || !onApplyPosition || elevationLoading) return;
326
+ onApplyPosition({
327
+ easting: Math.round(projectedCoords.easting * 1000) / 1000,
328
+ northing: Math.round(projectedCoords.northing * 1000) / 1000,
329
+ terrainHeight: pickedElevation,
330
+ });
331
+ // Clear picked state after applying
332
+ pickedMarkerRef.current?.remove();
333
+ pickedMarkerRef.current = null;
334
+ setPickedLatLon(null);
335
+ setProjectedCoords(null);
336
+ setPickedElevation(null);
337
+ }, [projectedCoords, pickedElevation, onApplyPosition, elevationLoading]);
338
+
339
+ // Clear picked pin
340
+ const handleClearPick = useCallback(() => {
341
+ pickedMarkerRef.current?.remove();
342
+ pickedMarkerRef.current = null;
343
+ setPickedLatLon(null);
344
+ setProjectedCoords(null);
345
+ setPickedElevation(null);
346
+ }, []);
347
+
348
+ // Initialize/update the map when we have a valid lat/lon
349
+ useEffect(() => {
350
+ if (!latLon || !containerRef.current) return;
351
+
352
+ let cancelled = false;
353
+
354
+ loadMaplibre().then(maplibregl => {
355
+ if (cancelled || !containerRef.current) return;
356
+
357
+ // If map already exists, just fly to new position
358
+ if (mapRef.current) {
359
+ mapRef.current.flyTo({ center: [latLon.lon, latLon.lat], zoom: 15, duration: 1200 });
360
+ if (markerRef.current) {
361
+ markerRef.current.setLngLat([latLon.lon, latLon.lat]);
362
+ }
363
+ return;
364
+ }
365
+
366
+ // Create new map
367
+ const map = new maplibregl.Map({
368
+ container: containerRef.current,
369
+ style: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',
370
+ center: [latLon.lon, latLon.lat],
371
+ zoom: 15,
372
+ attributionControl: false,
373
+ interactive: true,
374
+ });
375
+
376
+ map.addControl(new maplibregl.NavigationControl({ showCompass: false }), 'top-right');
377
+ map.addControl(new maplibregl.AttributionControl({ compact: false }), 'bottom-right');
378
+
379
+ // Add marker at model location (teal = current model position)
380
+ const marker = new maplibregl.Marker({ color: '#14b8a6' })
381
+ .setLngLat([latLon.lon, latLon.lat])
382
+ .addTo(map);
383
+
384
+ // Toggle marker vs footprint based on zoom level
385
+ map.on('zoomend', () => {
386
+ const zoom = map.getZoom();
387
+ if (markerRef.current) {
388
+ markerRef.current.getElement().style.opacity = zoom >= 17 ? '0' : '1';
389
+ markerRef.current.getElement().style.pointerEvents = zoom >= 17 ? 'none' : 'auto';
390
+ }
391
+ });
392
+
393
+ // Map click to place pin (only in edit mode)
394
+ map.on('click', handleMapClick);
395
+
396
+ mapRef.current = map;
397
+ markerRef.current = marker;
398
+
399
+ // If footprint was already computed before the map was created, add it now
400
+ if (footprintRef.current) {
401
+ map.once('load', () => {
402
+ addFootprintToMap(map, footprintRef.current!);
403
+ });
404
+ }
405
+ });
406
+
407
+ return () => {
408
+ cancelled = true;
409
+ };
410
+ }, [latLon, handleMapClick]);
411
+
412
+ // Add/update building footprint GeoJSON layer when footprint or style changes
413
+ useEffect(() => {
414
+ const map = mapRef.current;
415
+ if (!map) return;
416
+ if (!footprint) {
417
+ removeFootprintFromMap(map);
418
+ return;
419
+ }
420
+
421
+ if (map.isStyleLoaded()) {
422
+ addFootprintToMap(map, footprint);
423
+ } else {
424
+ map.once('style.load', () => addFootprintToMap(map, footprint));
425
+ }
426
+ }, [footprint, styleVersion]);
427
+
428
+ // Cleanup on unmount
429
+ useEffect(() => {
430
+ return () => {
431
+ pickedMarkerRef.current?.remove();
432
+ pickedMarkerRef.current = null;
433
+ markerRef.current?.remove();
434
+ markerRef.current = null;
435
+ mapRef.current?.remove();
436
+ mapRef.current = null;
437
+ };
438
+ }, []);
439
+
440
+ const googleMapsUrl = useMemo(() => {
441
+ if (!latLon) return null;
442
+ return `https://www.google.com/maps?q=${latLon.lat},${latLon.lon}`;
443
+ }, [latLon]);
444
+
445
+ const openStreetMapUrl = useMemo(() => {
446
+ if (!latLon) return null;
447
+ return `https://www.openstreetmap.org/?mlat=${latLon.lat}&mlon=${latLon.lon}#map=17/${latLon.lat}/${latLon.lon}`;
448
+ }, [latLon]);
449
+
450
+ const handleExportKmz = useCallback(() => {
451
+ if (!latLon || !geometryResult || !mapConversion) return;
452
+ try {
453
+ const exporter = new GLTFExporter(geometryResult);
454
+ const glb = new Uint8Array(exporter.exportGLB({ includeMetadata: true }));
455
+ const heading = ifcAngleToKmlHeading(mapConversion.xAxisAbscissa, mapConversion.xAxisOrdinate);
456
+ const kmz = buildKmz({
457
+ latLon,
458
+ altitude: mapConversion.orthogonalHeight,
459
+ heading,
460
+ glb,
461
+ name: 'IFC Model',
462
+ });
463
+ const blob = new Blob([kmz as BlobPart], { type: 'application/vnd.google-earth.kmz' });
464
+ const url = URL.createObjectURL(blob);
465
+ const a = document.createElement('a');
466
+ a.href = url;
467
+ a.download = 'model.kmz';
468
+ a.click();
469
+ URL.revokeObjectURL(url);
470
+ } catch (err) {
471
+ console.error('KMZ export failed:', err);
472
+ }
473
+ }, [latLon, geometryResult, mapConversion]);
474
+
475
+ const isDarkRef = useRef(false);
476
+
477
+ const handleStyleToggle = useCallback(() => {
478
+ if (!mapRef.current) return;
479
+ isDarkRef.current = !isDarkRef.current;
480
+ mapRef.current.setStyle(
481
+ isDarkRef.current
482
+ ? 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json'
483
+ : 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',
484
+ );
485
+ // Re-add markers and layers after style fully loads
486
+ if (mapRef.current) {
487
+ mapRef.current.once('style.load', () => {
488
+ if (markerRef.current && mapRef.current) {
489
+ markerRef.current.addTo(mapRef.current);
490
+ }
491
+ if (pickedMarkerRef.current && mapRef.current) {
492
+ pickedMarkerRef.current.addTo(mapRef.current);
493
+ }
494
+ // Trigger footprint layer re-add
495
+ setStyleVersion(v => v + 1);
496
+ });
497
+ }
498
+ }, []);
499
+
500
+ // Nothing to show if no georeferencing data
501
+ if (!mapConversion || !projectedCRS) {
502
+ return null;
503
+ }
504
+
505
+ return (
506
+ <div className="border-t border-zinc-100 dark:border-zinc-900">
507
+ {/* Header with search */}
508
+ <div className="flex items-center gap-2 px-3 py-1.5">
509
+ <MapIcon className="h-3 w-3 text-teal-500 shrink-0" />
510
+ <span className="font-bold text-[11px] text-zinc-700 dark:text-zinc-300 uppercase tracking-wide flex-1">
511
+ Location
512
+ </span>
513
+ {latLon && !searchOpen && (
514
+ <span className="text-[10px] font-mono text-teal-600/70 dark:text-teal-500/60">
515
+ {latLon.lat.toFixed(5)}, {latLon.lon.toFixed(5)}
516
+ </span>
517
+ )}
518
+ {editable && (
519
+ <button
520
+ onClick={() => { setSearchOpen(!searchOpen); setSearchQuery(''); setSearchResults([]); }}
521
+ className="p-0.5 text-zinc-400 hover:text-teal-600 dark:hover:text-teal-400 transition-colors"
522
+ title="Search for a place"
523
+ >
524
+ <Search className="h-3 w-3" />
525
+ </button>
526
+ )}
527
+ </div>
528
+
529
+ {/* Search bar */}
530
+ {editable && searchOpen && (
531
+ <div className="px-3 pb-1.5 relative">
532
+ <div className="flex items-center gap-1">
533
+ <div className="flex-1 relative">
534
+ <input
535
+ value={searchQuery}
536
+ onChange={e => setSearchQuery(e.target.value)}
537
+ placeholder="Search for a place..."
538
+ className="w-full text-[11px] px-2 py-1 border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 outline-none focus:ring-1 focus:ring-teal-400 focus:border-teal-400 placeholder:text-zinc-400/60"
539
+ autoFocus
540
+ onKeyDown={e => { if (e.key === 'Escape') { setSearchOpen(false); setSearchQuery(''); setSearchResults([]); } }}
541
+ />
542
+ {searchLoading && (
543
+ <Loader2 className="absolute right-2 top-1/2 -translate-y-1/2 h-3 w-3 text-teal-500 animate-spin" />
544
+ )}
545
+ </div>
546
+ <button
547
+ onClick={() => { setSearchOpen(false); setSearchQuery(''); setSearchResults([]); }}
548
+ className="p-0.5 text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300"
549
+ >
550
+ <X className="h-3 w-3" />
551
+ </button>
552
+ </div>
553
+
554
+ {/* Search results dropdown */}
555
+ {searchResults.length > 0 && (
556
+ <div className="absolute left-3 right-3 top-full z-50 mt-0.5 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-700 shadow-lg max-h-[160px] overflow-y-auto">
557
+ {searchResults.map((r, i) => (
558
+ <button
559
+ key={i}
560
+ onClick={() => handleSearchSelect(r)}
561
+ className="w-full text-left px-2 py-1.5 text-[10px] text-zinc-700 dark:text-zinc-300 hover:bg-teal-50 dark:hover:bg-teal-950/50 border-b border-zinc-100 dark:border-zinc-800 last:border-0 transition-colors"
562
+ >
563
+ <div className="flex items-start gap-1.5">
564
+ <MapPin className="h-3 w-3 text-teal-500 shrink-0 mt-0.5" />
565
+ <span className="line-clamp-2">{r.display_name}</span>
566
+ </div>
567
+ </button>
568
+ ))}
569
+ </div>
570
+ )}
571
+ </div>
572
+ )}
573
+
574
+ {/* Map container */}
575
+ {mapState === 'loading' && (
576
+ <div className="flex items-center justify-center h-[180px] bg-zinc-50 dark:bg-zinc-900/50">
577
+ <Loader2 className="h-4 w-4 text-teal-500 animate-spin" />
578
+ <span className="text-[10px] text-zinc-400 ml-2">Resolving coordinates...</span>
579
+ </div>
580
+ )}
581
+
582
+ {mapState === 'error' && (
583
+ <div className="flex items-center justify-center h-[60px] bg-zinc-50 dark:bg-zinc-900/50 gap-2 px-3">
584
+ <MapPinOff className="h-3.5 w-3.5 text-zinc-400 shrink-0" />
585
+ <span className="text-[10px] text-zinc-400">{error}</span>
586
+ </div>
587
+ )}
588
+
589
+ {(mapState === 'ready' || (mapState === 'loading' && latLon)) && (
590
+ <>
591
+ <div className="relative">
592
+ <div
593
+ ref={containerRef}
594
+ className="h-[180px] w-full [&_.maplibregl-ctrl-attrib]:!text-[7px] [&_.maplibregl-ctrl-attrib]:!bg-white/40 [&_.maplibregl-ctrl-attrib]:dark:!bg-black/30 [&_.maplibregl-ctrl-attrib]:!py-0 [&_.maplibregl-ctrl-attrib]:!px-1 [&_.maplibregl-ctrl-attrib]:!shadow-none [&_.maplibregl-ctrl-attrib]:!text-zinc-400/70 [&_.maplibregl-ctrl-attrib_a]:!text-zinc-400/70 [&_.maplibregl-ctrl-attrib]:!leading-normal"
595
+ style={{ minHeight: 180 }}
596
+ />
597
+ {/* Edit mode hint overlay */}
598
+ {editable && !pickedLatLon && (
599
+ <div className="absolute top-2 left-2 bg-white/90 dark:bg-zinc-900/90 backdrop-blur-sm px-2 py-1 text-[9px] text-zinc-500 dark:text-zinc-400 pointer-events-none shadow-sm border border-zinc-200/50 dark:border-zinc-700/50">
600
+ Click map to place pin
601
+ </div>
602
+ )}
603
+ </div>
604
+
605
+ {/* Picked position info bar */}
606
+ {pickedLatLon && editable && (
607
+ <div className="bg-purple-50/80 dark:bg-purple-950/30 border-t border-purple-200/50 dark:border-purple-800/30 px-3 py-2">
608
+ <div className="flex items-center gap-2 mb-1.5">
609
+ <MapPin className="h-3 w-3 text-purple-600 dark:text-purple-400 shrink-0" />
610
+ <span className="text-[10px] font-semibold text-purple-700 dark:text-purple-300 flex-1">
611
+ New Position
612
+ </span>
613
+ <button
614
+ onClick={handleClearPick}
615
+ className="p-0.5 text-purple-400 hover:text-purple-600 dark:hover:text-purple-300 transition-colors"
616
+ title="Remove pin"
617
+ >
618
+ <X className="h-3 w-3" />
619
+ </button>
620
+ </div>
621
+
622
+ <div className="grid grid-cols-2 gap-x-3 gap-y-0.5 text-[10px] font-mono mb-2">
623
+ <div className="text-zinc-500 dark:text-zinc-400">Lat/Lon</div>
624
+ <div className="text-purple-700 dark:text-purple-300 text-right">
625
+ {pickedLatLon.lat.toFixed(6)}, {pickedLatLon.lon.toFixed(6)}
626
+ </div>
627
+
628
+ {projectedCoords && (
629
+ <>
630
+ <div className="text-zinc-500 dark:text-zinc-400">Easting</div>
631
+ <div className="text-purple-700 dark:text-purple-300 text-right tabular-nums">
632
+ {projectedCoords.easting.toFixed(3)}
633
+ </div>
634
+ <div className="text-zinc-500 dark:text-zinc-400">Northing</div>
635
+ <div className="text-purple-700 dark:text-purple-300 text-right tabular-nums">
636
+ {projectedCoords.northing.toFixed(3)}
637
+ </div>
638
+ </>
639
+ )}
640
+
641
+ <div className="text-zinc-500 dark:text-zinc-400 flex items-center gap-1">
642
+ <Mountain className="h-2.5 w-2.5" />
643
+ Elevation
644
+ </div>
645
+ <div className="text-purple-700 dark:text-purple-300 text-right tabular-nums">
646
+ {elevationLoading ? (
647
+ <Loader2 className="h-2.5 w-2.5 animate-spin inline" />
648
+ ) : pickedElevation !== null ? (
649
+ `${pickedElevation.toFixed(1)} m`
650
+ ) : (
651
+ <span className="text-zinc-400">—</span>
652
+ )}
653
+ </div>
654
+ </div>
655
+
656
+ {/* Apply button */}
657
+ {onApplyPosition && projectedCoords && (
658
+ <button
659
+ onClick={handleApply}
660
+ disabled={elevationLoading}
661
+ className="w-full flex items-center justify-center gap-1.5 px-2 py-1.5 text-[10px] font-semibold text-white bg-purple-600 hover:bg-purple-700 dark:bg-purple-700 dark:hover:bg-purple-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
662
+ >
663
+ <Check className="h-3 w-3" />
664
+ Apply to Eastings / Northings
665
+ {pickedElevation !== null && ' / Height'}
666
+ </button>
667
+ )}
668
+ </div>
669
+ )}
670
+
671
+ {/* Action links */}
672
+ <div className="flex items-center gap-3 px-3 py-1.5 border-t border-zinc-100 dark:border-zinc-900">
673
+ {googleMapsUrl && (
674
+ <Tooltip>
675
+ <TooltipTrigger asChild>
676
+ <a
677
+ href={googleMapsUrl}
678
+ target="_blank"
679
+ rel="noopener noreferrer"
680
+ className="flex items-center gap-1 text-[10px] text-teal-600 dark:text-teal-400 hover:text-teal-800 dark:hover:text-teal-300 transition-colors"
681
+ >
682
+ <ExternalLink className="h-2.5 w-2.5" />
683
+ Google Maps
684
+ </a>
685
+ </TooltipTrigger>
686
+ <TooltipContent>Open model location in Google Maps</TooltipContent>
687
+ </Tooltip>
688
+ )}
689
+ {openStreetMapUrl && (
690
+ <Tooltip>
691
+ <TooltipTrigger asChild>
692
+ <a
693
+ href={openStreetMapUrl}
694
+ target="_blank"
695
+ rel="noopener noreferrer"
696
+ className="flex items-center gap-1 text-[10px] text-teal-600 dark:text-teal-400 hover:text-teal-800 dark:hover:text-teal-300 transition-colors"
697
+ >
698
+ <ExternalLink className="h-2.5 w-2.5" />
699
+ OpenStreetMap
700
+ </a>
701
+ </TooltipTrigger>
702
+ <TooltipContent>Open model location in OpenStreetMap</TooltipContent>
703
+ </Tooltip>
704
+ )}
705
+ {geometryResult && (
706
+ <Tooltip>
707
+ <TooltipTrigger asChild>
708
+ <button
709
+ onClick={handleExportKmz}
710
+ className="flex items-center gap-1 text-[10px] text-teal-600 dark:text-teal-400 hover:text-teal-800 dark:hover:text-teal-300 transition-colors"
711
+ >
712
+ <Globe2 className="h-2.5 w-2.5" />
713
+ Google Earth
714
+ </button>
715
+ </TooltipTrigger>
716
+ <TooltipContent>Download KMZ to open in Google Earth with model at correct position</TooltipContent>
717
+ </Tooltip>
718
+ )}
719
+ <button
720
+ onClick={handleStyleToggle}
721
+ className="ml-auto text-[10px] text-zinc-500 dark:text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-300 transition-colors"
722
+ >
723
+ Toggle style
724
+ </button>
725
+ </div>
726
+ </>
727
+ )}
728
+ </div>
729
+ );
730
+ }