@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.
- package/.turbo/turbo-build.log +34 -24
- package/.turbo/turbo-typecheck.log +1 -42
- package/CHANGELOG.md +24 -0
- package/DESKTOP_CONTRACT_VERSION +1 -0
- package/dist/assets/arrow-CZ5kQ26f.js +20 -0
- package/dist/assets/basketViewActivator-BmnNtVfZ.js +1 -0
- package/dist/assets/bcf-DOG9_WPX.js +281 -0
- package/dist/assets/{browser-BDShTXzi.js → browser-C5TFR7sH.js} +1 -1
- package/dist/assets/cesium-ADbP7waU.css +1 -0
- package/dist/assets/cesium-DUOzBlqv.js +17817 -0
- package/dist/assets/drawing-2d-gWfpdfYe.js +257 -0
- package/dist/assets/epsg-index.generated-BjJrt_0S.js +1 -0
- package/dist/assets/exporters-ChAtBmlj.js +80367 -0
- package/dist/assets/geometry.worker-BQ0rzNo-.js +1 -0
- package/dist/assets/ids-B4jTqB1O.js +1 -0
- package/dist/assets/ifc-lite_bg-BX4E7TX8.wasm +0 -0
- package/dist/assets/index-Co8E2-FE.js +106013 -0
- package/dist/assets/index-DckuDqlv.css +1 -0
- package/dist/assets/lens-CSASnhAL.js +1 -0
- package/dist/assets/maplibre-gl-CGLcoNXc.js +811 -0
- package/dist/assets/native-bridge-BRvbckFQ.js +429 -0
- package/dist/assets/{arrow2-bb-jcVEo.js → parquet-CEXmQNRO.js} +2 -2
- package/dist/assets/sandbox-DZiNLNMk.js +5933 -0
- package/dist/assets/server-client-BV8zHZ7Y.js +626 -0
- package/dist/assets/tauri-core-stub-D8Fa-u43.js +1 -0
- package/dist/assets/tauri-dialog-stub-r7Wksg7o.js +1 -0
- package/dist/assets/tauri-fs-stub-BdeRC7aK.js +1 -0
- package/dist/assets/wasm-bridge-g01g7T9b.js +1 -0
- package/dist/assets/zip-DBEtpeu6.js +12 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_0.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_1.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_10.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_11.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_12.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_13.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_14.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_15.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_16.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_17.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_18.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_19.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_2.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_20.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_21.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_22.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_23.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_24.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_25.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_26.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_27.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_3.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_4.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_5.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_6.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_7.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_8.json +1 -0
- package/dist/cesium/Assets/IAU2006_XYS/IAU2006_XYS_9.json +1 -0
- package/dist/cesium/Assets/Images/bing_maps_credit.png +0 -0
- package/dist/cesium/Assets/Images/cesium_credit.png +0 -0
- package/dist/cesium/Assets/Images/google_earth_credit.png +0 -0
- package/dist/cesium/Assets/Images/ion-credit.png +0 -0
- package/dist/cesium/Assets/Textures/LensFlare/DirtMask.jpg +0 -0
- package/dist/cesium/Assets/Textures/LensFlare/StarBurst.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/0/0/0.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/0/1/0.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/1/0/0.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/1/0/1.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/1/1/0.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/1/1/1.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/1/2/0.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/1/2/1.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/1/3/0.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/1/3/1.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/0.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/1.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/2.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/0/3.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/0.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/1.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/2.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/1/3.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/0.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/1.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/2.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/2/3.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/0.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/1.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/2.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/3/3.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/0.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/1.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/2.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/4/3.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/0.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/1.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/2.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/5/3.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/0.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/1.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/2.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/6/3.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/0.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/1.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/2.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/2/7/3.jpg +0 -0
- package/dist/cesium/Assets/Textures/NaturalEarthII/tilemapresource.xml +14 -0
- package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_mx.jpg +0 -0
- package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_my.jpg +0 -0
- package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_mz.jpg +0 -0
- package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_px.jpg +0 -0
- package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_py.jpg +0 -0
- package/dist/cesium/Assets/Textures/SkyBox/tycho2t3_80_pz.jpg +0 -0
- package/dist/cesium/Assets/Textures/maki/airfield.png +0 -0
- package/dist/cesium/Assets/Textures/maki/airport.png +0 -0
- package/dist/cesium/Assets/Textures/maki/alcohol-shop.png +0 -0
- package/dist/cesium/Assets/Textures/maki/america-football.png +0 -0
- package/dist/cesium/Assets/Textures/maki/art-gallery.png +0 -0
- package/dist/cesium/Assets/Textures/maki/bakery.png +0 -0
- package/dist/cesium/Assets/Textures/maki/bank.png +0 -0
- package/dist/cesium/Assets/Textures/maki/bar.png +0 -0
- package/dist/cesium/Assets/Textures/maki/baseball.png +0 -0
- package/dist/cesium/Assets/Textures/maki/basketball.png +0 -0
- package/dist/cesium/Assets/Textures/maki/beer.png +0 -0
- package/dist/cesium/Assets/Textures/maki/bicycle.png +0 -0
- package/dist/cesium/Assets/Textures/maki/building.png +0 -0
- package/dist/cesium/Assets/Textures/maki/bus.png +0 -0
- package/dist/cesium/Assets/Textures/maki/cafe.png +0 -0
- package/dist/cesium/Assets/Textures/maki/camera.png +0 -0
- package/dist/cesium/Assets/Textures/maki/campsite.png +0 -0
- package/dist/cesium/Assets/Textures/maki/car.png +0 -0
- package/dist/cesium/Assets/Textures/maki/cemetery.png +0 -0
- package/dist/cesium/Assets/Textures/maki/cesium.png +0 -0
- package/dist/cesium/Assets/Textures/maki/chemist.png +0 -0
- package/dist/cesium/Assets/Textures/maki/cinema.png +0 -0
- package/dist/cesium/Assets/Textures/maki/circle-stroked.png +0 -0
- package/dist/cesium/Assets/Textures/maki/circle.png +0 -0
- package/dist/cesium/Assets/Textures/maki/city.png +0 -0
- package/dist/cesium/Assets/Textures/maki/clothing-store.png +0 -0
- package/dist/cesium/Assets/Textures/maki/college.png +0 -0
- package/dist/cesium/Assets/Textures/maki/commercial.png +0 -0
- package/dist/cesium/Assets/Textures/maki/cricket.png +0 -0
- package/dist/cesium/Assets/Textures/maki/cross.png +0 -0
- package/dist/cesium/Assets/Textures/maki/dam.png +0 -0
- package/dist/cesium/Assets/Textures/maki/danger.png +0 -0
- package/dist/cesium/Assets/Textures/maki/disability.png +0 -0
- package/dist/cesium/Assets/Textures/maki/dog-park.png +0 -0
- package/dist/cesium/Assets/Textures/maki/embassy.png +0 -0
- package/dist/cesium/Assets/Textures/maki/emergency-telephone.png +0 -0
- package/dist/cesium/Assets/Textures/maki/entrance.png +0 -0
- package/dist/cesium/Assets/Textures/maki/farm.png +0 -0
- package/dist/cesium/Assets/Textures/maki/fast-food.png +0 -0
- package/dist/cesium/Assets/Textures/maki/ferry.png +0 -0
- package/dist/cesium/Assets/Textures/maki/fire-station.png +0 -0
- package/dist/cesium/Assets/Textures/maki/fuel.png +0 -0
- package/dist/cesium/Assets/Textures/maki/garden.png +0 -0
- package/dist/cesium/Assets/Textures/maki/gift.png +0 -0
- package/dist/cesium/Assets/Textures/maki/golf.png +0 -0
- package/dist/cesium/Assets/Textures/maki/grocery.png +0 -0
- package/dist/cesium/Assets/Textures/maki/hairdresser.png +0 -0
- package/dist/cesium/Assets/Textures/maki/harbor.png +0 -0
- package/dist/cesium/Assets/Textures/maki/heart.png +0 -0
- package/dist/cesium/Assets/Textures/maki/heliport.png +0 -0
- package/dist/cesium/Assets/Textures/maki/hospital.png +0 -0
- package/dist/cesium/Assets/Textures/maki/ice-cream.png +0 -0
- package/dist/cesium/Assets/Textures/maki/industrial.png +0 -0
- package/dist/cesium/Assets/Textures/maki/land-use.png +0 -0
- package/dist/cesium/Assets/Textures/maki/laundry.png +0 -0
- package/dist/cesium/Assets/Textures/maki/library.png +0 -0
- package/dist/cesium/Assets/Textures/maki/lighthouse.png +0 -0
- package/dist/cesium/Assets/Textures/maki/lodging.png +0 -0
- package/dist/cesium/Assets/Textures/maki/logging.png +0 -0
- package/dist/cesium/Assets/Textures/maki/london-underground.png +0 -0
- package/dist/cesium/Assets/Textures/maki/marker-stroked.png +0 -0
- package/dist/cesium/Assets/Textures/maki/marker.png +0 -0
- package/dist/cesium/Assets/Textures/maki/minefield.png +0 -0
- package/dist/cesium/Assets/Textures/maki/mobilephone.png +0 -0
- package/dist/cesium/Assets/Textures/maki/monument.png +0 -0
- package/dist/cesium/Assets/Textures/maki/museum.png +0 -0
- package/dist/cesium/Assets/Textures/maki/music.png +0 -0
- package/dist/cesium/Assets/Textures/maki/oil-well.png +0 -0
- package/dist/cesium/Assets/Textures/maki/park.png +0 -0
- package/dist/cesium/Assets/Textures/maki/park2.png +0 -0
- package/dist/cesium/Assets/Textures/maki/parking-garage.png +0 -0
- package/dist/cesium/Assets/Textures/maki/parking.png +0 -0
- package/dist/cesium/Assets/Textures/maki/pharmacy.png +0 -0
- package/dist/cesium/Assets/Textures/maki/pitch.png +0 -0
- package/dist/cesium/Assets/Textures/maki/place-of-worship.png +0 -0
- package/dist/cesium/Assets/Textures/maki/playground.png +0 -0
- package/dist/cesium/Assets/Textures/maki/police.png +0 -0
- package/dist/cesium/Assets/Textures/maki/polling-place.png +0 -0
- package/dist/cesium/Assets/Textures/maki/post.png +0 -0
- package/dist/cesium/Assets/Textures/maki/prison.png +0 -0
- package/dist/cesium/Assets/Textures/maki/rail-above.png +0 -0
- package/dist/cesium/Assets/Textures/maki/rail-light.png +0 -0
- package/dist/cesium/Assets/Textures/maki/rail-metro.png +0 -0
- package/dist/cesium/Assets/Textures/maki/rail-underground.png +0 -0
- package/dist/cesium/Assets/Textures/maki/rail.png +0 -0
- package/dist/cesium/Assets/Textures/maki/religious-christian.png +0 -0
- package/dist/cesium/Assets/Textures/maki/religious-jewish.png +0 -0
- package/dist/cesium/Assets/Textures/maki/religious-muslim.png +0 -0
- package/dist/cesium/Assets/Textures/maki/restaurant.png +0 -0
- package/dist/cesium/Assets/Textures/maki/roadblock.png +0 -0
- package/dist/cesium/Assets/Textures/maki/rocket.png +0 -0
- package/dist/cesium/Assets/Textures/maki/school.png +0 -0
- package/dist/cesium/Assets/Textures/maki/scooter.png +0 -0
- package/dist/cesium/Assets/Textures/maki/shop.png +0 -0
- package/dist/cesium/Assets/Textures/maki/skiing.png +0 -0
- package/dist/cesium/Assets/Textures/maki/slaughterhouse.png +0 -0
- package/dist/cesium/Assets/Textures/maki/soccer.png +0 -0
- package/dist/cesium/Assets/Textures/maki/square-stroked.png +0 -0
- package/dist/cesium/Assets/Textures/maki/square.png +0 -0
- package/dist/cesium/Assets/Textures/maki/star-stroked.png +0 -0
- package/dist/cesium/Assets/Textures/maki/star.png +0 -0
- package/dist/cesium/Assets/Textures/maki/suitcase.png +0 -0
- package/dist/cesium/Assets/Textures/maki/swimming.png +0 -0
- package/dist/cesium/Assets/Textures/maki/telephone.png +0 -0
- package/dist/cesium/Assets/Textures/maki/tennis.png +0 -0
- package/dist/cesium/Assets/Textures/maki/theatre.png +0 -0
- package/dist/cesium/Assets/Textures/maki/toilets.png +0 -0
- package/dist/cesium/Assets/Textures/maki/town-hall.png +0 -0
- package/dist/cesium/Assets/Textures/maki/town.png +0 -0
- package/dist/cesium/Assets/Textures/maki/triangle-stroked.png +0 -0
- package/dist/cesium/Assets/Textures/maki/triangle.png +0 -0
- package/dist/cesium/Assets/Textures/maki/village.png +0 -0
- package/dist/cesium/Assets/Textures/maki/warehouse.png +0 -0
- package/dist/cesium/Assets/Textures/maki/waste-basket.png +0 -0
- package/dist/cesium/Assets/Textures/maki/water.png +0 -0
- package/dist/cesium/Assets/Textures/maki/wetland.png +0 -0
- package/dist/cesium/Assets/Textures/maki/zoo.png +0 -0
- package/dist/cesium/Assets/Textures/moonSmall.jpg +0 -0
- package/dist/cesium/Assets/Textures/pin.svg +1 -0
- package/dist/cesium/Assets/Textures/waterNormals.jpg +0 -0
- package/dist/cesium/Assets/Textures/waterNormalsSmall.jpg +0 -0
- package/dist/cesium/Assets/approximateTerrainHeights.json +1 -0
- package/dist/cesium/ThirdParty/Workers/package.json +1 -0
- package/dist/cesium/ThirdParty/Workers/zip-web-worker.js +1 -0
- package/dist/cesium/ThirdParty/basis_transcoder.wasm +0 -0
- package/dist/cesium/ThirdParty/draco_decoder.wasm +0 -0
- package/dist/cesium/ThirdParty/google-earth-dbroot-parser.js +1 -0
- package/dist/cesium/ThirdParty/wasm_splats_bg.wasm +0 -0
- package/dist/cesium/ThirdParty/zip-module.wasm +0 -0
- package/dist/cesium/Widgets/Animation/Animation.css +127 -0
- package/dist/cesium/Widgets/Animation/lighter.css +70 -0
- package/dist/cesium/Widgets/BaseLayerPicker/BaseLayerPicker.css +108 -0
- package/dist/cesium/Widgets/BaseLayerPicker/lighter.css +22 -0
- package/dist/cesium/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.css +102 -0
- package/dist/cesium/Widgets/CesiumInspector/CesiumInspector.css +113 -0
- package/dist/cesium/Widgets/CesiumWidget/CesiumWidget.css +119 -0
- package/dist/cesium/Widgets/CesiumWidget/lighter.css +14 -0
- package/dist/cesium/Widgets/FullscreenButton/FullscreenButton.css +8 -0
- package/dist/cesium/Widgets/Geocoder/Geocoder.css +70 -0
- package/dist/cesium/Widgets/Geocoder/lighter.css +17 -0
- package/dist/cesium/Widgets/I3SBuildingSceneLayerExplorer/I3SBuildingSceneLayerExplorer.css +27 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/ArcGisMapServiceWorldHillshade.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/ArcGisMapServiceWorldImagery.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/ArcGisMapServiceWorldOcean.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/azureAerial.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/azureRoads.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/bingAerial.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/bingAerialLabels.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/bingRoads.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/blueMarble.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/earthAtNight.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/googleContour.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/googleRoadmap.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/googleSatellite.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/googleSatelliteLabels.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/mapQuestOpenStreetMap.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/mapboxSatellite.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/mapboxStreets.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/mapboxTerrain.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/naturalEarthII.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/openStreetMap.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/sentinel-2.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/stadiaAlidadeSmooth.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/stadiaAlidadeSmoothDark.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/stamenToner.png +0 -0
- package/dist/cesium/Widgets/Images/ImageryProviders/stamenWatercolor.png +0 -0
- package/dist/cesium/Widgets/Images/NavigationHelp/Mouse.svg +84 -0
- package/dist/cesium/Widgets/Images/NavigationHelp/MouseLeft.svg +76 -0
- package/dist/cesium/Widgets/Images/NavigationHelp/MouseMiddle.svg +76 -0
- package/dist/cesium/Widgets/Images/NavigationHelp/MouseRight.svg +76 -0
- package/dist/cesium/Widgets/Images/NavigationHelp/Touch.svg +120 -0
- package/dist/cesium/Widgets/Images/NavigationHelp/TouchDrag.svg +129 -0
- package/dist/cesium/Widgets/Images/NavigationHelp/TouchRotate.svg +76 -0
- package/dist/cesium/Widgets/Images/NavigationHelp/TouchTilt.svg +135 -0
- package/dist/cesium/Widgets/Images/NavigationHelp/TouchZoom.svg +74 -0
- package/dist/cesium/Widgets/Images/TerrainProviders/CesiumWorldTerrain.png +0 -0
- package/dist/cesium/Widgets/Images/TerrainProviders/Ellipsoid.png +0 -0
- package/dist/cesium/Widgets/Images/TimelineIcons.png +0 -0
- package/dist/cesium/Widgets/Images/info-loading.gif +0 -0
- package/dist/cesium/Widgets/InfoBox/InfoBox.css +92 -0
- package/dist/cesium/Widgets/InfoBox/InfoBoxDescription.css +178 -0
- package/dist/cesium/Widgets/NavigationHelpButton/NavigationHelpButton.css +93 -0
- package/dist/cesium/Widgets/NavigationHelpButton/lighter.css +38 -0
- package/dist/cesium/Widgets/PerformanceWatchdog/PerformanceWatchdog.css +15 -0
- package/dist/cesium/Widgets/ProjectionPicker/ProjectionPicker.css +38 -0
- package/dist/cesium/Widgets/SceneModePicker/SceneModePicker.css +56 -0
- package/dist/cesium/Widgets/SelectionIndicator/SelectionIndicator.css +20 -0
- package/dist/cesium/Widgets/Timeline/Timeline.css +103 -0
- package/dist/cesium/Widgets/Timeline/lighter.css +23 -0
- package/dist/cesium/Widgets/VRButton/VRButton.css +8 -0
- package/dist/cesium/Widgets/Viewer/Viewer.css +107 -0
- package/dist/cesium/Widgets/VoxelInspector/VoxelInspector.css +16 -0
- package/dist/cesium/Widgets/lighter.css +237 -0
- package/dist/cesium/Widgets/lighterShared.css +46 -0
- package/dist/cesium/Widgets/shared.css +103 -0
- package/dist/cesium/Widgets/widgets.css +1342 -0
- package/dist/cesium/Workers/chunk-23ZQ2IVV.js +29 -0
- package/dist/cesium/Workers/chunk-2EQO3Q56.js +26 -0
- package/dist/cesium/Workers/chunk-2MJIIVP4.js +26 -0
- package/dist/cesium/Workers/chunk-2TE5NTVD.js +26 -0
- package/dist/cesium/Workers/chunk-2ZBHLJST.js +26 -0
- package/dist/cesium/Workers/chunk-5TJMAQVL.js +26 -0
- package/dist/cesium/Workers/chunk-6BD4U3VO.js +26 -0
- package/dist/cesium/Workers/chunk-7TVGLKQF.js +26 -0
- package/dist/cesium/Workers/chunk-BTSYJ5XU.js +26 -0
- package/dist/cesium/Workers/chunk-BXMEEOCS.js +63 -0
- package/dist/cesium/Workers/chunk-BYLCY7GP.js +29 -0
- package/dist/cesium/Workers/chunk-CTHM3W6I.js +26 -0
- package/dist/cesium/Workers/chunk-CUUSNIVQ.js +26 -0
- package/dist/cesium/Workers/chunk-E3JOOS3S.js +26 -0
- package/dist/cesium/Workers/chunk-E7KYDCM5.js +26 -0
- package/dist/cesium/Workers/chunk-EDVBB7SS.js +27 -0
- package/dist/cesium/Workers/chunk-EFBN7QNX.js +26 -0
- package/dist/cesium/Workers/chunk-EQ4YRVWL.js +26 -0
- package/dist/cesium/Workers/chunk-F6PRE7D6.js +26 -0
- package/dist/cesium/Workers/chunk-FC4ZZ65J.js +26 -0
- package/dist/cesium/Workers/chunk-FFBVWF2L.js +26 -0
- package/dist/cesium/Workers/chunk-GBAA6GVX.js +26 -0
- package/dist/cesium/Workers/chunk-ICALLYLG.js +26 -0
- package/dist/cesium/Workers/chunk-ILRYTWTP.js +26 -0
- package/dist/cesium/Workers/chunk-IRNLBSEJ.js +26 -0
- package/dist/cesium/Workers/chunk-IX4VMHEV.js +26 -0
- package/dist/cesium/Workers/chunk-L6QHHACZ.js +26 -0
- package/dist/cesium/Workers/chunk-LI2ZSORM.js +26 -0
- package/dist/cesium/Workers/chunk-LSLE2RL4.js +26 -0
- package/dist/cesium/Workers/chunk-M4HLDBCG.js +26 -0
- package/dist/cesium/Workers/chunk-MJHHSGEH.js +26 -0
- package/dist/cesium/Workers/chunk-NMVKML6W.js +26 -0
- package/dist/cesium/Workers/chunk-OCWJRAXS.js +26 -0
- package/dist/cesium/Workers/chunk-OIRKANTH.js +26 -0
- package/dist/cesium/Workers/chunk-OIT7J4IC.js +26 -0
- package/dist/cesium/Workers/chunk-OLZ3FYUM.js +26 -0
- package/dist/cesium/Workers/chunk-Q5BPHJQF.js +26 -0
- package/dist/cesium/Workers/chunk-QFM5DCMQ.js +26 -0
- package/dist/cesium/Workers/chunk-QKUIYMGC.js +28 -0
- package/dist/cesium/Workers/chunk-S44JILQT.js +26 -0
- package/dist/cesium/Workers/chunk-SLT4J352.js +26 -0
- package/dist/cesium/Workers/chunk-SQMIIXB7.js +26 -0
- package/dist/cesium/Workers/chunk-TJ4XLGBQ.js +26 -0
- package/dist/cesium/Workers/chunk-TNSUQXWK.js +27 -0
- package/dist/cesium/Workers/chunk-UBOGZS7F.js +26 -0
- package/dist/cesium/Workers/chunk-V3OSTMM6.js +26 -0
- package/dist/cesium/Workers/chunk-V7QEYVP3.js +26 -0
- package/dist/cesium/Workers/chunk-VUKYSU4H.js +26 -0
- package/dist/cesium/Workers/chunk-W37FE5GR.js +26 -0
- package/dist/cesium/Workers/chunk-WBOV35NL.js +26 -0
- package/dist/cesium/Workers/chunk-WPMZLB3Y.js +26 -0
- package/dist/cesium/Workers/chunk-WWWZVEEH.js +26 -0
- package/dist/cesium/Workers/chunk-XFIQ5DEQ.js +28 -0
- package/dist/cesium/Workers/chunk-XQHLGIO7.js +26 -0
- package/dist/cesium/Workers/chunk-XUSCFAVF.js +26 -0
- package/dist/cesium/Workers/chunk-YP7I5QBZ.js +26 -0
- package/dist/cesium/Workers/chunk-Z3QF2EHT.js +26 -0
- package/dist/cesium/Workers/combineGeometry.js +26 -0
- package/dist/cesium/Workers/createBoxGeometry.js +26 -0
- package/dist/cesium/Workers/createBoxOutlineGeometry.js +26 -0
- package/dist/cesium/Workers/createCircleGeometry.js +26 -0
- package/dist/cesium/Workers/createCircleOutlineGeometry.js +26 -0
- package/dist/cesium/Workers/createCoplanarPolygonGeometry.js +26 -0
- package/dist/cesium/Workers/createCoplanarPolygonOutlineGeometry.js +26 -0
- package/dist/cesium/Workers/createCorridorGeometry.js +26 -0
- package/dist/cesium/Workers/createCorridorOutlineGeometry.js +26 -0
- package/dist/cesium/Workers/createCylinderGeometry.js +26 -0
- package/dist/cesium/Workers/createCylinderOutlineGeometry.js +26 -0
- package/dist/cesium/Workers/createEllipseGeometry.js +26 -0
- package/dist/cesium/Workers/createEllipseOutlineGeometry.js +26 -0
- package/dist/cesium/Workers/createEllipsoidGeometry.js +26 -0
- package/dist/cesium/Workers/createEllipsoidOutlineGeometry.js +26 -0
- package/dist/cesium/Workers/createFrustumGeometry.js +26 -0
- package/dist/cesium/Workers/createFrustumOutlineGeometry.js +26 -0
- package/dist/cesium/Workers/createGeometry.js +26 -0
- package/dist/cesium/Workers/createGroundPolylineGeometry.js +26 -0
- package/dist/cesium/Workers/createPlaneGeometry.js +26 -0
- package/dist/cesium/Workers/createPlaneOutlineGeometry.js +26 -0
- package/dist/cesium/Workers/createPolygonGeometry.js +26 -0
- package/dist/cesium/Workers/createPolygonOutlineGeometry.js +26 -0
- package/dist/cesium/Workers/createPolylineGeometry.js +26 -0
- package/dist/cesium/Workers/createPolylineVolumeGeometry.js +26 -0
- package/dist/cesium/Workers/createPolylineVolumeOutlineGeometry.js +26 -0
- package/dist/cesium/Workers/createRectangleGeometry.js +26 -0
- package/dist/cesium/Workers/createRectangleOutlineGeometry.js +26 -0
- package/dist/cesium/Workers/createSimplePolylineGeometry.js +26 -0
- package/dist/cesium/Workers/createSphereGeometry.js +26 -0
- package/dist/cesium/Workers/createSphereOutlineGeometry.js +26 -0
- package/dist/cesium/Workers/createTaskProcessorWorker.js +26 -0
- package/dist/cesium/Workers/createVectorTileClampedPolylines.js +26 -0
- package/dist/cesium/Workers/createVectorTileGeometries.js +26 -0
- package/dist/cesium/Workers/createVectorTilePoints.js +26 -0
- package/dist/cesium/Workers/createVectorTilePolygons.js +26 -0
- package/dist/cesium/Workers/createVectorTilePolylines.js +26 -0
- package/dist/cesium/Workers/createVerticesFromCesium3DTilesTerrain.js +26 -0
- package/dist/cesium/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +26 -0
- package/dist/cesium/Workers/createVerticesFromHeightmap.js +26 -0
- package/dist/cesium/Workers/createVerticesFromQuantizedTerrainMesh.js +26 -0
- package/dist/cesium/Workers/createWallGeometry.js +26 -0
- package/dist/cesium/Workers/createWallOutlineGeometry.js +26 -0
- package/dist/cesium/Workers/decodeDraco.js +26 -0
- package/dist/cesium/Workers/decodeGoogleEarthEnterprisePacket.js +26 -0
- package/dist/cesium/Workers/decodeI3S.js +26 -0
- package/dist/cesium/Workers/gaussianSplatSorter.js +26 -0
- package/dist/cesium/Workers/gaussianSplatTextureGenerator.js +26 -0
- package/dist/cesium/Workers/incrementallyBuildTerrainPicker.js +26 -0
- package/dist/cesium/Workers/transcodeKTX2.js +56 -0
- package/dist/cesium/Workers/transferTypedArrayTest.js +26 -0
- package/dist/cesium/Workers/upsampleQuantizedTerrainMesh.js +26 -0
- package/dist/cesium/Workers/upsampleVerticesFromCesium3DTilesTerrain.js +26 -0
- package/dist/index.html +13 -2
- package/package.json +27 -19
- package/src/App.tsx +1 -17
- package/src/components/viewer/BCFPanel.tsx +46 -4
- package/src/components/viewer/CesiumOverlay.tsx +672 -0
- package/src/components/viewer/CesiumSettingsDialog.tsx +100 -0
- package/src/components/viewer/ChatPanel.tsx +54 -16
- package/src/components/viewer/CommandPalette.tsx +6 -1
- package/src/components/viewer/DesktopEntitlementBanner.tsx +74 -0
- package/src/components/viewer/ExportChangesButton.tsx +24 -4
- package/src/components/viewer/ExportDialog.tsx +38 -9
- package/src/components/viewer/HierarchyPanel.tsx +202 -6
- package/src/components/viewer/IDSPanel.tsx +52 -3
- package/src/components/viewer/KeyboardShortcutsDialog.tsx +1 -1
- package/src/components/viewer/MainToolbar.tsx +353 -27
- package/src/components/viewer/PropertiesPanel.tsx +306 -131
- package/src/components/viewer/ScriptPanel.tsx +34 -8
- package/src/components/viewer/Section2DPanel.tsx +3 -2
- package/src/components/viewer/SettingsPage.tsx +430 -0
- package/src/components/viewer/StatusBar.tsx +17 -1
- package/src/components/viewer/UpgradePage.tsx +6 -4
- package/src/components/viewer/ViewerLayout.tsx +47 -6
- package/src/components/viewer/Viewport.tsx +49 -8
- package/src/components/viewer/ViewportContainer.tsx +285 -32
- package/src/components/viewer/ViewportOverlays.tsx +129 -27
- package/src/components/viewer/hierarchy/treeDataBuilder.ts +2 -1
- package/src/components/viewer/properties/EpsgLookupDialog.tsx +418 -0
- package/src/components/viewer/properties/GeoreferencingPanel.tsx +703 -0
- package/src/components/viewer/properties/LocationMap.tsx +730 -0
- package/src/components/viewer/properties/ModelMetadataPanel.tsx +3 -70
- package/src/components/viewer/selectionHandlers.ts +4 -3
- package/src/components/viewer/useAnimationLoop.ts +4 -0
- package/src/components/viewer/useGeometryStreaming.ts +127 -40
- package/src/components/viewer/useMouseControls.ts +4 -1
- package/src/hooks/bcfIdLookup.ts +13 -11
- package/src/hooks/ids/idsColorSystem.ts +3 -8
- package/src/hooks/ingest/viewerModelIngest.ts +275 -0
- package/src/hooks/useIDS.ts +32 -17
- package/src/hooks/useIfc.ts +7 -1
- package/src/hooks/useIfcCache.ts +28 -15
- package/src/hooks/useIfcFederation.ts +59 -227
- package/src/hooks/useIfcLoader.ts +1656 -130
- package/src/hooks/useIfcServer.ts +0 -69
- package/src/lib/desktop/ClerkDesktopEntitlementSync.tsx +175 -0
- package/src/lib/desktop/desktopEntitlementEvents.ts +39 -0
- package/src/lib/desktop-entitlement.ts +45 -0
- package/src/lib/desktop-product.ts +124 -0
- package/src/lib/geo/cesium-bridge.ts +310 -0
- package/src/lib/geo/kmz-exporter.ts +112 -0
- package/src/lib/geo/reproject.ts +370 -0
- package/src/lib/lens/adapter.ts +3 -1
- package/src/lib/recent-files.ts +2 -1
- package/src/main.tsx +1 -0
- package/src/sdk/adapters/export-adapter.ts +14 -1
- package/src/sdk/adapters/viewer-adapter.ts +5 -9
- package/src/sdk/adapters/visibility-adapter.ts +6 -9
- package/src/services/analysis-extensions.ts +125 -0
- package/src/services/app-navigation.ts +13 -0
- package/src/services/bsdd.ts +53 -4
- package/src/services/cacheService.ts +1 -1
- package/src/services/desktop-cache.ts +43 -0
- package/src/services/desktop-export.ts +77 -0
- package/src/services/desktop-harness.ts +196 -0
- package/src/services/desktop-logger.ts +20 -0
- package/src/services/desktop-native-metadata.ts +207 -0
- package/src/services/desktop-panel-actions.ts +43 -0
- package/src/services/desktop-preferences.ts +44 -0
- package/src/services/file-dialog.ts +147 -0
- package/src/services/tauri-core-stub.ts +7 -0
- package/src/services/tauri-dialog-stub.ts +7 -0
- package/src/services/tauri-fs-stub.ts +7 -0
- package/src/store/basketVisibleSet.ts +3 -4
- package/src/store/globalId.ts +79 -0
- package/src/store/index.ts +41 -2
- package/src/store/slices/cesiumSlice.ts +122 -0
- package/src/store/slices/chatSlice.ts +5 -1
- package/src/store/slices/dataSlice.ts +139 -28
- package/src/store/slices/desktopEntitlementSlice.ts +86 -0
- package/src/store/slices/loadingSlice.ts +14 -2
- package/src/store/slices/modelSlice.ts +58 -3
- package/src/store/slices/mutationSlice.ts +178 -0
- package/src/store/slices/pinboardSlice.ts +4 -8
- package/src/store/types.ts +96 -2
- package/src/store.ts +1 -1
- package/src/utils/desktopModelSnapshot.ts +358 -0
- package/src/utils/ifcConfig.ts +6 -1
- package/src/utils/nativeSpatialDataStore.ts +250 -0
- package/src/utils/serverDataModel.ts +4 -0
- package/src/utils/spatialHierarchy.ts +10 -11
- package/vite.config.ts +41 -0
- package/dist/assets/Arrow.dom-BhOg9lpn.js +0 -20
- package/dist/assets/arrow2_bg-BlXl-cSQ.js +0 -1
- package/dist/assets/basketViewActivator-BRG5DBmM.js +0 -1
- package/dist/assets/desktop-cache-oPzaWXYE.js +0 -1
- package/dist/assets/geometry.worker-kgiT_Qhh.js +0 -1
- package/dist/assets/ifc-lite_bg-FNRmpSvM.wasm +0 -0
- package/dist/assets/index-B1Ecw4AU.js +0 -189756
- package/dist/assets/index-Ba4eoTe7.css +0 -1
- package/dist/assets/index-CrgYBjTn.js +0 -229
- package/dist/assets/module-6F3E5H7Y-tx0BadV3.js +0 -6
- package/dist/assets/native-bridge-Crsb7TKz.js +0 -111
- package/dist/assets/wasm-bridge-mJUhb7uk.js +0 -1
|
@@ -0,0 +1,703 @@
|
|
|
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
|
+
* Georeferencing panel - displays and allows editing of IfcProjectedCRS
|
|
7
|
+
* and IfcMapConversion entities with field-specific editing assistance.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useState, useCallback, useMemo } from 'react';
|
|
11
|
+
import { Globe, MapPin, PenLine, Check, X, Search, ChevronRight, Mountain } from 'lucide-react';
|
|
12
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
|
13
|
+
import { Badge } from '@/components/ui/badge';
|
|
14
|
+
import { computeAngleToGridNorth, type GeoreferenceInfo, type MapConversion, type ProjectedCRS } from '@ifc-lite/parser';
|
|
15
|
+
import { useViewerStore } from '@/store';
|
|
16
|
+
import type { CoordinateInfo, GeometryResult } from '@ifc-lite/geometry';
|
|
17
|
+
import { EpsgLookupDialog, type EpsgResult } from './EpsgLookupDialog';
|
|
18
|
+
import { LocationMap, type PickedPosition } from './LocationMap';
|
|
19
|
+
|
|
20
|
+
// ── Field-specific assistance data ─────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
const COMMON_DATUMS = ['WGS84', 'ETRS89', 'NAD83', 'NAD27', 'GRS80', 'Bessel 1841', 'Clarke 1866'];
|
|
23
|
+
const COMMON_PROJECTIONS = ['Transverse Mercator', 'UTM', 'Lambert Conformal Conic', 'Mercator', 'Stereographic', 'Oblique Mercator'];
|
|
24
|
+
const MAP_UNITS = ['METRE', 'FOOT', 'US SURVEY FOOT'];
|
|
25
|
+
const COMMON_VERTICAL_DATUMS = ['MSL', 'NAVD88', 'EVRF2007', 'EVRF2019', 'AHD', 'ODN', 'LN02'];
|
|
26
|
+
|
|
27
|
+
type FieldHint = {
|
|
28
|
+
placeholder?: string;
|
|
29
|
+
suggestions?: string[];
|
|
30
|
+
isSelect?: boolean;
|
|
31
|
+
helpText?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function getFieldHint(entity: string, field: string): FieldHint {
|
|
35
|
+
if (entity === 'projectedCRS') {
|
|
36
|
+
switch (field) {
|
|
37
|
+
case 'name': return { placeholder: 'e.g. EPSG:4326', helpText: 'Use EPSG lookup to search' };
|
|
38
|
+
case 'description': return { placeholder: 'e.g. WGS 84 / UTM zone 32N' };
|
|
39
|
+
case 'geodeticDatum': return { placeholder: 'e.g. WGS84', suggestions: COMMON_DATUMS };
|
|
40
|
+
case 'verticalDatum': return { placeholder: 'e.g. MSL', suggestions: COMMON_VERTICAL_DATUMS };
|
|
41
|
+
case 'mapProjection': return { placeholder: 'e.g. Transverse Mercator', suggestions: COMMON_PROJECTIONS };
|
|
42
|
+
case 'mapZone': return { placeholder: 'e.g. 32N' };
|
|
43
|
+
case 'mapUnit': return { isSelect: true, suggestions: MAP_UNITS };
|
|
44
|
+
default: return {};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (entity === 'mapConversion') {
|
|
48
|
+
switch (field) {
|
|
49
|
+
case 'eastings': return { placeholder: '0.0', helpText: 'X offset in map units' };
|
|
50
|
+
case 'northings': return { placeholder: '0.0', helpText: 'Y offset in map units' };
|
|
51
|
+
case 'orthogonalHeight': return { placeholder: '0.0', helpText: 'Z offset in metres' };
|
|
52
|
+
case 'xAxisAbscissa': return { placeholder: '1.0', helpText: 'cos(angle to grid north)' };
|
|
53
|
+
case 'xAxisOrdinate': return { placeholder: '0.0', helpText: 'sin(angle to grid north)' };
|
|
54
|
+
case 'scale': return { placeholder: '1.0', helpText: 'Usually 1.0 or close to it' };
|
|
55
|
+
default: return {};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── GeorefRow: a single editable field ─────────────────────────────────
|
|
62
|
+
|
|
63
|
+
interface GeorefRowProps {
|
|
64
|
+
label: string;
|
|
65
|
+
value: string | number | undefined | null;
|
|
66
|
+
suffix?: string;
|
|
67
|
+
isComputed?: boolean;
|
|
68
|
+
isNumber?: boolean;
|
|
69
|
+
editable?: boolean;
|
|
70
|
+
isMutated?: boolean;
|
|
71
|
+
fieldEntity?: string;
|
|
72
|
+
fieldName?: string;
|
|
73
|
+
onSave?: (value: string | number) => void;
|
|
74
|
+
/** Extra inline content rendered after the value (e.g. terrain height button) */
|
|
75
|
+
children?: React.ReactNode;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function GeorefRow({ label, value, suffix, isComputed, isNumber, editable, isMutated, fieldEntity, fieldName, onSave, children }: GeorefRowProps) {
|
|
79
|
+
const [editing, setEditing] = useState(false);
|
|
80
|
+
const [editValue, setEditValue] = useState('');
|
|
81
|
+
|
|
82
|
+
const hint = useMemo(() => getFieldHint(fieldEntity ?? '', fieldName ?? ''), [fieldEntity, fieldName]);
|
|
83
|
+
|
|
84
|
+
const startEdit = useCallback(() => {
|
|
85
|
+
if (!editable || isComputed) return;
|
|
86
|
+
setEditValue(value != null ? String(value) : '');
|
|
87
|
+
setEditing(true);
|
|
88
|
+
}, [value, editable, isComputed]);
|
|
89
|
+
|
|
90
|
+
const commitEdit = useCallback((overrideValue?: string) => {
|
|
91
|
+
if (!onSave) { setEditing(false); return; }
|
|
92
|
+
const trimmed = (overrideValue ?? editValue).trim();
|
|
93
|
+
if (!trimmed && !hint.isSelect) { setEditing(false); return; }
|
|
94
|
+
if (isNumber) {
|
|
95
|
+
const num = parseFloat(trimmed);
|
|
96
|
+
if (!Number.isFinite(num)) { setEditing(false); return; }
|
|
97
|
+
onSave(num);
|
|
98
|
+
} else {
|
|
99
|
+
onSave(trimmed);
|
|
100
|
+
}
|
|
101
|
+
setEditing(false);
|
|
102
|
+
}, [editValue, isNumber, onSave, hint.isSelect]);
|
|
103
|
+
|
|
104
|
+
const cancelEdit = useCallback(() => {
|
|
105
|
+
setEditing(false);
|
|
106
|
+
}, []);
|
|
107
|
+
|
|
108
|
+
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
109
|
+
if (e.key === 'Enter') commitEdit();
|
|
110
|
+
if (e.key === 'Escape') cancelEdit();
|
|
111
|
+
}, [commitEdit, cancelEdit]);
|
|
112
|
+
|
|
113
|
+
const selectSuggestion = useCallback((s: string) => {
|
|
114
|
+
if (!onSave) return;
|
|
115
|
+
if (isNumber) {
|
|
116
|
+
const num = parseFloat(s);
|
|
117
|
+
if (Number.isFinite(num)) onSave(num);
|
|
118
|
+
} else {
|
|
119
|
+
onSave(s);
|
|
120
|
+
}
|
|
121
|
+
setEditing(false);
|
|
122
|
+
}, [onSave, isNumber]);
|
|
123
|
+
|
|
124
|
+
const displayValue = value != null ? String(value) : '-';
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<div
|
|
128
|
+
className={`flex items-start gap-2 px-3 py-1.5 min-w-0 ${
|
|
129
|
+
isMutated ? 'bg-purple-50/50 dark:bg-purple-950/30' : ''
|
|
130
|
+
} ${editable && !isComputed ? 'cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-900/50 group/row' : ''}`}
|
|
131
|
+
onClick={!editing ? startEdit : undefined}
|
|
132
|
+
>
|
|
133
|
+
<span className="text-[11px] text-zinc-500 dark:text-zinc-400 shrink-0 pt-0.5 flex items-center gap-0.5 min-w-[110px]">
|
|
134
|
+
{isComputed && (
|
|
135
|
+
<Tooltip>
|
|
136
|
+
<TooltipTrigger asChild>
|
|
137
|
+
<span className="text-[10px] text-teal-500">*</span>
|
|
138
|
+
</TooltipTrigger>
|
|
139
|
+
<TooltipContent>Computed from XAxisAbscissa and XAxisOrdinate</TooltipContent>
|
|
140
|
+
</Tooltip>
|
|
141
|
+
)}
|
|
142
|
+
{label}
|
|
143
|
+
</span>
|
|
144
|
+
<div className="flex-1 flex flex-col items-end gap-0.5 min-w-0">
|
|
145
|
+
<div className="flex items-start gap-1 w-full justify-end">
|
|
146
|
+
{isMutated && !editing && (
|
|
147
|
+
<Badge variant="secondary" className="h-4 px-1 text-[9px] bg-purple-100 dark:bg-purple-900 text-purple-700 dark:text-purple-300 border-purple-200 dark:border-purple-700 shrink-0 mt-0.5">
|
|
148
|
+
edited
|
|
149
|
+
</Badge>
|
|
150
|
+
)}
|
|
151
|
+
{editing ? (
|
|
152
|
+
<div className="flex flex-col gap-1 w-full" onClick={e => e.stopPropagation()}>
|
|
153
|
+
<div className="flex items-center gap-1">
|
|
154
|
+
{hint.isSelect ? (
|
|
155
|
+
<select
|
|
156
|
+
value={editValue}
|
|
157
|
+
onChange={e => { setEditValue(e.target.value); }}
|
|
158
|
+
className="flex-1 text-[11px] font-mono px-1.5 py-1 border border-teal-400 dark:border-teal-600 bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 outline-none focus:ring-1 focus:ring-teal-400"
|
|
159
|
+
autoFocus
|
|
160
|
+
>
|
|
161
|
+
<option value="">-- select --</option>
|
|
162
|
+
{hint.suggestions?.map(s => <option key={s} value={s}>{s}</option>)}
|
|
163
|
+
</select>
|
|
164
|
+
) : (
|
|
165
|
+
<input
|
|
166
|
+
value={editValue}
|
|
167
|
+
onChange={e => setEditValue(e.target.value)}
|
|
168
|
+
onKeyDown={handleKeyDown}
|
|
169
|
+
placeholder={hint.placeholder}
|
|
170
|
+
className="flex-1 min-w-0 text-[11px] font-mono px-1.5 py-0.5 border border-teal-400 dark:border-teal-600 bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 outline-none focus:ring-1 focus:ring-teal-400 placeholder:text-zinc-400/50"
|
|
171
|
+
autoFocus
|
|
172
|
+
/>
|
|
173
|
+
)}
|
|
174
|
+
<button onClick={() => commitEdit()} className="p-0.5 text-green-600 hover:text-green-700 dark:text-green-400 shrink-0">
|
|
175
|
+
<Check className="h-3 w-3" />
|
|
176
|
+
</button>
|
|
177
|
+
<button onClick={cancelEdit} className="p-0.5 text-red-500 hover:text-red-600 dark:text-red-400 shrink-0">
|
|
178
|
+
<X className="h-3 w-3" />
|
|
179
|
+
</button>
|
|
180
|
+
</div>
|
|
181
|
+
{/* Suggestion chips for fields with common values */}
|
|
182
|
+
{hint.suggestions && !hint.isSelect && (
|
|
183
|
+
<div className="flex flex-wrap gap-1">
|
|
184
|
+
{hint.suggestions.map(s => (
|
|
185
|
+
<button
|
|
186
|
+
key={s}
|
|
187
|
+
onClick={() => selectSuggestion(s)}
|
|
188
|
+
className="text-[9px] font-mono px-1.5 py-0.5 border border-zinc-200 dark:border-zinc-700 text-zinc-600 dark:text-zinc-400 hover:border-teal-400 hover:text-teal-700 dark:hover:text-teal-400 hover:bg-teal-50 dark:hover:bg-teal-950/50 transition-colors"
|
|
189
|
+
>
|
|
190
|
+
{s}
|
|
191
|
+
</button>
|
|
192
|
+
))}
|
|
193
|
+
</div>
|
|
194
|
+
)}
|
|
195
|
+
{/* Help text */}
|
|
196
|
+
{hint.helpText && (
|
|
197
|
+
<span className="text-[9px] text-zinc-400 dark:text-zinc-500">{hint.helpText}</span>
|
|
198
|
+
)}
|
|
199
|
+
</div>
|
|
200
|
+
) : (
|
|
201
|
+
<>
|
|
202
|
+
<span
|
|
203
|
+
className={`text-[11px] font-mono tabular-nums break-all text-right ${
|
|
204
|
+
isMutated
|
|
205
|
+
? 'text-purple-700 dark:text-purple-300 font-semibold'
|
|
206
|
+
: 'text-teal-700 dark:text-teal-400'
|
|
207
|
+
}`}
|
|
208
|
+
title={displayValue}
|
|
209
|
+
>
|
|
210
|
+
{displayValue}
|
|
211
|
+
{suffix && <span className="text-zinc-400 dark:text-zinc-500 ml-0.5">{suffix}</span>}
|
|
212
|
+
</span>
|
|
213
|
+
{editable && !isComputed && (
|
|
214
|
+
<PenLine className="h-3 w-3 opacity-0 group-hover/row:opacity-100 transition-opacity text-zinc-400 shrink-0 mt-0.5" />
|
|
215
|
+
)}
|
|
216
|
+
</>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
{children}
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ── AngleRow: edit angle and auto-compute XAxisAbscissa/XAxisOrdinate ───
|
|
226
|
+
|
|
227
|
+
interface AngleRowProps {
|
|
228
|
+
angle: number | null;
|
|
229
|
+
editable?: boolean;
|
|
230
|
+
onAngleChange?: (abscissa: number, ordinate: number) => void;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function AngleRow({ angle, editable, onAngleChange }: AngleRowProps) {
|
|
234
|
+
const [editing, setEditing] = useState(false);
|
|
235
|
+
const [editValue, setEditValue] = useState('');
|
|
236
|
+
|
|
237
|
+
const startEdit = useCallback(() => {
|
|
238
|
+
if (!editable) return;
|
|
239
|
+
setEditValue(angle != null ? angle.toFixed(6) : '');
|
|
240
|
+
setEditing(true);
|
|
241
|
+
}, [angle, editable]);
|
|
242
|
+
|
|
243
|
+
const commitEdit = useCallback(() => {
|
|
244
|
+
if (!onAngleChange) return;
|
|
245
|
+
const deg = parseFloat(editValue.trim());
|
|
246
|
+
if (!Number.isFinite(deg)) return;
|
|
247
|
+
const rad = deg * (Math.PI / 180);
|
|
248
|
+
onAngleChange(Math.cos(rad), Math.sin(rad));
|
|
249
|
+
setEditing(false);
|
|
250
|
+
}, [editValue, onAngleChange]);
|
|
251
|
+
|
|
252
|
+
const cancelEdit = useCallback(() => setEditing(false), []);
|
|
253
|
+
|
|
254
|
+
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
255
|
+
if (e.key === 'Enter') commitEdit();
|
|
256
|
+
if (e.key === 'Escape') cancelEdit();
|
|
257
|
+
}, [commitEdit, cancelEdit]);
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<div
|
|
261
|
+
className={`flex items-start gap-2 px-3 py-1.5 min-w-0 ${editable ? 'cursor-pointer hover:bg-zinc-50 dark:hover:bg-zinc-900/50 group/row' : ''}`}
|
|
262
|
+
onClick={!editing ? startEdit : undefined}
|
|
263
|
+
>
|
|
264
|
+
<span className="text-[11px] text-zinc-500 dark:text-zinc-400 shrink-0 pt-0.5 flex items-center gap-0.5 min-w-[110px]">
|
|
265
|
+
<Tooltip>
|
|
266
|
+
<TooltipTrigger asChild>
|
|
267
|
+
<span className="text-[10px] text-teal-500">*</span>
|
|
268
|
+
</TooltipTrigger>
|
|
269
|
+
<TooltipContent>{editable ? 'Edit angle to auto-compute XAxisAbscissa/XAxisOrdinate' : 'Computed from XAxisAbscissa and XAxisOrdinate'}</TooltipContent>
|
|
270
|
+
</Tooltip>
|
|
271
|
+
Angle to Grid North
|
|
272
|
+
</span>
|
|
273
|
+
<div className="flex-1 flex items-start gap-1 min-w-0 justify-end">
|
|
274
|
+
{editing ? (
|
|
275
|
+
<div className="flex flex-col gap-1" onClick={e => e.stopPropagation()}>
|
|
276
|
+
<div className="flex items-center gap-1">
|
|
277
|
+
<input
|
|
278
|
+
value={editValue}
|
|
279
|
+
onChange={e => setEditValue(e.target.value)}
|
|
280
|
+
onKeyDown={handleKeyDown}
|
|
281
|
+
placeholder="0.0"
|
|
282
|
+
className="w-28 text-[11px] font-mono px-1.5 py-0.5 border border-teal-400 dark:border-teal-600 bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 outline-none focus:ring-1 focus:ring-teal-400 placeholder:text-zinc-400/50"
|
|
283
|
+
autoFocus
|
|
284
|
+
/>
|
|
285
|
+
<span className="text-[10px] text-zinc-400">deg</span>
|
|
286
|
+
<button onClick={commitEdit} className="p-0.5 text-green-600 hover:text-green-700 dark:text-green-400 shrink-0">
|
|
287
|
+
<Check className="h-3 w-3" />
|
|
288
|
+
</button>
|
|
289
|
+
<button onClick={cancelEdit} className="p-0.5 text-red-500 hover:text-red-600 dark:text-red-400 shrink-0">
|
|
290
|
+
<X className="h-3 w-3" />
|
|
291
|
+
</button>
|
|
292
|
+
</div>
|
|
293
|
+
<span className="text-[9px] text-zinc-400 dark:text-zinc-500">Sets XAxisAbscissa = cos(angle), XAxisOrdinate = sin(angle)</span>
|
|
294
|
+
</div>
|
|
295
|
+
) : (
|
|
296
|
+
<>
|
|
297
|
+
<span className="text-[11px] font-mono tabular-nums text-teal-700 dark:text-teal-400">
|
|
298
|
+
{angle != null ? parseFloat(angle.toFixed(6)) : '-'}
|
|
299
|
+
<span className="text-zinc-400 dark:text-zinc-500 ml-0.5">deg</span>
|
|
300
|
+
</span>
|
|
301
|
+
{editable && (
|
|
302
|
+
<PenLine className="h-3 w-3 opacity-0 group-hover/row:opacity-100 transition-opacity text-zinc-400 shrink-0 mt-0.5" />
|
|
303
|
+
)}
|
|
304
|
+
</>
|
|
305
|
+
)}
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ── Main Panel ─────────────────────────────────────────────────────────
|
|
312
|
+
|
|
313
|
+
export interface GeoreferencingPanelProps {
|
|
314
|
+
georef: GeoreferenceInfo | null;
|
|
315
|
+
modelId?: string;
|
|
316
|
+
enableEditing?: boolean;
|
|
317
|
+
schemaVersion?: string;
|
|
318
|
+
/** CoordinateInfo from the model's geometry (for map position calculation) */
|
|
319
|
+
coordinateInfo?: CoordinateInfo;
|
|
320
|
+
/** GeometryResult for KMZ export */
|
|
321
|
+
geometryResult?: GeometryResult | null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function GeoreferencingPanel({ georef, modelId, enableEditing, schemaVersion, coordinateInfo, geometryResult }: GeoreferencingPanelProps) {
|
|
325
|
+
const georefMutations = useViewerStore(s => s.georefMutations);
|
|
326
|
+
const setGeorefField = useViewerStore(s => s.setGeorefField);
|
|
327
|
+
const setGeorefFields = useViewerStore(s => s.setGeorefFields);
|
|
328
|
+
const cesiumEnabled = useViewerStore(s => s.cesiumEnabled);
|
|
329
|
+
const terrainClamp = useViewerStore(s => s.cesiumTerrainClamp);
|
|
330
|
+
const setCesiumTerrainClamp = useViewerStore(s => s.setCesiumTerrainClamp);
|
|
331
|
+
const cesiumTerrainHeight = useViewerStore(s => s.cesiumTerrainHeight);
|
|
332
|
+
const cesiumSourceModelId = useViewerStore(s => s.cesiumSourceModelId);
|
|
333
|
+
// Only show terrain actions when this panel's model is the one backing the Cesium overlay
|
|
334
|
+
const isActiveCesiumModel = !!modelId && modelId === cesiumSourceModelId;
|
|
335
|
+
const [crsOpen, setCrsOpen] = useState(false);
|
|
336
|
+
const [conversionOpen, setConversionOpen] = useState(false);
|
|
337
|
+
|
|
338
|
+
useViewerStore(s => s.mutationVersion);
|
|
339
|
+
|
|
340
|
+
const mutations = modelId ? georefMutations?.get(modelId) : undefined;
|
|
341
|
+
const supportsStandardGeoreferencing = !schemaVersion?.toUpperCase().includes('2X3');
|
|
342
|
+
|
|
343
|
+
const mergedCRS = useMemo((): ProjectedCRS | undefined => {
|
|
344
|
+
const base = georef?.projectedCRS;
|
|
345
|
+
const muts = mutations?.projectedCRS;
|
|
346
|
+
if (!base && !muts) return undefined;
|
|
347
|
+
return {
|
|
348
|
+
id: base?.id ?? 0,
|
|
349
|
+
name: muts?.name ?? base?.name ?? '',
|
|
350
|
+
description: muts?.description ?? base?.description,
|
|
351
|
+
geodeticDatum: muts?.geodeticDatum ?? base?.geodeticDatum,
|
|
352
|
+
verticalDatum: muts?.verticalDatum ?? base?.verticalDatum,
|
|
353
|
+
mapProjection: muts?.mapProjection ?? base?.mapProjection,
|
|
354
|
+
mapZone: muts?.mapZone ?? base?.mapZone,
|
|
355
|
+
mapUnit: muts?.mapUnit ?? base?.mapUnit,
|
|
356
|
+
};
|
|
357
|
+
}, [georef, mutations]);
|
|
358
|
+
|
|
359
|
+
const mergedConversion = useMemo((): MapConversion | undefined => {
|
|
360
|
+
const base = georef?.mapConversion;
|
|
361
|
+
const muts = mutations?.mapConversion;
|
|
362
|
+
if (!base && !muts) return undefined;
|
|
363
|
+
return {
|
|
364
|
+
id: base?.id ?? 0,
|
|
365
|
+
sourceCRS: base?.sourceCRS ?? 0,
|
|
366
|
+
targetCRS: base?.targetCRS ?? 0,
|
|
367
|
+
eastings: muts?.eastings ?? base?.eastings ?? 0,
|
|
368
|
+
northings: muts?.northings ?? base?.northings ?? 0,
|
|
369
|
+
orthogonalHeight: muts?.orthogonalHeight ?? base?.orthogonalHeight ?? 0,
|
|
370
|
+
xAxisAbscissa: muts?.xAxisAbscissa ?? base?.xAxisAbscissa,
|
|
371
|
+
xAxisOrdinate: muts?.xAxisOrdinate ?? base?.xAxisOrdinate,
|
|
372
|
+
scale: muts?.scale ?? base?.scale,
|
|
373
|
+
};
|
|
374
|
+
}, [georef, mutations]);
|
|
375
|
+
|
|
376
|
+
const angleToGridNorth = useMemo(() => {
|
|
377
|
+
return computeAngleToGridNorth(mergedConversion?.xAxisAbscissa, mergedConversion?.xAxisOrdinate);
|
|
378
|
+
}, [mergedConversion]);
|
|
379
|
+
|
|
380
|
+
const mapUnitSuffix = useMemo(() => {
|
|
381
|
+
const mapUnit = mergedCRS?.mapUnit?.toUpperCase();
|
|
382
|
+
if (!mapUnit) return 'm';
|
|
383
|
+
if (mapUnit.includes('US') && mapUnit.includes('FOOT')) return 'ftUS';
|
|
384
|
+
if (mapUnit.includes('FOOT') || mapUnit.includes('FEET')) return 'ft';
|
|
385
|
+
return 'm';
|
|
386
|
+
}, [mergedCRS?.mapUnit]);
|
|
387
|
+
|
|
388
|
+
// Convert meters to map units (Cesium always returns meters)
|
|
389
|
+
const metersToMapUnit = useCallback((meters: number): number => {
|
|
390
|
+
if (mapUnitSuffix === 'ftUS') return meters / 0.3048006096;
|
|
391
|
+
if (mapUnitSuffix === 'ft') return meters / 0.3048;
|
|
392
|
+
return meters; // already meters
|
|
393
|
+
}, [mapUnitSuffix]);
|
|
394
|
+
|
|
395
|
+
const isMutated = useCallback((entity: 'projectedCRS' | 'mapConversion', field: string): boolean => {
|
|
396
|
+
if (!mutations) return false;
|
|
397
|
+
const entityMuts = mutations[entity];
|
|
398
|
+
if (!entityMuts) return false;
|
|
399
|
+
return field in entityMuts;
|
|
400
|
+
}, [mutations]);
|
|
401
|
+
|
|
402
|
+
const handleSave = useCallback((entity: 'projectedCRS' | 'mapConversion', field: string, value: string | number) => {
|
|
403
|
+
if (!modelId || !setGeorefField) return;
|
|
404
|
+
const oldValue = entity === 'projectedCRS'
|
|
405
|
+
? georef?.projectedCRS?.[field as keyof ProjectedCRS]
|
|
406
|
+
: georef?.mapConversion?.[field as keyof MapConversion];
|
|
407
|
+
setGeorefField(modelId, entity, field, value, oldValue as string | number | undefined);
|
|
408
|
+
}, [modelId, setGeorefField, georef]);
|
|
409
|
+
|
|
410
|
+
// Handle angle edit: compute and set both XAxisAbscissa and XAxisOrdinate
|
|
411
|
+
const handleAngleChange = useCallback((abscissa: number, ordinate: number) => {
|
|
412
|
+
if (!modelId || !setGeorefFields) return;
|
|
413
|
+
setGeorefFields(modelId, 'mapConversion', [
|
|
414
|
+
{ field: 'xAxisAbscissa', value: abscissa, oldValue: georef?.mapConversion?.xAxisAbscissa },
|
|
415
|
+
{ field: 'xAxisOrdinate', value: ordinate, oldValue: georef?.mapConversion?.xAxisOrdinate },
|
|
416
|
+
]);
|
|
417
|
+
}, [modelId, setGeorefFields, georef]);
|
|
418
|
+
|
|
419
|
+
// Handle position picked from the map (reverse-projected easting/northing + optional terrain height)
|
|
420
|
+
const handleApplyPosition = useCallback((position: PickedPosition) => {
|
|
421
|
+
if (!modelId || !setGeorefFields) return;
|
|
422
|
+
const fields: Array<{ field: string; value: number; oldValue?: number }> = [
|
|
423
|
+
{ field: 'eastings', value: position.easting, oldValue: mergedConversion?.eastings },
|
|
424
|
+
{ field: 'northings', value: position.northing, oldValue: mergedConversion?.northings },
|
|
425
|
+
];
|
|
426
|
+
if (position.terrainHeight !== null) {
|
|
427
|
+
fields.push({
|
|
428
|
+
field: 'orthogonalHeight',
|
|
429
|
+
value: Math.round(position.terrainHeight * 10) / 10,
|
|
430
|
+
oldValue: mergedConversion?.orthogonalHeight,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
setGeorefFields(modelId, 'mapConversion', fields);
|
|
434
|
+
setConversionOpen(true);
|
|
435
|
+
}, [modelId, setGeorefFields, mergedConversion]);
|
|
436
|
+
|
|
437
|
+
const initializeMapConversionDefaults = useCallback(() => {
|
|
438
|
+
if (!modelId || !setGeorefFields) return;
|
|
439
|
+
setGeorefFields(modelId, 'mapConversion', [
|
|
440
|
+
{ field: 'eastings', value: georef?.mapConversion?.eastings ?? 0, oldValue: georef?.mapConversion?.eastings },
|
|
441
|
+
{ field: 'northings', value: georef?.mapConversion?.northings ?? 0, oldValue: georef?.mapConversion?.northings },
|
|
442
|
+
{ field: 'orthogonalHeight', value: georef?.mapConversion?.orthogonalHeight ?? 0, oldValue: georef?.mapConversion?.orthogonalHeight },
|
|
443
|
+
{ field: 'xAxisAbscissa', value: georef?.mapConversion?.xAxisAbscissa ?? 1, oldValue: georef?.mapConversion?.xAxisAbscissa },
|
|
444
|
+
{ field: 'xAxisOrdinate', value: georef?.mapConversion?.xAxisOrdinate ?? 0, oldValue: georef?.mapConversion?.xAxisOrdinate },
|
|
445
|
+
{ field: 'scale', value: georef?.mapConversion?.scale ?? 1, oldValue: georef?.mapConversion?.scale },
|
|
446
|
+
]);
|
|
447
|
+
setConversionOpen(true);
|
|
448
|
+
}, [modelId, setGeorefFields, georef]);
|
|
449
|
+
|
|
450
|
+
const handleEpsgSelect = useCallback((result: EpsgResult) => {
|
|
451
|
+
if (!modelId || !setGeorefFields) return;
|
|
452
|
+
const epsgName = `EPSG:${result.code}`;
|
|
453
|
+
const fieldUpdates: Array<{ field: string; value: string | number; oldValue?: string | number }> = [
|
|
454
|
+
{ field: 'name', value: epsgName, oldValue: georef?.projectedCRS?.name },
|
|
455
|
+
];
|
|
456
|
+
if (result.name) {
|
|
457
|
+
fieldUpdates.push({ field: 'description', value: result.name, oldValue: georef?.projectedCRS?.description });
|
|
458
|
+
}
|
|
459
|
+
if (result.datum) {
|
|
460
|
+
fieldUpdates.push({ field: 'geodeticDatum', value: result.datum, oldValue: georef?.projectedCRS?.geodeticDatum });
|
|
461
|
+
}
|
|
462
|
+
if (result.projection) {
|
|
463
|
+
fieldUpdates.push({ field: 'mapProjection', value: result.projection, oldValue: georef?.projectedCRS?.mapProjection });
|
|
464
|
+
}
|
|
465
|
+
if (result.unit) {
|
|
466
|
+
const unitUpper = result.unit.toUpperCase();
|
|
467
|
+
const mapUnit = unitUpper.includes('US') && (unitUpper.includes('SURVEY') || unitUpper.includes('FTUS'))
|
|
468
|
+
? 'US SURVEY FOOT'
|
|
469
|
+
: unitUpper.includes('METRE') || unitUpper.includes('METER')
|
|
470
|
+
? 'METRE'
|
|
471
|
+
: unitUpper.includes('FOOT') || unitUpper.includes('FEET')
|
|
472
|
+
? 'FOOT'
|
|
473
|
+
: result.unit;
|
|
474
|
+
fieldUpdates.push({ field: 'mapUnit', value: mapUnit, oldValue: georef?.projectedCRS?.mapUnit });
|
|
475
|
+
}
|
|
476
|
+
setGeorefFields(modelId, 'projectedCRS', fieldUpdates);
|
|
477
|
+
if (!georef?.mapConversion && !mutations?.mapConversion) {
|
|
478
|
+
initializeMapConversionDefaults();
|
|
479
|
+
}
|
|
480
|
+
setCrsOpen(true);
|
|
481
|
+
}, [modelId, setGeorefFields, georef, mutations, initializeMapConversionDefaults]);
|
|
482
|
+
|
|
483
|
+
const hasData = mergedCRS || mergedConversion;
|
|
484
|
+
const editable = enableEditing && !!modelId && supportsStandardGeoreferencing;
|
|
485
|
+
|
|
486
|
+
if (enableEditing && !supportsStandardGeoreferencing) {
|
|
487
|
+
return (
|
|
488
|
+
<div className="px-2 py-1.5 flex items-center gap-2">
|
|
489
|
+
<Globe className="h-3 w-3 text-zinc-400" />
|
|
490
|
+
<span className="text-[10px] text-zinc-500 dark:text-zinc-400">
|
|
491
|
+
Georeferencing editing requires IFC4 or newer. IFC2X3 does not support IfcProjectedCRS or IfcMapConversion.
|
|
492
|
+
</span>
|
|
493
|
+
</div>
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// When no georef data exists, show "Add Georeferencing" in edit mode
|
|
498
|
+
if (!hasData && !georef?.hasGeoreference) {
|
|
499
|
+
if (!editable) return null;
|
|
500
|
+
return (
|
|
501
|
+
<div className="px-2 py-1.5 flex items-center gap-2">
|
|
502
|
+
<Globe className="h-3 w-3 text-teal-500" />
|
|
503
|
+
<span className="text-[10px] text-zinc-500 dark:text-zinc-400 flex-1">No georeferencing</span>
|
|
504
|
+
<EpsgLookupDialog onSelect={handleEpsgSelect}>
|
|
505
|
+
<button 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 px-1.5 py-0.5 border border-teal-300/50 dark:border-teal-700/50 hover:bg-teal-50 dark:hover:bg-teal-950/50">
|
|
506
|
+
<Globe className="h-2.5 w-2.5" />
|
|
507
|
+
Add Georeferencing
|
|
508
|
+
</button>
|
|
509
|
+
</EpsgLookupDialog>
|
|
510
|
+
</div>
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return (
|
|
515
|
+
<div>
|
|
516
|
+
{/* CRS summary — always visible */}
|
|
517
|
+
<div className="px-2 py-1.5 flex items-center gap-2">
|
|
518
|
+
<Globe className="h-3 w-3 text-teal-500 shrink-0" />
|
|
519
|
+
{mergedCRS?.name && (
|
|
520
|
+
<span className="text-[10px] font-mono font-semibold text-teal-600 dark:text-teal-400">{mergedCRS.name}</span>
|
|
521
|
+
)}
|
|
522
|
+
{!mergedCRS?.name && (
|
|
523
|
+
<span className="text-[10px] text-zinc-500 dark:text-zinc-400">No projected CRS</span>
|
|
524
|
+
)}
|
|
525
|
+
{mergedCRS?.description && (
|
|
526
|
+
<span className="text-[10px] font-mono text-teal-500/60 truncate">{mergedCRS.description}</span>
|
|
527
|
+
)}
|
|
528
|
+
{editable && (
|
|
529
|
+
<EpsgLookupDialog onSelect={handleEpsgSelect}>
|
|
530
|
+
<button className="flex items-center gap-1 text-[9px] text-teal-500 hover:text-teal-700 dark:hover:text-teal-300 transition-colors ml-auto shrink-0">
|
|
531
|
+
<Search className="h-2.5 w-2.5" />
|
|
532
|
+
EPSG
|
|
533
|
+
</button>
|
|
534
|
+
</EpsgLookupDialog>
|
|
535
|
+
)}
|
|
536
|
+
</div>
|
|
537
|
+
|
|
538
|
+
{/* IfcProjectedCRS */}
|
|
539
|
+
{mergedCRS && (
|
|
540
|
+
<div>
|
|
541
|
+
<button
|
|
542
|
+
onClick={() => setCrsOpen(!crsOpen)}
|
|
543
|
+
className="flex items-center gap-2 w-full px-3 py-1.5 hover:bg-zinc-50 dark:hover:bg-zinc-900 text-left transition-colors border-b border-zinc-100 dark:border-zinc-900"
|
|
544
|
+
>
|
|
545
|
+
<ChevronRight className={`h-3 w-3 text-teal-500 shrink-0 transition-transform ${crsOpen ? 'rotate-90' : ''}`} />
|
|
546
|
+
<Globe className="h-3 w-3 text-teal-500 shrink-0" />
|
|
547
|
+
<span className="font-bold text-[11px] text-zinc-700 dark:text-zinc-300 uppercase tracking-wide flex-1 text-left">Projected CRS</span>
|
|
548
|
+
{!crsOpen && mergedCRS.name && (
|
|
549
|
+
<span className="text-[10px] font-mono text-teal-600/70 dark:text-teal-500/60 truncate max-w-[50%]">{mergedCRS.name}</span>
|
|
550
|
+
)}
|
|
551
|
+
</button>
|
|
552
|
+
{crsOpen && (
|
|
553
|
+
<div className="divide-y divide-zinc-100 dark:divide-zinc-900">
|
|
554
|
+
<GeorefRow label="Name" value={mergedCRS.name} editable={editable} isMutated={isMutated('projectedCRS', 'name')} fieldEntity="projectedCRS" fieldName="name" onSave={v => handleSave('projectedCRS', 'name', v)} />
|
|
555
|
+
<GeorefRow label="Description" value={mergedCRS.description} editable={editable} isMutated={isMutated('projectedCRS', 'description')} fieldEntity="projectedCRS" fieldName="description" onSave={v => handleSave('projectedCRS', 'description', v)} />
|
|
556
|
+
<GeorefRow label="GeodeticDatum" value={mergedCRS.geodeticDatum} editable={editable} isMutated={isMutated('projectedCRS', 'geodeticDatum')} fieldEntity="projectedCRS" fieldName="geodeticDatum" onSave={v => handleSave('projectedCRS', 'geodeticDatum', v)} />
|
|
557
|
+
<GeorefRow label="VerticalDatum" value={mergedCRS.verticalDatum} editable={editable} isMutated={isMutated('projectedCRS', 'verticalDatum')} fieldEntity="projectedCRS" fieldName="verticalDatum" onSave={v => handleSave('projectedCRS', 'verticalDatum', v)} />
|
|
558
|
+
<GeorefRow label="MapProjection" value={mergedCRS.mapProjection} editable={editable} isMutated={isMutated('projectedCRS', 'mapProjection')} fieldEntity="projectedCRS" fieldName="mapProjection" onSave={v => handleSave('projectedCRS', 'mapProjection', v)} />
|
|
559
|
+
<GeorefRow label="MapZone" value={mergedCRS.mapZone} editable={editable} isMutated={isMutated('projectedCRS', 'mapZone')} fieldEntity="projectedCRS" fieldName="mapZone" onSave={v => handleSave('projectedCRS', 'mapZone', v)} />
|
|
560
|
+
<GeorefRow label="MapUnit" value={mergedCRS.mapUnit} editable={editable} isMutated={isMutated('projectedCRS', 'mapUnit')} fieldEntity="projectedCRS" fieldName="mapUnit" onSave={v => handleSave('projectedCRS', 'mapUnit', v)} />
|
|
561
|
+
</div>
|
|
562
|
+
)}
|
|
563
|
+
</div>
|
|
564
|
+
)}
|
|
565
|
+
|
|
566
|
+
{!mergedCRS && editable && mergedConversion && (
|
|
567
|
+
<div className="px-3 py-2 border-b border-zinc-100 dark:border-zinc-900 flex items-center gap-2">
|
|
568
|
+
<span className="text-[10px] text-zinc-500 dark:text-zinc-400 flex-1">Coordinate operation exists, but projected CRS is missing.</span>
|
|
569
|
+
<EpsgLookupDialog onSelect={handleEpsgSelect}>
|
|
570
|
+
<button className="flex items-center gap-1 text-[9px] text-teal-500 hover:text-teal-700 dark:hover:text-teal-300 transition-colors shrink-0">
|
|
571
|
+
<Search className="h-2.5 w-2.5" />
|
|
572
|
+
Add CRS
|
|
573
|
+
</button>
|
|
574
|
+
</EpsgLookupDialog>
|
|
575
|
+
</div>
|
|
576
|
+
)}
|
|
577
|
+
|
|
578
|
+
{/* IfcMapConversion */}
|
|
579
|
+
{mergedConversion && (
|
|
580
|
+
<div>
|
|
581
|
+
<button
|
|
582
|
+
onClick={() => setConversionOpen(!conversionOpen)}
|
|
583
|
+
className="flex items-center gap-2 w-full px-3 py-1.5 hover:bg-zinc-50 dark:hover:bg-zinc-900 text-left transition-colors border-b border-zinc-100 dark:border-zinc-900"
|
|
584
|
+
>
|
|
585
|
+
<ChevronRight className={`h-3 w-3 text-teal-500 shrink-0 transition-transform ${conversionOpen ? 'rotate-90' : ''}`} />
|
|
586
|
+
<MapPin className="h-3 w-3 text-teal-500 shrink-0" />
|
|
587
|
+
<span className="font-bold text-[11px] text-zinc-700 dark:text-zinc-300 uppercase tracking-wide flex-1 text-left">Coordinate Operation</span>
|
|
588
|
+
{!conversionOpen && (
|
|
589
|
+
<span className="text-[10px] font-mono text-teal-600/70 dark:text-teal-500/60">
|
|
590
|
+
E {mergedConversion.eastings.toFixed(0)} N {mergedConversion.northings.toFixed(0)}
|
|
591
|
+
</span>
|
|
592
|
+
)}
|
|
593
|
+
</button>
|
|
594
|
+
{conversionOpen && (
|
|
595
|
+
<div className="divide-y divide-zinc-100 dark:divide-zinc-900">
|
|
596
|
+
<GeorefRow label="Type" value="IfcMapConversion" />
|
|
597
|
+
<GeorefRow label="Eastings" value={mergedConversion.eastings} suffix={mapUnitSuffix} isNumber editable={editable} isMutated={isMutated('mapConversion', 'eastings')} fieldEntity="mapConversion" fieldName="eastings" onSave={v => handleSave('mapConversion', 'eastings', v)} />
|
|
598
|
+
<GeorefRow label="Northings" value={mergedConversion.northings} suffix={mapUnitSuffix} isNumber editable={editable} isMutated={isMutated('mapConversion', 'northings')} fieldEntity="mapConversion" fieldName="northings" onSave={v => handleSave('mapConversion', 'northings', v)} />
|
|
599
|
+
<GeorefRow label="OrthogonalHeight" value={mergedConversion.orthogonalHeight} suffix={mapUnitSuffix} isNumber editable={editable} isMutated={isMutated('mapConversion', 'orthogonalHeight')} fieldEntity="mapConversion" fieldName="orthogonalHeight" onSave={v => handleSave('mapConversion', 'orthogonalHeight', v)}>
|
|
600
|
+
<TerrainHeightButton modelId={modelId} editable={editable} onApply={(h) => handleSave('mapConversion', 'orthogonalHeight', Math.round(metersToMapUnit(h) * 100) / 100)} />
|
|
601
|
+
</GeorefRow>
|
|
602
|
+
<GeorefRow label="XAxisAbscissa" value={mergedConversion.xAxisAbscissa} isNumber editable={editable} isMutated={isMutated('mapConversion', 'xAxisAbscissa')} fieldEntity="mapConversion" fieldName="xAxisAbscissa" onSave={v => handleSave('mapConversion', 'xAxisAbscissa', v)} />
|
|
603
|
+
<GeorefRow label="XAxisOrdinate" value={mergedConversion.xAxisOrdinate} isNumber editable={editable} isMutated={isMutated('mapConversion', 'xAxisOrdinate')} fieldEntity="mapConversion" fieldName="xAxisOrdinate" onSave={v => handleSave('mapConversion', 'xAxisOrdinate', v)} />
|
|
604
|
+
<AngleRow angle={angleToGridNorth} editable={editable} onAngleChange={handleAngleChange} />
|
|
605
|
+
<GeorefRow label="Scale" value={mergedConversion.scale} isNumber editable={editable} isMutated={isMutated('mapConversion', 'scale')} fieldEntity="mapConversion" fieldName="scale" onSave={v => handleSave('mapConversion', 'scale', v)} />
|
|
606
|
+
</div>
|
|
607
|
+
)}
|
|
608
|
+
</div>
|
|
609
|
+
)}
|
|
610
|
+
|
|
611
|
+
{!mergedConversion && editable && mergedCRS && (
|
|
612
|
+
<div className="px-3 py-2 border-b border-zinc-100 dark:border-zinc-900 flex items-center gap-2">
|
|
613
|
+
<span className="text-[10px] text-zinc-500 dark:text-zinc-400 flex-1">No coordinate operation. Add map coordinates, angle to grid north, and scale.</span>
|
|
614
|
+
<button
|
|
615
|
+
onClick={initializeMapConversionDefaults}
|
|
616
|
+
className="flex items-center gap-1 text-[9px] text-teal-500 hover:text-teal-700 dark:hover:text-teal-300 transition-colors shrink-0"
|
|
617
|
+
>
|
|
618
|
+
<MapPin className="h-2.5 w-2.5" />
|
|
619
|
+
Add Coordinates
|
|
620
|
+
</button>
|
|
621
|
+
</div>
|
|
622
|
+
)}
|
|
623
|
+
|
|
624
|
+
{/* Terrain clamp toggle — only when Cesium overlay is active */}
|
|
625
|
+
{cesiumEnabled && isActiveCesiumModel && mergedConversion && (
|
|
626
|
+
<div className="px-3 py-1.5 border-t border-zinc-100 dark:border-zinc-900 space-y-1">
|
|
627
|
+
<div className="flex items-center gap-2">
|
|
628
|
+
<Mountain className="h-3 w-3 text-teal-500 shrink-0" />
|
|
629
|
+
<label className="flex items-center gap-1.5 cursor-pointer flex-1">
|
|
630
|
+
<input
|
|
631
|
+
type="checkbox"
|
|
632
|
+
checked={terrainClamp}
|
|
633
|
+
onChange={(e) => setCesiumTerrainClamp(e.target.checked)}
|
|
634
|
+
className="accent-teal-500 h-3 w-3"
|
|
635
|
+
/>
|
|
636
|
+
<span className="text-[10px] text-zinc-600 dark:text-zinc-400">Clamp to terrain</span>
|
|
637
|
+
</label>
|
|
638
|
+
{cesiumTerrainHeight !== null ? (
|
|
639
|
+
<span className="text-[9px] font-mono text-teal-500">
|
|
640
|
+
{cesiumTerrainHeight.toFixed(1)} m
|
|
641
|
+
</span>
|
|
642
|
+
) : (
|
|
643
|
+
<span className="text-[9px] font-mono text-zinc-400">querying...</span>
|
|
644
|
+
)}
|
|
645
|
+
</div>
|
|
646
|
+
{cesiumTerrainHeight !== null && editable && modelId && (
|
|
647
|
+
<div className="flex items-center gap-1 ml-5">
|
|
648
|
+
<button
|
|
649
|
+
onClick={() => handleSave('mapConversion', 'orthogonalHeight', Math.round(metersToMapUnit(cesiumTerrainHeight) * 100) / 100)}
|
|
650
|
+
className="text-[9px] text-teal-500 hover:text-teal-700 dark:hover:text-teal-300 transition-colors flex items-center gap-0.5"
|
|
651
|
+
>
|
|
652
|
+
<Mountain className="h-2.5 w-2.5" />
|
|
653
|
+
Set OrthogonalHeight to {cesiumTerrainHeight.toFixed(1)} m
|
|
654
|
+
</button>
|
|
655
|
+
</div>
|
|
656
|
+
)}
|
|
657
|
+
</div>
|
|
658
|
+
)}
|
|
659
|
+
|
|
660
|
+
{/* Location minimap */}
|
|
661
|
+
<LocationMap
|
|
662
|
+
mapConversion={mergedConversion}
|
|
663
|
+
projectedCRS={mergedCRS}
|
|
664
|
+
coordinateInfo={coordinateInfo}
|
|
665
|
+
geometryResult={geometryResult}
|
|
666
|
+
editable={editable}
|
|
667
|
+
onApplyPosition={editable ? handleApplyPosition : undefined}
|
|
668
|
+
/>
|
|
669
|
+
</div>
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/** Small button to apply Cesium terrain height to OrthogonalHeight field */
|
|
674
|
+
function TerrainHeightButton({ modelId, editable, onApply }: {
|
|
675
|
+
modelId?: string;
|
|
676
|
+
editable?: boolean;
|
|
677
|
+
onApply: (height: number) => void;
|
|
678
|
+
}) {
|
|
679
|
+
const cesiumEnabled = useViewerStore(s => s.cesiumEnabled);
|
|
680
|
+
const terrainHeight = useViewerStore(s => s.cesiumTerrainHeight);
|
|
681
|
+
const sourceModelId = useViewerStore(s => s.cesiumSourceModelId);
|
|
682
|
+
|
|
683
|
+
// Only show when this panel's model is the active Cesium model
|
|
684
|
+
if (!cesiumEnabled || terrainHeight === null || !editable || !modelId || modelId !== sourceModelId) return null;
|
|
685
|
+
|
|
686
|
+
return (
|
|
687
|
+
<Tooltip>
|
|
688
|
+
<TooltipTrigger asChild>
|
|
689
|
+
<button
|
|
690
|
+
onClick={(e) => {
|
|
691
|
+
e.stopPropagation();
|
|
692
|
+
onApply(terrainHeight);
|
|
693
|
+
}}
|
|
694
|
+
className="flex items-center gap-0.5 text-[9px] text-teal-500 hover:text-teal-700 dark:hover:text-teal-300 transition-colors mt-0.5"
|
|
695
|
+
>
|
|
696
|
+
<Mountain className="h-2.5 w-2.5" />
|
|
697
|
+
<span>{terrainHeight.toFixed(1)} m</span>
|
|
698
|
+
</button>
|
|
699
|
+
</TooltipTrigger>
|
|
700
|
+
<TooltipContent>Set OrthogonalHeight to Cesium terrain elevation ({terrainHeight.toFixed(1)} m)</TooltipContent>
|
|
701
|
+
</Tooltip>
|
|
702
|
+
);
|
|
703
|
+
}
|