@cveo/dsh-digital-earth 0.0.0-stage → 1.0.0
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/CHANGELOG.md +19 -0
- package/LICENSE +21 -0
- package/NOTICE +13 -0
- package/README.en.md +124 -0
- package/README.md +124 -3
- package/client.js +2297 -0
- package/cordis.patch.yml +3 -0
- package/icon.svg +7 -0
- package/index.js +590 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +74 -6
- package/preview.png +0 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_0.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_1.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_10.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_11.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_12.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_13.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_14.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_15.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_16.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_17.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_18.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_19.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_2.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_20.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_21.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_22.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_23.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_24.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_25.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_26.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_27.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_3.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_4.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_5.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_6.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_7.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_8.json +1 -0
- package/vendor/cesium/Assets/IAU2006_XYS/IAU2006_XYS_9.json +1 -0
- package/vendor/cesium/Assets/Images/bing_maps_credit.png +0 -0
- package/vendor/cesium/Assets/Images/cesium_credit.png +0 -0
- package/vendor/cesium/Assets/Images/google_earth_credit.png +0 -0
- package/vendor/cesium/Assets/Images/ion-credit.png +0 -0
- package/vendor/cesium/Assets/Textures/LensFlare/DirtMask.jpg +0 -0
- package/vendor/cesium/Assets/Textures/LensFlare/StarBurst.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/0/0/0.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/0/1/0.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/1/0/0.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/1/0/1.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/1/1/0.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/1/1/1.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/1/2/0.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/1/2/1.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/1/3/0.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/1/3/1.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/0/0.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/0/1.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/0/2.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/0/3.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/1/0.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/1/1.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/1/2.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/1/3.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/2/0.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/2/1.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/2/2.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/2/3.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/3/0.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/3/1.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/3/2.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/3/3.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/4/0.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/4/1.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/4/2.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/4/3.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/5/0.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/5/1.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/5/2.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/5/3.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/6/0.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/6/1.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/6/2.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/6/3.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/7/0.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/7/1.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/7/2.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/2/7/3.jpg +0 -0
- package/vendor/cesium/Assets/Textures/NaturalEarthII/tilemapresource.xml +14 -0
- package/vendor/cesium/Assets/Textures/SkyBox/tycho2t3_80_mx.jpg +0 -0
- package/vendor/cesium/Assets/Textures/SkyBox/tycho2t3_80_my.jpg +0 -0
- package/vendor/cesium/Assets/Textures/SkyBox/tycho2t3_80_mz.jpg +0 -0
- package/vendor/cesium/Assets/Textures/SkyBox/tycho2t3_80_px.jpg +0 -0
- package/vendor/cesium/Assets/Textures/SkyBox/tycho2t3_80_py.jpg +0 -0
- package/vendor/cesium/Assets/Textures/SkyBox/tycho2t3_80_pz.jpg +0 -0
- package/vendor/cesium/Assets/Textures/maki/airfield.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/airport.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/alcohol-shop.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/america-football.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/art-gallery.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/bakery.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/bank.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/bar.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/baseball.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/basketball.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/beer.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/bicycle.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/building.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/bus.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/cafe.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/camera.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/campsite.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/car.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/cemetery.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/cesium.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/chemist.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/cinema.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/circle-stroked.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/circle.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/city.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/clothing-store.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/college.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/commercial.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/cricket.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/cross.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/dam.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/danger.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/disability.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/dog-park.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/embassy.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/emergency-telephone.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/entrance.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/farm.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/fast-food.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/ferry.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/fire-station.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/fuel.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/garden.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/gift.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/golf.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/grocery.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/hairdresser.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/harbor.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/heart.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/heliport.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/hospital.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/ice-cream.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/industrial.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/land-use.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/laundry.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/library.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/lighthouse.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/lodging.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/logging.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/london-underground.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/marker-stroked.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/marker.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/minefield.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/mobilephone.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/monument.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/museum.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/music.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/oil-well.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/park.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/park2.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/parking-garage.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/parking.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/pharmacy.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/pitch.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/place-of-worship.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/playground.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/police.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/polling-place.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/post.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/prison.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/rail-above.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/rail-light.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/rail-metro.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/rail-underground.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/rail.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/religious-christian.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/religious-jewish.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/religious-muslim.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/restaurant.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/roadblock.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/rocket.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/school.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/scooter.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/shop.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/skiing.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/slaughterhouse.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/soccer.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/square-stroked.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/square.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/star-stroked.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/star.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/suitcase.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/swimming.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/telephone.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/tennis.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/theatre.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/toilets.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/town-hall.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/town.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/triangle-stroked.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/triangle.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/village.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/warehouse.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/waste-basket.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/water.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/wetland.png +0 -0
- package/vendor/cesium/Assets/Textures/maki/zoo.png +0 -0
- package/vendor/cesium/Assets/Textures/moonSmall.jpg +0 -0
- package/vendor/cesium/Assets/Textures/pin.svg +1 -0
- package/vendor/cesium/Assets/Textures/waterNormals.jpg +0 -0
- package/vendor/cesium/Assets/Textures/waterNormalsSmall.jpg +0 -0
- package/vendor/cesium/Assets/approximateTerrainHeights.json +1 -0
- package/vendor/cesium/Cesium.js +18348 -0
- package/vendor/cesium/LICENSE.md +1068 -0
- package/vendor/cesium/ThirdParty/Workers/package.json +1 -0
- package/vendor/cesium/ThirdParty/Workers/zip-web-worker.js +1 -0
- package/vendor/cesium/ThirdParty/basis_transcoder.wasm +0 -0
- package/vendor/cesium/ThirdParty/draco_decoder.wasm +0 -0
- package/vendor/cesium/ThirdParty/google-earth-dbroot-parser.js +1 -0
- package/vendor/cesium/ThirdParty/wasm_splats_bg.wasm +0 -0
- package/vendor/cesium/ThirdParty/zip-module.wasm +0 -0
- package/vendor/cesium/Widgets/Animation/Animation.css +127 -0
- package/vendor/cesium/Widgets/Animation/lighter.css +70 -0
- package/vendor/cesium/Widgets/BaseLayerPicker/BaseLayerPicker.css +108 -0
- package/vendor/cesium/Widgets/BaseLayerPicker/lighter.css +22 -0
- package/vendor/cesium/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.css +102 -0
- package/vendor/cesium/Widgets/CesiumInspector/CesiumInspector.css +113 -0
- package/vendor/cesium/Widgets/CesiumWidget/CesiumWidget.css +119 -0
- package/vendor/cesium/Widgets/CesiumWidget/lighter.css +14 -0
- package/vendor/cesium/Widgets/FullscreenButton/FullscreenButton.css +8 -0
- package/vendor/cesium/Widgets/Geocoder/Geocoder.css +70 -0
- package/vendor/cesium/Widgets/Geocoder/lighter.css +17 -0
- package/vendor/cesium/Widgets/I3SBuildingSceneLayerExplorer/I3SBuildingSceneLayerExplorer.css +27 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/ArcGisMapServiceWorldHillshade.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/ArcGisMapServiceWorldImagery.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/ArcGisMapServiceWorldOcean.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/azureAerial.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/azureRoads.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/bingAerial.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/bingAerialLabels.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/bingRoads.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/blueMarble.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/earthAtNight.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/googleContour.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/googleRoadmap.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/googleSatellite.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/googleSatelliteLabels.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/mapQuestOpenStreetMap.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/mapboxSatellite.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/mapboxStreets.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/mapboxTerrain.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/naturalEarthII.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/openStreetMap.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/sentinel-2.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/stadiaAlidadeSmooth.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/stadiaAlidadeSmoothDark.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/stamenToner.png +0 -0
- package/vendor/cesium/Widgets/Images/ImageryProviders/stamenWatercolor.png +0 -0
- package/vendor/cesium/Widgets/Images/NavigationHelp/Mouse.svg +84 -0
- package/vendor/cesium/Widgets/Images/NavigationHelp/MouseLeft.svg +76 -0
- package/vendor/cesium/Widgets/Images/NavigationHelp/MouseMiddle.svg +76 -0
- package/vendor/cesium/Widgets/Images/NavigationHelp/MouseRight.svg +76 -0
- package/vendor/cesium/Widgets/Images/NavigationHelp/Touch.svg +120 -0
- package/vendor/cesium/Widgets/Images/NavigationHelp/TouchDrag.svg +129 -0
- package/vendor/cesium/Widgets/Images/NavigationHelp/TouchRotate.svg +76 -0
- package/vendor/cesium/Widgets/Images/NavigationHelp/TouchTilt.svg +135 -0
- package/vendor/cesium/Widgets/Images/NavigationHelp/TouchZoom.svg +74 -0
- package/vendor/cesium/Widgets/Images/TerrainProviders/CesiumWorldTerrain.png +0 -0
- package/vendor/cesium/Widgets/Images/TerrainProviders/Ellipsoid.png +0 -0
- package/vendor/cesium/Widgets/Images/TimelineIcons.png +0 -0
- package/vendor/cesium/Widgets/Images/info-loading.gif +0 -0
- package/vendor/cesium/Widgets/InfoBox/InfoBox.css +92 -0
- package/vendor/cesium/Widgets/InfoBox/InfoBoxDescription.css +178 -0
- package/vendor/cesium/Widgets/NavigationHelpButton/NavigationHelpButton.css +93 -0
- package/vendor/cesium/Widgets/NavigationHelpButton/lighter.css +38 -0
- package/vendor/cesium/Widgets/PerformanceWatchdog/PerformanceWatchdog.css +15 -0
- package/vendor/cesium/Widgets/ProjectionPicker/ProjectionPicker.css +38 -0
- package/vendor/cesium/Widgets/SceneModePicker/SceneModePicker.css +56 -0
- package/vendor/cesium/Widgets/SelectionIndicator/SelectionIndicator.css +20 -0
- package/vendor/cesium/Widgets/Timeline/Timeline.css +103 -0
- package/vendor/cesium/Widgets/Timeline/lighter.css +23 -0
- package/vendor/cesium/Widgets/VRButton/VRButton.css +8 -0
- package/vendor/cesium/Widgets/Viewer/Viewer.css +107 -0
- package/vendor/cesium/Widgets/VoxelInspector/VoxelInspector.css +16 -0
- package/vendor/cesium/Widgets/lighter.css +237 -0
- package/vendor/cesium/Widgets/lighterShared.css +46 -0
- package/vendor/cesium/Widgets/shared.css +103 -0
- package/vendor/cesium/Widgets/widgets.css +1342 -0
- package/vendor/cesium/Workers/chunk-3XZ3CL24.js +26 -0
- package/vendor/cesium/Workers/chunk-674HC7OA.js +26 -0
- package/vendor/cesium/Workers/chunk-6LO6MWK3.js +26 -0
- package/vendor/cesium/Workers/chunk-F6M5ZWO4.js +29 -0
- package/vendor/cesium/Workers/chunk-FZYUOFLY.js +70 -0
- package/vendor/cesium/Workers/chunk-GACOCTFW.js +26 -0
- package/vendor/cesium/Workers/chunk-MDI2T7LJ.js +27 -0
- package/vendor/cesium/Workers/chunk-P6X455E3.js +27 -0
- package/vendor/cesium/Workers/chunk-SOIWLLGP.js +26 -0
- package/vendor/cesium/Workers/chunk-UBKFZE5C.js +26 -0
- package/vendor/cesium/Workers/combineGeometry.js +26 -0
- package/vendor/cesium/Workers/createBoxGeometry.js +26 -0
- package/vendor/cesium/Workers/createBoxOutlineGeometry.js +26 -0
- package/vendor/cesium/Workers/createCircleGeometry.js +26 -0
- package/vendor/cesium/Workers/createCircleOutlineGeometry.js +26 -0
- package/vendor/cesium/Workers/createCoplanarPolygonGeometry.js +26 -0
- package/vendor/cesium/Workers/createCoplanarPolygonOutlineGeometry.js +26 -0
- package/vendor/cesium/Workers/createCorridorGeometry.js +26 -0
- package/vendor/cesium/Workers/createCorridorOutlineGeometry.js +26 -0
- package/vendor/cesium/Workers/createCylinderGeometry.js +26 -0
- package/vendor/cesium/Workers/createCylinderOutlineGeometry.js +26 -0
- package/vendor/cesium/Workers/createEllipseGeometry.js +26 -0
- package/vendor/cesium/Workers/createEllipseOutlineGeometry.js +26 -0
- package/vendor/cesium/Workers/createEllipsoidGeometry.js +26 -0
- package/vendor/cesium/Workers/createEllipsoidOutlineGeometry.js +26 -0
- package/vendor/cesium/Workers/createFrustumGeometry.js +26 -0
- package/vendor/cesium/Workers/createFrustumOutlineGeometry.js +26 -0
- package/vendor/cesium/Workers/createGeometry.js +26 -0
- package/vendor/cesium/Workers/createGroundPolylineGeometry.js +26 -0
- package/vendor/cesium/Workers/createPlaneGeometry.js +26 -0
- package/vendor/cesium/Workers/createPlaneOutlineGeometry.js +26 -0
- package/vendor/cesium/Workers/createPolygonGeometry.js +26 -0
- package/vendor/cesium/Workers/createPolygonOutlineGeometry.js +26 -0
- package/vendor/cesium/Workers/createPolylineGeometry.js +26 -0
- package/vendor/cesium/Workers/createPolylineVolumeGeometry.js +26 -0
- package/vendor/cesium/Workers/createPolylineVolumeOutlineGeometry.js +26 -0
- package/vendor/cesium/Workers/createRectangleGeometry.js +26 -0
- package/vendor/cesium/Workers/createRectangleOutlineGeometry.js +26 -0
- package/vendor/cesium/Workers/createSimplePolylineGeometry.js +26 -0
- package/vendor/cesium/Workers/createSphereGeometry.js +26 -0
- package/vendor/cesium/Workers/createSphereOutlineGeometry.js +26 -0
- package/vendor/cesium/Workers/createTaskProcessorWorker.js +26 -0
- package/vendor/cesium/Workers/createVectorTileClampedPolylines.js +26 -0
- package/vendor/cesium/Workers/createVectorTileGeometries.js +26 -0
- package/vendor/cesium/Workers/createVectorTilePoints.js +26 -0
- package/vendor/cesium/Workers/createVectorTilePolygons.js +26 -0
- package/vendor/cesium/Workers/createVectorTilePolylines.js +26 -0
- package/vendor/cesium/Workers/createVerticesFromCesium3DTilesTerrain.js +26 -0
- package/vendor/cesium/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +26 -0
- package/vendor/cesium/Workers/createVerticesFromHeightmap.js +26 -0
- package/vendor/cesium/Workers/createVerticesFromQuantizedTerrainMesh.js +26 -0
- package/vendor/cesium/Workers/createWallGeometry.js +26 -0
- package/vendor/cesium/Workers/createWallOutlineGeometry.js +26 -0
- package/vendor/cesium/Workers/decodeDraco.js +26 -0
- package/vendor/cesium/Workers/decodeGoogleEarthEnterprisePacket.js +26 -0
- package/vendor/cesium/Workers/decodeI3S.js +26 -0
- package/vendor/cesium/Workers/gaussianSplatSorter.js +26 -0
- package/vendor/cesium/Workers/gaussianSplatTextureGenerator.js +26 -0
- package/vendor/cesium/Workers/incrementallyBuildTerrainPicker.js +26 -0
- package/vendor/cesium/Workers/transcodeKTX2.js +56 -0
- package/vendor/cesium/Workers/transferTypedArrayTest.js +26 -0
- package/vendor/cesium/Workers/upsampleQuantizedTerrainMesh.js +26 -0
- package/vendor/cesium/Workers/upsampleVerticesFromCesium3DTilesTerrain.js +26 -0
- package/vendor/cesium/index.cjs +18348 -0
- package/vendor/cesium/index.js +18348 -0
package/cordis.patch.yml
ADDED
package/icon.svg
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" fill="none">
|
|
2
|
+
<circle cx="30" cy="30" r="20" stroke="#4f8cff" stroke-width="3"/>
|
|
3
|
+
<ellipse cx="30" cy="30" rx="8.5" ry="20" stroke="#4f8cff" stroke-width="2.2" opacity=".85"/>
|
|
4
|
+
<path d="M10.5 30h39M13.5 20.5h33M13.5 39.5h33" stroke="#4f8cff" stroke-width="2" opacity=".6"/>
|
|
5
|
+
<path d="M48 34l9 5v10l-9 5-9-5V39z" fill="#4f8cff"/>
|
|
6
|
+
<path d="M39 39l9 5 9-5M48 44v10" stroke="#eaf2ff" stroke-width="1.6" opacity=".9"/>
|
|
7
|
+
</svg>
|
package/index.js
ADDED
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host half of the 3D viewer plugin.
|
|
3
|
+
*
|
|
4
|
+
* Owns everything the browser must not decide on its own:
|
|
5
|
+
* - a per-process token, so only this UI page can read local files;
|
|
6
|
+
* - same-origin static routes for the vendored CesiumJS build and for the
|
|
7
|
+
* tileset files under a directory the user picked in the browser;
|
|
8
|
+
* - a directory scan for `tileset.json` and a small CORS-bypass proxy for
|
|
9
|
+
* online tilesets whose server does not send CORS headers.
|
|
10
|
+
*/
|
|
11
|
+
import { randomBytes } from 'node:crypto'
|
|
12
|
+
import { createReadStream, promises as fs } from 'node:fs'
|
|
13
|
+
import { homedir } from 'node:os'
|
|
14
|
+
import { extname, join, resolve, sep } from 'node:path'
|
|
15
|
+
import { Readable } from 'node:stream'
|
|
16
|
+
import { fileURLToPath } from 'node:url'
|
|
17
|
+
|
|
18
|
+
const ROOT_DIR = fileURLToPath(new URL('.', import.meta.url))
|
|
19
|
+
const CESIUM_DIR = join(ROOT_DIR, 'vendor', 'cesium')
|
|
20
|
+
const TOKEN = randomBytes(12).toString('base64url')
|
|
21
|
+
const API = '/api/3dviewer'
|
|
22
|
+
// Prefix routes are registered without the trailing slash: the router matches
|
|
23
|
+
// `pathname === prefix || pathname.startsWith(prefix + '/')`.
|
|
24
|
+
const CESIUM_ROUTE = `${API}/cesium`
|
|
25
|
+
const TILES_ROUTE = `${API}/tiles`
|
|
26
|
+
const REMOTE_ROUTE = `${API}/remote`
|
|
27
|
+
const SOURCES_ROUTE = `${API}/sources`
|
|
28
|
+
const MAX_SCAN_ITEMS = 200
|
|
29
|
+
const MAX_SCAN_DEPTH = 4
|
|
30
|
+
// The source catalog is small JSON owned by this plugin; keep it outside the
|
|
31
|
+
// browser so it survives port changes (localStorage is per-origin) and restarts.
|
|
32
|
+
const STATE_DIR = join(process.env.DSH_HOME || join(homedir(), '.dsh'), 'digital-earth')
|
|
33
|
+
const SOURCES_FILE = join(STATE_DIR, 'sources.json')
|
|
34
|
+
const MAX_SOURCES_BODY = 128 * 1024
|
|
35
|
+
const MAX_IMAGERY_SOURCES = 20
|
|
36
|
+
const MAX_TERRAIN_SOURCES = 10
|
|
37
|
+
const IMAGERY_TYPES = new Set(['xyz', 'arcgis', 'wms'])
|
|
38
|
+
const TERRAIN_TYPES = new Set(['arcgis', 'cesium'])
|
|
39
|
+
const THEME_MODES = new Set(['auto', 'light', 'dark'])
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Built-in catalogs, used for a first run and for the "restore defaults"
|
|
43
|
+
* action. Names are brand names rather than translated UI copy, so the same
|
|
44
|
+
* value reads correctly in both locales.
|
|
45
|
+
*/
|
|
46
|
+
const DEFAULT_SOURCES = {
|
|
47
|
+
imagery: [
|
|
48
|
+
{
|
|
49
|
+
id: 'esri-world-imagery',
|
|
50
|
+
name: 'Esri World Imagery',
|
|
51
|
+
type: 'xyz',
|
|
52
|
+
url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
|
|
53
|
+
maximumLevel: 19,
|
|
54
|
+
credit: 'Esri, Maxar, Earthstar Geographics, and the GIS User Community',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'esri-world-topo',
|
|
58
|
+
name: 'Esri World Topographic',
|
|
59
|
+
type: 'arcgis',
|
|
60
|
+
url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer',
|
|
61
|
+
credit: 'Esri, HERE, Garmin, FAO, NOAA, USGS, and the GIS User Community',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'esri-world-street',
|
|
65
|
+
name: 'Esri World Street Map',
|
|
66
|
+
type: 'arcgis',
|
|
67
|
+
url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer',
|
|
68
|
+
credit: 'Esri, HERE, Garmin, USGS, NGA, and the GIS User Community',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'esri-light-gray',
|
|
72
|
+
name: 'Esri Light Gray Canvas',
|
|
73
|
+
type: 'arcgis',
|
|
74
|
+
url: 'https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer',
|
|
75
|
+
credit: 'Esri, HERE, Garmin, and the GIS User Community',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 'esri-shaded-relief',
|
|
79
|
+
name: 'Esri World Shaded Relief',
|
|
80
|
+
type: 'arcgis',
|
|
81
|
+
url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Shaded_Relief/MapServer',
|
|
82
|
+
credit: 'Esri, and the GIS User Community',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'esri-world-ocean',
|
|
86
|
+
name: 'Esri World Ocean',
|
|
87
|
+
type: 'arcgis',
|
|
88
|
+
url: 'https://server.arcgisonline.com/ArcGIS/rest/services/Ocean/World_Ocean_Base/MapServer',
|
|
89
|
+
credit: 'Esri, GEBCO, NOAA, and the GIS User Community',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: 'esri-world-hillshade',
|
|
93
|
+
name: 'Esri World Hillshade',
|
|
94
|
+
type: 'arcgis',
|
|
95
|
+
url: 'https://server.arcgisonline.com/ArcGIS/rest/services/Elevation/World_Hillshade/MapServer',
|
|
96
|
+
credit: 'Esri, USGS, and the GIS User Community',
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
terrain: [
|
|
100
|
+
{
|
|
101
|
+
id: 'arcgis-world-elevation',
|
|
102
|
+
name: 'ArcGIS WorldElevation3D',
|
|
103
|
+
type: 'arcgis',
|
|
104
|
+
url: 'https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer',
|
|
105
|
+
credit: 'Esri, CGIAR, USGS',
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
settings: { theme: 'auto', imagery: 'esri-world-imagery', terrain: null },
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const MIME = {
|
|
112
|
+
'.json': 'application/json; charset=utf-8',
|
|
113
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
114
|
+
'.mjs': 'text/javascript; charset=utf-8',
|
|
115
|
+
'.css': 'text/css; charset=utf-8',
|
|
116
|
+
'.html': 'text/html; charset=utf-8',
|
|
117
|
+
'.txt': 'text/plain; charset=utf-8',
|
|
118
|
+
'.svg': 'image/svg+xml',
|
|
119
|
+
'.png': 'image/png',
|
|
120
|
+
'.jpg': 'image/jpeg',
|
|
121
|
+
'.jpeg': 'image/jpeg',
|
|
122
|
+
'.webp': 'image/webp',
|
|
123
|
+
'.gif': 'image/gif',
|
|
124
|
+
'.glb': 'model/gltf-binary',
|
|
125
|
+
'.gltf': 'model/gltf+json',
|
|
126
|
+
'.bin': 'application/octet-stream',
|
|
127
|
+
'.ktx2': 'image/ktx2',
|
|
128
|
+
'.wasm': 'application/wasm',
|
|
129
|
+
'.terrain': 'application/octet-stream',
|
|
130
|
+
'.b3dm': 'application/octet-stream',
|
|
131
|
+
'.pnts': 'application/octet-stream',
|
|
132
|
+
'.i3dm': 'application/octet-stream',
|
|
133
|
+
'.cmpt': 'application/octet-stream',
|
|
134
|
+
'.spz': 'application/octet-stream',
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const toB64 = (value) => Buffer.from(value, 'utf8').toString('base64url')
|
|
138
|
+
const fromB64 = (value) => Buffer.from(value, 'base64url').toString('utf8')
|
|
139
|
+
const segments = (value) => value.split('/').filter((part) => part.length > 0)
|
|
140
|
+
const decodeSegment = (value) => {
|
|
141
|
+
try {
|
|
142
|
+
return decodeURIComponent(value)
|
|
143
|
+
} catch {
|
|
144
|
+
return null
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function sendText(res, status, text, type = 'text/plain; charset=utf-8') {
|
|
149
|
+
res.writeHead(status, { 'content-type': type, 'cache-control': 'no-store' })
|
|
150
|
+
res.end(text)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function sendJson(res, status, data) {
|
|
154
|
+
res.writeHead(status, {
|
|
155
|
+
'content-type': 'application/json; charset=utf-8',
|
|
156
|
+
'cache-control': 'no-store',
|
|
157
|
+
})
|
|
158
|
+
res.end(JSON.stringify(data))
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const cloneDefaults = () => JSON.parse(JSON.stringify(DEFAULT_SOURCES))
|
|
162
|
+
|
|
163
|
+
const cleanText = (value, max) => (typeof value === 'string' ? value.trim().slice(0, max) : '')
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Accept only absolute http(s) URLs. Imagery templates legitimately contain
|
|
167
|
+
* placeholders such as `{z}` or `{s}`, so those are neutralised before parsing
|
|
168
|
+
* and the original text is what gets stored.
|
|
169
|
+
*/
|
|
170
|
+
function cleanUrl(value) {
|
|
171
|
+
const text = cleanText(value, 2048)
|
|
172
|
+
if (text === '') return ''
|
|
173
|
+
try {
|
|
174
|
+
const parsed = new URL(text.replace(/\{[^}]*\}/g, '0'))
|
|
175
|
+
return parsed.protocol === 'http:' || parsed.protocol === 'https:' ? text : ''
|
|
176
|
+
} catch {
|
|
177
|
+
return ''
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function cleanId(value, fallback) {
|
|
182
|
+
const text = typeof value === 'string' ? value.trim().toLowerCase() : ''
|
|
183
|
+
return /^[a-z0-9][a-z0-9-]{0,39}$/.test(text) ? text : fallback
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function cleanLevel(value) {
|
|
187
|
+
return Number.isInteger(value) && value >= 0 && value <= 25 ? value : undefined
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function normalizeSource(kind, raw, index) {
|
|
191
|
+
if (raw === null || typeof raw !== 'object') return null
|
|
192
|
+
const type = cleanText(raw.type, 16).toLowerCase()
|
|
193
|
+
if (!(kind === 'imagery' ? IMAGERY_TYPES : TERRAIN_TYPES).has(type)) return null
|
|
194
|
+
const url = cleanUrl(raw.url)
|
|
195
|
+
if (url === '') return null
|
|
196
|
+
const source = {
|
|
197
|
+
id: cleanId(raw.id, `${kind}-${index + 1}`),
|
|
198
|
+
name: cleanText(raw.name, 60) || `${kind} ${index + 1}`,
|
|
199
|
+
type,
|
|
200
|
+
url,
|
|
201
|
+
}
|
|
202
|
+
const credit = cleanText(raw.credit, 200)
|
|
203
|
+
if (credit !== '') source.credit = credit
|
|
204
|
+
if (kind === 'imagery') {
|
|
205
|
+
const maximumLevel = cleanLevel(raw.maximumLevel)
|
|
206
|
+
if (maximumLevel !== undefined) source.maximumLevel = maximumLevel
|
|
207
|
+
if (raw.tilingScheme === 'geographic' || raw.tilingScheme === 'web-mercator') source.tilingScheme = raw.tilingScheme
|
|
208
|
+
if (raw.reverseY === true) source.reverseY = true
|
|
209
|
+
const subdomains = Array.isArray(raw.subdomains)
|
|
210
|
+
? raw.subdomains.map((item) => cleanText(item, 8)).filter((item) => item !== '').slice(0, 8)
|
|
211
|
+
: []
|
|
212
|
+
if (subdomains.length > 0) source.subdomains = subdomains
|
|
213
|
+
const layers = cleanText(raw.layers, 200)
|
|
214
|
+
if (layers !== '') source.layers = layers
|
|
215
|
+
} else {
|
|
216
|
+
if (raw.requestVertexNormals === true) source.requestVertexNormals = true
|
|
217
|
+
if (raw.requestWaterMask === true) source.requestWaterMask = true
|
|
218
|
+
}
|
|
219
|
+
return source
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function normalizeSourceList(kind, raw, fallback) {
|
|
223
|
+
if (!Array.isArray(raw)) return fallback.map((source) => ({ ...source }))
|
|
224
|
+
const limit = kind === 'imagery' ? MAX_IMAGERY_SOURCES : MAX_TERRAIN_SOURCES
|
|
225
|
+
const seen = new Set()
|
|
226
|
+
const sources = []
|
|
227
|
+
for (const [index, item] of raw.slice(0, limit).entries()) {
|
|
228
|
+
const source = normalizeSource(kind, item, index)
|
|
229
|
+
if (source === null) continue
|
|
230
|
+
while (seen.has(source.id)) source.id = `${source.id}-${sources.length + 1}`
|
|
231
|
+
seen.add(source.id)
|
|
232
|
+
sources.push(source)
|
|
233
|
+
}
|
|
234
|
+
return sources
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Pick a stored selection id, but only when it still exists in the list. */
|
|
238
|
+
function cleanSelection(value, sources) {
|
|
239
|
+
if (typeof value !== 'string') return null
|
|
240
|
+
return sources.some((source) => source.id === value) ? value : null
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Append built-in presets that the user has not seen yet (id-based, keeps order). */
|
|
244
|
+
function withBuiltins(list, builtins, limit) {
|
|
245
|
+
const seen = new Set(list.map((source) => source.id))
|
|
246
|
+
const merged = [...list]
|
|
247
|
+
for (const source of builtins) {
|
|
248
|
+
if (merged.length >= limit) break
|
|
249
|
+
if (seen.has(source.id)) continue
|
|
250
|
+
merged.push({ ...source })
|
|
251
|
+
}
|
|
252
|
+
return merged
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Turn arbitrary request JSON into the catalog persisted on disk. */
|
|
256
|
+
function normalizeConfig(raw) {
|
|
257
|
+
const input = raw !== null && typeof raw === 'object' ? raw : {}
|
|
258
|
+
const theme = THEME_MODES.has(input.settings?.theme) ? input.settings.theme : 'auto'
|
|
259
|
+
const imagery = withBuiltins(
|
|
260
|
+
normalizeSourceList('imagery', input.imagery, DEFAULT_SOURCES.imagery),
|
|
261
|
+
DEFAULT_SOURCES.imagery,
|
|
262
|
+
MAX_IMAGERY_SOURCES,
|
|
263
|
+
)
|
|
264
|
+
const terrain = withBuiltins(
|
|
265
|
+
normalizeSourceList('terrain', input.terrain, DEFAULT_SOURCES.terrain),
|
|
266
|
+
DEFAULT_SOURCES.terrain,
|
|
267
|
+
MAX_TERRAIN_SOURCES,
|
|
268
|
+
)
|
|
269
|
+
// A missing selection (older file, first run) falls back to the default choice;
|
|
270
|
+
// an explicit `null` means "basemap/terrain deliberately off" and is preserved.
|
|
271
|
+
const selection = (value, list, fallback) =>
|
|
272
|
+
value === undefined ? cleanSelection(fallback, list) : cleanSelection(value, list)
|
|
273
|
+
return {
|
|
274
|
+
imagery,
|
|
275
|
+
terrain,
|
|
276
|
+
settings: {
|
|
277
|
+
theme,
|
|
278
|
+
imagery: selection(input.settings?.imagery, imagery, DEFAULT_SOURCES.settings.imagery),
|
|
279
|
+
terrain: selection(input.settings?.terrain, terrain, DEFAULT_SOURCES.settings.terrain),
|
|
280
|
+
},
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async function readSources() {
|
|
285
|
+
let text
|
|
286
|
+
try {
|
|
287
|
+
text = await fs.readFile(SOURCES_FILE, 'utf8')
|
|
288
|
+
} catch {
|
|
289
|
+
return cloneDefaults()
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
return normalizeConfig(JSON.parse(text))
|
|
293
|
+
} catch {
|
|
294
|
+
return cloneDefaults()
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function writeSources(config) {
|
|
299
|
+
await fs.mkdir(STATE_DIR, { recursive: true })
|
|
300
|
+
const temporary = `${SOURCES_FILE}.tmp`
|
|
301
|
+
await fs.writeFile(temporary, `${JSON.stringify(config, null, 2)}\n`, 'utf8')
|
|
302
|
+
await fs.rename(temporary, SOURCES_FILE)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Hosts the proxy must never reach: loopback, private ranges and link-local. */
|
|
306
|
+
function blockedHost(origin) {
|
|
307
|
+
let hostname
|
|
308
|
+
try {
|
|
309
|
+
hostname = new URL(origin).hostname.toLowerCase()
|
|
310
|
+
} catch {
|
|
311
|
+
return true
|
|
312
|
+
}
|
|
313
|
+
if (hostname.startsWith('[')) return true
|
|
314
|
+
if (hostname === 'localhost' || hostname.endsWith('.localhost')) return true
|
|
315
|
+
if (hostname.endsWith('.local') || hostname.endsWith('.internal')) return true
|
|
316
|
+
const parts = hostname.split('.').map((part) => Number(part))
|
|
317
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part))) return false
|
|
318
|
+
const [a, b] = parts
|
|
319
|
+
return a === 0 || a === 10 || a === 127 || (a === 169 && b === 254) || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168) || a >= 224
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function within(root, target) {
|
|
323
|
+
const prefix = root.endsWith(sep) ? root : root + sep
|
|
324
|
+
return target === root || target.startsWith(prefix)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Percent-encoded pathname of a request; the router hands route handlers only (req, res). */
|
|
328
|
+
function requestPath(req) {
|
|
329
|
+
try {
|
|
330
|
+
return new URL(req.url ?? '/', 'http://localhost').pathname
|
|
331
|
+
} catch {
|
|
332
|
+
return '/'
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** The raw path below a prefix route, or `null` when the request is not below it. */
|
|
337
|
+
function subPath(pathname, prefix) {
|
|
338
|
+
if (pathname === prefix) return ''
|
|
339
|
+
if (pathname.startsWith(`${prefix}/`)) return pathname.slice(prefix.length + 1)
|
|
340
|
+
return null
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function serveFile(req, res, root, relPath, cacheControl) {
|
|
344
|
+
if (relPath === '') return sendText(res, 404, 'not found')
|
|
345
|
+
const target = resolve(root, relPath)
|
|
346
|
+
if (!within(root, target)) return sendText(res, 403, 'forbidden')
|
|
347
|
+
let stat
|
|
348
|
+
try {
|
|
349
|
+
stat = await fs.stat(target)
|
|
350
|
+
} catch {
|
|
351
|
+
return sendText(res, 404, 'not found')
|
|
352
|
+
}
|
|
353
|
+
if (!stat.isFile()) return sendText(res, 404, 'not found')
|
|
354
|
+
res.writeHead(200, {
|
|
355
|
+
'content-type': MIME[extname(target).toLowerCase()] ?? 'application/octet-stream',
|
|
356
|
+
'content-length': String(stat.size),
|
|
357
|
+
'cache-control': cacheControl,
|
|
358
|
+
})
|
|
359
|
+
if (req.method === 'HEAD') {
|
|
360
|
+
res.end()
|
|
361
|
+
return
|
|
362
|
+
}
|
|
363
|
+
createReadStream(target)
|
|
364
|
+
.on('error', () => res.destroy())
|
|
365
|
+
.pipe(res)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function metaHandler(req, res) {
|
|
369
|
+
sendJson(res, 200, {
|
|
370
|
+
token: TOKEN,
|
|
371
|
+
cesiumBase: `${API}/cesium/`,
|
|
372
|
+
tilesBase: `${API}/tiles/${TOKEN}/`,
|
|
373
|
+
scanUrl: `${API}/scan`,
|
|
374
|
+
remoteBase: `${API}/remote/${TOKEN}/`,
|
|
375
|
+
sourcesUrl: `${SOURCES_ROUTE}/${TOKEN}`,
|
|
376
|
+
})
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** GET: the current catalog plus the built-ins the UI can restore. */
|
|
380
|
+
async function sourcesHandler(req, res) {
|
|
381
|
+
sendJson(res, 200, { config: await readSources(), defaults: cloneDefaults() })
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/** PUT: replace the catalog, normalised so a malformed entry cannot reach Cesium. */
|
|
385
|
+
async function saveSourcesHandler(req, res) {
|
|
386
|
+
let body = ''
|
|
387
|
+
try {
|
|
388
|
+
for await (const chunk of req) {
|
|
389
|
+
body += chunk
|
|
390
|
+
if (body.length > MAX_SOURCES_BODY) {
|
|
391
|
+
req.destroy()
|
|
392
|
+
return sendText(res, 413, 'payload too large')
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
} catch {
|
|
396
|
+
return sendText(res, 400, 'bad request')
|
|
397
|
+
}
|
|
398
|
+
let parsed
|
|
399
|
+
try {
|
|
400
|
+
parsed = JSON.parse(body)
|
|
401
|
+
} catch {
|
|
402
|
+
return sendText(res, 400, 'invalid json')
|
|
403
|
+
}
|
|
404
|
+
const config = normalizeConfig(parsed)
|
|
405
|
+
try {
|
|
406
|
+
await writeSources(config)
|
|
407
|
+
} catch (error) {
|
|
408
|
+
return sendText(res, 500, `save failed: ${error instanceof Error ? error.message : String(error)}`)
|
|
409
|
+
}
|
|
410
|
+
sendJson(res, 200, { config })
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function cesiumHandler(req, res) {
|
|
414
|
+
const raw = subPath(requestPath(req), CESIUM_ROUTE)
|
|
415
|
+
if (raw === null || raw === '') return sendText(res, 404, 'not found')
|
|
416
|
+
const relPath = decodeSegment(raw)
|
|
417
|
+
if (relPath === null) return sendText(res, 400, 'bad path')
|
|
418
|
+
serveFile(req, res, resolve(CESIUM_DIR), relPath, 'public, max-age=86400')
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function tilesHandler(req, res, allowedDirs) {
|
|
422
|
+
const raw = subPath(requestPath(req), TILES_ROUTE)
|
|
423
|
+
if (raw === null) return sendText(res, 403, 'forbidden')
|
|
424
|
+
const parts = segments(raw)
|
|
425
|
+
if (parts[0] !== TOKEN || parts.length < 2) return sendText(res, 403, 'forbidden')
|
|
426
|
+
const dir = fromB64(parts[1])
|
|
427
|
+
// Only absolute Windows or POSIX directories, and only ones the user opened.
|
|
428
|
+
if (!/^([a-zA-Z]:[\\/]|\\\\|\/)/.test(dir) || !allowedDirs.has(resolve(dir))) {
|
|
429
|
+
return sendText(res, 403, 'forbidden')
|
|
430
|
+
}
|
|
431
|
+
const relParts = []
|
|
432
|
+
for (const part of parts.slice(2)) {
|
|
433
|
+
const decoded = decodeSegment(part)
|
|
434
|
+
if (decoded === null || decoded.includes('\0') || decoded === '..') {
|
|
435
|
+
return sendText(res, 403, 'forbidden')
|
|
436
|
+
}
|
|
437
|
+
relParts.push(decoded)
|
|
438
|
+
}
|
|
439
|
+
serveFile(req, res, resolve(dir), relParts.join(sep), 'public, max-age=60')
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
async function scanHandler(req, res, allowedDirs) {
|
|
443
|
+
let parsed
|
|
444
|
+
try {
|
|
445
|
+
parsed = new URL(req.url, 'http://localhost')
|
|
446
|
+
} catch {
|
|
447
|
+
return sendText(res, 400, 'bad request')
|
|
448
|
+
}
|
|
449
|
+
const dir = parsed.searchParams.get('dir')
|
|
450
|
+
if (parsed.searchParams.get('token') !== TOKEN || !dir) return sendText(res, 403, 'forbidden')
|
|
451
|
+
allowedDirs.add(resolve(dir))
|
|
452
|
+
const files = await findTilesets(dir)
|
|
453
|
+
const items = files.map((file) => {
|
|
454
|
+
const rel = file
|
|
455
|
+
.slice(dir.length)
|
|
456
|
+
.replace(/^[\\/]+/, '')
|
|
457
|
+
.split(/[\\/]/)
|
|
458
|
+
.map(encodeURIComponent)
|
|
459
|
+
.join('/')
|
|
460
|
+
return {
|
|
461
|
+
name: file.slice(dir.length).replace(/^[\\/]+/, ''),
|
|
462
|
+
url: `${API}/tiles/${TOKEN}/${encodeURIComponent(toB64(dir))}/${rel}`,
|
|
463
|
+
}
|
|
464
|
+
})
|
|
465
|
+
sendJson(res, 200, { items })
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async function findTilesets(dir) {
|
|
469
|
+
const found = []
|
|
470
|
+
async function walk(current, depth) {
|
|
471
|
+
if (found.length >= MAX_SCAN_ITEMS) return
|
|
472
|
+
let entries
|
|
473
|
+
try {
|
|
474
|
+
entries = await fs.readdir(current, { withFileTypes: true })
|
|
475
|
+
} catch {
|
|
476
|
+
return
|
|
477
|
+
}
|
|
478
|
+
for (const entry of entries) {
|
|
479
|
+
if (found.length >= MAX_SCAN_ITEMS) return
|
|
480
|
+
const full = join(current, entry.name)
|
|
481
|
+
if (entry.isDirectory()) {
|
|
482
|
+
if (depth < MAX_SCAN_DEPTH) await walk(full, depth + 1)
|
|
483
|
+
} else if (entry.name.toLowerCase() === 'tileset.json') {
|
|
484
|
+
found.push(full)
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
await walk(dir, 0)
|
|
489
|
+
return found
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async function remoteHandler(req, res) {
|
|
493
|
+
const raw = subPath(requestPath(req), REMOTE_ROUTE)
|
|
494
|
+
if (raw === null) return sendText(res, 403, 'forbidden')
|
|
495
|
+
const parts = segments(raw)
|
|
496
|
+
if (parts[0] !== TOKEN || parts.length < 2) return sendText(res, 403, 'forbidden')
|
|
497
|
+
const origin = fromB64(parts[1])
|
|
498
|
+
if (!/^https?:\/\/[^/]+$/i.test(origin) || blockedHost(origin)) return sendText(res, 403, 'forbidden')
|
|
499
|
+
const relParts = []
|
|
500
|
+
for (const part of parts.slice(2)) {
|
|
501
|
+
const decoded = decodeSegment(part)
|
|
502
|
+
if (decoded === null || decoded.includes('\0')) return sendText(res, 403, 'forbidden')
|
|
503
|
+
relParts.push(decoded)
|
|
504
|
+
}
|
|
505
|
+
const target = `${origin}/${relParts.join('/')}`
|
|
506
|
+
try {
|
|
507
|
+
const upstream = await fetch(target, { redirect: 'follow' })
|
|
508
|
+
if (!upstream.ok || upstream.body === null) {
|
|
509
|
+
return sendText(res, upstream.status === 200 ? 502 : upstream.status, `upstream ${upstream.status}`)
|
|
510
|
+
}
|
|
511
|
+
res.writeHead(200, {
|
|
512
|
+
'content-type': upstream.headers.get('content-type') ?? 'application/octet-stream',
|
|
513
|
+
'cache-control': 'no-store',
|
|
514
|
+
})
|
|
515
|
+
Readable.fromWeb(upstream.body).pipe(res)
|
|
516
|
+
} catch (error) {
|
|
517
|
+
sendText(res, 502, `proxy failed: ${error instanceof Error ? error.message : String(error)}`)
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
export const inject = ['webServer', 'connection']
|
|
522
|
+
|
|
523
|
+
export function apply(ctx) {
|
|
524
|
+
// Directories this page opened through `/scan`; tile files are served only from
|
|
525
|
+
// them, so a token holder cannot read arbitrary paths on the machine.
|
|
526
|
+
const allowedDirs = new Set()
|
|
527
|
+
|
|
528
|
+
// The host web server has no auth or origin policy of its own, so every route
|
|
529
|
+
// below asks the DSH Connection service to reject foreign or unauthenticated
|
|
530
|
+
// requests, and fails closed whenever that answer is doubtful.
|
|
531
|
+
const rejection = (req) => {
|
|
532
|
+
const headers = req.headers ?? {}
|
|
533
|
+
const site = headers['sec-fetch-site']
|
|
534
|
+
if (site === 'cross-site') return 403
|
|
535
|
+
const origin = headers.origin
|
|
536
|
+
if (typeof origin === 'string' && origin !== 'null' && headers.host) {
|
|
537
|
+
try {
|
|
538
|
+
if (new URL(origin).host !== headers.host) return 403
|
|
539
|
+
} catch {
|
|
540
|
+
return 403
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
let connection
|
|
544
|
+
try {
|
|
545
|
+
connection = ctx.get('connection') ?? ctx.connection
|
|
546
|
+
} catch {
|
|
547
|
+
connection = null
|
|
548
|
+
}
|
|
549
|
+
if (connection && typeof connection.requestRejection === 'function') {
|
|
550
|
+
try {
|
|
551
|
+
const code = connection.requestRejection(req)
|
|
552
|
+
return code === undefined || code === null || code === false ? undefined : typeof code === 'number' ? code : 403
|
|
553
|
+
} catch {
|
|
554
|
+
return 403
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
// Older hosts without that fence: only this page's own requests may pass.
|
|
558
|
+
return site === 'same-origin' || site === 'none' ? undefined : 403
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
const register = (kind, path, handler) =>
|
|
562
|
+
ctx.webServer.register({
|
|
563
|
+
kind,
|
|
564
|
+
path,
|
|
565
|
+
handler(req, res) {
|
|
566
|
+
const code = rejection(req)
|
|
567
|
+
if (code) return sendText(res, code, 'forbidden')
|
|
568
|
+
return handler(req, res)
|
|
569
|
+
},
|
|
570
|
+
})
|
|
571
|
+
|
|
572
|
+
ctx.effect(() => {
|
|
573
|
+
const disposers = [
|
|
574
|
+
register('exact', `${API}/meta`, metaHandler),
|
|
575
|
+
register('prefix', CESIUM_ROUTE, cesiumHandler),
|
|
576
|
+
register('prefix', TILES_ROUTE, (req, res) => tilesHandler(req, res, allowedDirs)),
|
|
577
|
+
register('prefix', REMOTE_ROUTE, remoteHandler),
|
|
578
|
+
register('exact', `${API}/scan`, (req, res) => scanHandler(req, res, allowedDirs)),
|
|
579
|
+
register('exact', `${SOURCES_ROUTE}/${TOKEN}`, (req, res) =>
|
|
580
|
+
req.method === 'GET' ? sourcesHandler(req, res) : req.method === 'PUT' ? saveSourcesHandler(req, res) : sendText(res, 405, 'method not allowed'),
|
|
581
|
+
),
|
|
582
|
+
]
|
|
583
|
+
return () => {
|
|
584
|
+
for (const dispose of disposers) {
|
|
585
|
+
if (typeof dispose === 'function') dispose()
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}, 'digital-earth api routes')
|
|
589
|
+
}
|
|
590
|
+
|
package/locale/en.json
ADDED