@ifc-lite/viewer 1.17.3 → 1.17.6

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