├── .gitignore
├── .gitlab-ci.yml
├── .idea
├── .gitignore
├── codeStyles
│ └── codeStyleConfig.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── jsLibraryMappings.xml
├── jsLinters
│ └── eslint.xml
├── misc.xml
├── modules.xml
├── prettier.xml
├── vcmap-core.iml
└── vcs.xml
├── .npmrc
├── .prettierignore
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── build
├── .eslintrc
├── postBuild.js
└── postinstall.js
├── documentation
├── VcsLayer.png
├── VcsLayer.uxf
├── clipping.md
├── editor.md
├── interaction.md
├── layers.md
├── maps.md
├── navigation.md
├── renderScreenshot.md
├── style.md
├── vcsApp.md
├── vcsModule.md
├── vcsTemplate.md
├── vectorClusterGroup.md
└── vectorProperties.md
├── index.ts
├── jsconfig.json
├── package-lock.json
├── package.json
├── src
├── category
│ ├── category.ts
│ └── categoryCollection.ts
├── cesium
│ ├── cesium.d.ts
│ ├── cesium3DTileFeature.ts
│ ├── cesium3DTilePointFeature.ts
│ ├── cesiumVcsCameraPrimitive.ts
│ ├── clippingPolygon.ts
│ ├── clippingPolygonCollection.ts
│ ├── entity.ts
│ └── wallpaperMaterial.js
├── classRegistry.ts
├── featureProvider
│ ├── abstractFeatureProvider.ts
│ ├── featureProviderSymbols.ts
│ ├── tileProviderFeatureProvider.ts
│ └── wmsFeatureProvider.ts
├── global.d.ts
├── interaction
│ ├── abstractInteraction.ts
│ ├── coordinateAtPixel.ts
│ ├── eventHandler.ts
│ ├── featureAtPixelInteraction.ts
│ ├── featureProviderInteraction.ts
│ ├── interactionChain.ts
│ └── interactionType.ts
├── layer
│ ├── cesium
│ │ ├── cesiumTilesetCesiumImpl.ts
│ │ ├── dataSourceCesiumImpl.ts
│ │ ├── openStreetMapCesiumImpl.ts
│ │ ├── rasterLayerCesiumImpl.ts
│ │ ├── resourceHelper.ts
│ │ ├── singleImageCesiumImpl.ts
│ │ ├── sourceVectorContextSync.ts
│ │ ├── terrainCesiumImpl.ts
│ │ ├── tmsCesiumImpl.ts
│ │ ├── vcsTile
│ │ │ ├── vcsChildTile.ts
│ │ │ ├── vcsDebugTile.ts
│ │ │ ├── vcsNoDataTile.ts
│ │ │ ├── vcsQuadtreeTileProvider.ts
│ │ │ ├── vcsTileHelpers.ts
│ │ │ └── vcsVectorTile.ts
│ │ ├── vectorCesiumImpl.ts
│ │ ├── vectorContext.ts
│ │ ├── vectorRasterTileCesiumImpl.ts
│ │ ├── vectorTileCesiumImpl.ts
│ │ ├── vectorTileImageryProvider.ts
│ │ ├── wmsCesiumImpl.ts
│ │ └── wmtsCesiumImpl.ts
│ ├── cesiumTilesetLayer.ts
│ ├── czmlLayer.ts
│ ├── dataSourceLayer.ts
│ ├── featureLayer.ts
│ ├── featureStoreFeatureVisibility.ts
│ ├── featureStoreLayer.ts
│ ├── featureStoreLayerChanges.ts
│ ├── featureStoreLayerState.ts
│ ├── featureVisibility.ts
│ ├── flatGeobufHelpers.ts
│ ├── flatGeobufLayer.ts
│ ├── geojsonHelpers.ts
│ ├── geojsonLayer.ts
│ ├── globalHider.ts
│ ├── layer.ts
│ ├── layerImplementation.ts
│ ├── layerState.ts
│ ├── layerSymbols.ts
│ ├── oblique
│ │ ├── layerObliqueImpl.ts
│ │ ├── obliqueHelpers.ts
│ │ ├── sourceObliqueSync.ts
│ │ └── vectorObliqueImpl.ts
│ ├── openStreetMapLayer.ts
│ ├── openlayers
│ │ ├── layerOpenlayersImpl.ts
│ │ ├── loadFunctionHelpers.ts
│ │ ├── openStreetMapOpenlayersImpl.ts
│ │ ├── rasterLayerOpenlayersImpl.ts
│ │ ├── singleImageOpenlayersImpl.ts
│ │ ├── tileDebugOpenlayersImpl.ts
│ │ ├── tmsOpenlayersImpl.ts
│ │ ├── vectorOpenlayersImpl.ts
│ │ ├── vectorTileOpenlayersImpl.ts
│ │ ├── wmsOpenlayersImpl.ts
│ │ └── wmtsOpenlayersImpl.ts
│ ├── pointCloudLayer.ts
│ ├── rasterLayer.ts
│ ├── singleImageLayer.ts
│ ├── terrainHelpers.ts
│ ├── terrainLayer.ts
│ ├── tileLoadedHelper.ts
│ ├── tileProvider
│ │ ├── flatGeobufTileProvider.ts
│ │ ├── mvtTileProvider.ts
│ │ ├── staticFeatureTileProvider.ts
│ │ ├── staticGeojsonTileProvider.ts
│ │ ├── tileProvider.ts
│ │ └── urlTemplateTileProvider.ts
│ ├── tmsLayer.ts
│ ├── vectorHelpers.ts
│ ├── vectorLayer.ts
│ ├── vectorProperties.ts
│ ├── vectorSymbols.ts
│ ├── vectorTileLayer.ts
│ ├── wfsLayer.ts
│ ├── wmsHelpers.ts
│ ├── wmsLayer.ts
│ └── wmtsLayer.ts
├── map
│ ├── baseOLMap.ts
│ ├── cameraLimiter.ts
│ ├── cesiumMap.ts
│ ├── mapState.ts
│ ├── navigation
│ │ ├── cameraHelper.ts
│ │ ├── cesiumNavigation.ts
│ │ ├── controller
│ │ │ ├── controller.ts
│ │ │ ├── controllerInput.ts
│ │ │ └── keyboardController.ts
│ │ ├── easingHelper.ts
│ │ ├── navigation.ts
│ │ ├── navigationImpl.ts
│ │ ├── obliqueNavigation.ts
│ │ ├── openlayersNavigation.ts
│ │ └── viewHelper.ts
│ ├── obliqueMap.ts
│ ├── openlayersMap.ts
│ └── vcsMap.ts
├── moduleIdSymbol.ts
├── oblique
│ ├── defaultObliqueCollection.ts
│ ├── helpers.ts
│ ├── obliqueCollection.ts
│ ├── obliqueDataSet.ts
│ ├── obliqueImage.ts
│ ├── obliqueImageMeta.ts
│ ├── obliqueProvider.ts
│ ├── obliqueView.ts
│ ├── obliqueViewDirection.ts
│ └── parseImageJson.ts
├── ol
│ ├── feature.ts
│ ├── geojson.d.ts
│ ├── geom
│ │ ├── circle.ts
│ │ └── geometryCollection.js
│ ├── ol.d.ts
│ ├── render
│ │ └── canvas
│ │ │ └── canvasTileRenderer.js
│ └── source
│ │ ├── ClusterEnhancedVectorSource.ts
│ │ └── VcsCluster.ts
├── overrideClassRegistry.ts
├── style
│ ├── arcStyle.ts
│ ├── arrowStyle.ts
│ ├── declarativeStyleItem.ts
│ ├── modelFill.ts
│ ├── shapesCategory.ts
│ ├── styleFactory.ts
│ ├── styleHelpers.ts
│ ├── styleItem.ts
│ ├── vectorStyleItem.ts
│ └── writeStyle.ts
├── util
│ ├── clipping
│ │ ├── clippingObject.ts
│ │ ├── clippingObjectManager.ts
│ │ ├── clippingPlaneHelper.ts
│ │ ├── clippingPolygonHelper.ts
│ │ ├── clippingPolygonObject.ts
│ │ └── clippingPolygonObjectCollection.ts
│ ├── collection.ts
│ ├── displayQuality
│ │ └── displayQuality.ts
│ ├── editor
│ │ ├── createFeatureSession.ts
│ │ ├── editFeaturesSession.ts
│ │ ├── editGeometrySession.ts
│ │ ├── editorHelpers.ts
│ │ ├── editorSessionHelpers.ts
│ │ ├── editorSymbols.ts
│ │ ├── interactions
│ │ │ ├── createBBoxInteraction.ts
│ │ │ ├── createCircleInteraction.ts
│ │ │ ├── createLineStringInteraction.ts
│ │ │ ├── createPointInteraction.ts
│ │ │ ├── createPolygonInteraction.ts
│ │ │ ├── creationSnapping.ts
│ │ │ ├── editFeaturesMouseOverInteraction.ts
│ │ │ ├── editGeometryMouseOverInteraction.ts
│ │ │ ├── ensureHandlerSelectionInteraction.ts
│ │ │ ├── insertVertexInteraction.ts
│ │ │ ├── layerSnapping.ts
│ │ │ ├── mapInteractionController.ts
│ │ │ ├── removeVertexInteraction.ts
│ │ │ ├── rightClickInteraction.ts
│ │ │ ├── segmentLengthInteraction.ts
│ │ │ ├── selectFeatureMouseOverInteraction.ts
│ │ │ ├── selectMultiFeatureInteraction.ts
│ │ │ ├── selectSingleFeatureInteraction.ts
│ │ │ ├── translateVertexInteraction.ts
│ │ │ └── translationSnapping.ts
│ │ ├── selectFeaturesSession.ts
│ │ ├── snappingHelpers.ts
│ │ ├── transformation
│ │ │ ├── create2DHandlers.ts
│ │ │ ├── create3DHandlers.ts
│ │ │ ├── extrudeInteraction.ts
│ │ │ ├── rotateInteraction.ts
│ │ │ ├── scaleInteraction.ts
│ │ │ ├── transformationHandler.ts
│ │ │ ├── transformationTypes.ts
│ │ │ └── translateInteraction.ts
│ │ └── validateGeoemetry.ts
│ ├── exclusiveManager.ts
│ ├── extent.ts
│ ├── featureconverter
│ │ ├── arcToCesium.ts
│ │ ├── circleToCesium.ts
│ │ ├── clampedPrimitive.ts
│ │ ├── convert.ts
│ │ ├── extent3D.ts
│ │ ├── lineStringToCesium.ts
│ │ ├── pointHelpers.ts
│ │ ├── pointToCesium.ts
│ │ ├── polygonToCesium.ts
│ │ ├── storeyHelpers.ts
│ │ ├── vectorGeometryFactory.ts
│ │ └── vectorHeightInfo.ts
│ ├── fetch.ts
│ ├── flight
│ │ ├── flightAnchor.ts
│ │ ├── flightCollection.ts
│ │ ├── flightHelpers.ts
│ │ ├── flightInstance.ts
│ │ ├── flightPlayer.ts
│ │ └── flightVisualizer.ts
│ ├── geometryHelpers.ts
│ ├── hiddenObjects.ts
│ ├── indexedCollection.ts
│ ├── isMobile.ts
│ ├── layerCollection.ts
│ ├── locale.ts
│ ├── mapCollection.ts
│ ├── math.ts
│ ├── overrideCollection.ts
│ ├── projection.ts
│ ├── renderScreenshot.ts
│ ├── rotation.ts
│ ├── urlHelpers.ts
│ ├── vcsTemplate.ts
│ └── viewpoint.ts
├── vcsApp.ts
├── vcsEvent.ts
├── vcsModule.ts
├── vcsModuleHelpers.ts
├── vcsObject.ts
└── vectorCluster
│ ├── vectorClusterCesiumContext.ts
│ ├── vectorClusterGroup.ts
│ ├── vectorClusterGroupCesiumImpl.ts
│ ├── vectorClusterGroupCollection.ts
│ ├── vectorClusterGroupImpl.ts
│ ├── vectorClusterGroupObliqueImpl.ts
│ ├── vectorClusterGroupOpenlayersImpl.ts
│ ├── vectorClusterStyleItem.ts
│ └── vectorClusterSymbols.ts
├── tests
├── .eslintrc
├── data
│ ├── dynamicPointCzml.json
│ ├── oblique
│ │ ├── imageData
│ │ │ ├── imagev34.json
│ │ │ ├── imagev35.json
│ │ │ └── imagev35PerImageSize.json
│ │ └── tiledImageData
│ │ │ ├── 12
│ │ │ ├── 2199
│ │ │ │ ├── 1342.json
│ │ │ │ ├── 1343.json
│ │ │ │ └── 1344.json
│ │ │ ├── 2200
│ │ │ │ ├── 1342.json
│ │ │ │ ├── 1343.json
│ │ │ │ └── 1344.json
│ │ │ └── 2201
│ │ │ │ ├── 1342.json
│ │ │ │ ├── 1343.json
│ │ │ │ └── 1344.json
│ │ │ └── image.json
│ ├── terrain
│ │ ├── 13
│ │ │ ├── 8800
│ │ │ │ ├── 6485.terrain
│ │ │ │ └── 6486.terrain
│ │ │ └── 8801
│ │ │ │ ├── 6485.terrain
│ │ │ │ └── 6486.terrain
│ │ └── layer.json
│ ├── testGeoJSON.json
│ ├── tile.pbf
│ └── wgs84Points.fgb
├── setup.js
├── setupJsdom.js
├── tsconfig.json
├── unit
│ ├── category
│ │ ├── category.spec.js
│ │ └── categoryCollection.spec.js
│ ├── exclusiveManager.spec.js
│ ├── featureProvider
│ │ ├── abstractFeatureProvider.spec.js
│ │ └── wmsFeatureProvider.spec.js
│ ├── helpers
│ │ ├── cesiumHelpers.js
│ │ ├── getFileNameFromUrl.js
│ │ ├── helpers.ts
│ │ ├── imageHelpers.js
│ │ ├── importJSON.js
│ │ ├── obliqueData.js
│ │ ├── obliqueHelpers.js
│ │ ├── openlayersHelpers.js
│ │ └── terrain
│ │ │ └── terrainData.js
│ ├── interaction
│ │ ├── abstractInteraction.spec.js
│ │ ├── coordinateAtPixel.spec.js
│ │ ├── eventHandler.spec.js
│ │ ├── featureAtPixelInteraction.spec.ts
│ │ ├── featureProviderInteraction.spec.ts
│ │ └── interactionChain.spec.js
│ ├── layer
│ │ ├── cesium
│ │ │ ├── cesiumTilesetCesiumImpl.spec.js
│ │ │ ├── dataSourceCesiumImpl.spec.js
│ │ │ ├── getDummyCesium3DTileset.js
│ │ │ ├── sourceVectorContextSync.spec.ts
│ │ │ ├── vcsTile
│ │ │ │ ├── vcsQuadtreeTileProvider.spec.ts
│ │ │ │ ├── vcsTileHelpers.spec.ts
│ │ │ │ └── vcsVectorTile.spec.ts
│ │ │ ├── vectorCesiumImpl.spec.ts
│ │ │ └── vectorContext.spec.ts
│ │ ├── cesiumTilesetLayer.spec.js
│ │ ├── czmlLayer.spec.js
│ │ ├── dataSourceLayer.spec.js
│ │ ├── featureLayer.spec.js
│ │ ├── featureStoreFeatureVisibility.spec.ts
│ │ ├── featureStoreLayer.spec.js
│ │ ├── featureStoreLayerChanges.spec.js
│ │ ├── featureVisibility.spec.ts
│ │ ├── flatGeobufHelpers.spec.ts
│ │ ├── geojsonHelpers.spec.ts
│ │ ├── geojsonLayer.spec.js
│ │ ├── globalHider.spec.js
│ │ ├── layer.spec.js
│ │ ├── layerImplementation.spec.js
│ │ ├── oblique
│ │ │ ├── obliqueHelpers.spec.ts
│ │ │ └── sourceObliqueSync.spec.ts
│ │ ├── openStreetMapLayer.spec.ts
│ │ ├── openlayers
│ │ │ ├── layerOpenlayersImpl.spec.js
│ │ │ └── vectorTileOpenlayersImpl.spec.js
│ │ ├── pointCloudLayer.spec.js
│ │ ├── rasterLayer.spec.ts
│ │ ├── singleImageLayer.spec.js
│ │ ├── terrainHelpers.spec.js
│ │ ├── terrainLayer.spec.js.js
│ │ ├── tileProvider
│ │ │ ├── flatGeobufTileProvider.spec.ts
│ │ │ ├── mvtTileProvider.spec.js
│ │ │ ├── staticGeojsonTileProvider.spec.js
│ │ │ ├── tileProvider.spec.ts
│ │ │ └── urlTemplateTileProvider.spec.js
│ │ ├── tmsLayer.spec.js
│ │ ├── vectorHelpers.spec.js
│ │ ├── vectorLayer.spec.ts
│ │ ├── vectorProperties.spec.ts
│ │ ├── vectorTileLayer.spec.js
│ │ ├── wfsLayer.spec.ts
│ │ ├── wmsLayer.spec.ts
│ │ └── wmtsLayer.spec.js
│ ├── map
│ │ ├── baseOLMap.spec.ts
│ │ ├── cameraLimiter.spec.js
│ │ ├── cesiumMap.spec.js
│ │ ├── navigation
│ │ │ ├── cameraHelper.spec.ts
│ │ │ ├── cesiumNavigation.spec.ts
│ │ │ ├── easingHelper.spec.ts
│ │ │ ├── navigation.spec.ts
│ │ │ ├── openlayersNavigation.spec.ts
│ │ │ └── viewHelper.spec.ts
│ │ ├── obliqueMap.spec.js
│ │ ├── openlayersMap.spec.js
│ │ └── vcsMap.spec.js
│ ├── oblique
│ │ ├── obliqueCollection.spec.js
│ │ ├── obliqueDataSet.spec.js
│ │ ├── obliqueImage.spec.js
│ │ ├── obliqueImageMeta.spec.js
│ │ ├── obliqueProvider.spec.js
│ │ └── parseImageJson.spec.js
│ ├── ol
│ │ ├── geom
│ │ │ ├── circle.spec.js
│ │ │ └── geometryCollection.spec.js
│ │ └── render
│ │ │ └── canvas
│ │ │ └── canvasTileRenderer.spec.js
│ ├── overrideClassRegistry.spec.js
│ ├── style
│ │ ├── arcStyle.spec.js
│ │ ├── arrowStyle.spec.js
│ │ ├── declarativeStyleItem.spec.js
│ │ ├── styleFactory.spec.ts
│ │ ├── styleHelpers.spec.js
│ │ └── writeStyle.spec.js
│ ├── util
│ │ ├── clipping
│ │ │ ├── clippingObject.spec.js
│ │ │ ├── clippingObjectManager.spec.js
│ │ │ ├── clippingPlaneHelper.spec.js
│ │ │ ├── clippingPolygonObject.spec.ts
│ │ │ └── clippingPolygonObjectCollection.spec.ts
│ │ ├── collection.spec.js
│ │ ├── displayQuality
│ │ │ └── displayQuality.spec.ts
│ │ ├── editor
│ │ │ ├── createFeatureSession.spec.ts
│ │ │ ├── editFeaturesSession.spec.ts
│ │ │ ├── editGeometrySession.spec.ts
│ │ │ ├── editorHelpers.spec.ts
│ │ │ ├── interactions
│ │ │ │ ├── createBBoxInteraction.spec.ts
│ │ │ │ ├── createCircleInteraction.spec.ts
│ │ │ │ ├── createLineStringInteraction.spec.ts
│ │ │ │ ├── createPointInteraction.spec.ts
│ │ │ │ ├── createPolygonInteraction.spec.ts
│ │ │ │ ├── creationSnapping.spec.ts
│ │ │ │ ├── editFeaturesMouseOverInteraction.spec.js
│ │ │ │ ├── editGeometryMouseOverInteraction.spec.js
│ │ │ │ ├── ensureHandlerSelectionInteraction.spec.js
│ │ │ │ ├── insertVertexInteraction.spec.ts
│ │ │ │ ├── layerSnapping.spec.ts
│ │ │ │ ├── mapInteractionController.spec.js
│ │ │ │ ├── segmentLengthInteraction.spec.ts
│ │ │ │ ├── selectFeatureMouseOverInteraction.spec.js
│ │ │ │ ├── selectMultiFeatureInteraction.spec.js
│ │ │ │ ├── selectSingleFeatureInteraction.spec.js
│ │ │ │ ├── translateVertexInteraction.spec.ts
│ │ │ │ └── translationSnapping.spec.ts
│ │ │ ├── selectFeaturesSession.spec.js
│ │ │ └── transformation
│ │ │ │ ├── create2DHandlers.spec.ts
│ │ │ │ ├── create3DHandlers.spec.ts
│ │ │ │ ├── extrudeInteraction.spec.js
│ │ │ │ ├── rotateInteraction.spec.js
│ │ │ │ ├── scaleInteraction.spec.ts
│ │ │ │ ├── setupTransformationHandler.ts
│ │ │ │ ├── transformationHandler.spec.ts
│ │ │ │ └── translateInteraction.spec.js
│ │ ├── extent.spec.js
│ │ ├── featureconverter
│ │ │ ├── circleToCesium.spec.ts
│ │ │ ├── clampedPrimitive.spec.ts
│ │ │ ├── convert.spec.ts
│ │ │ ├── extent3D.spec.ts
│ │ │ ├── lineStringToCesium.spec.ts
│ │ │ ├── pointHelpers.spec.ts
│ │ │ ├── pointToCesium.spec.ts
│ │ │ ├── polygonToCesium.spec.ts
│ │ │ ├── storeyHelpers.spec.ts
│ │ │ ├── vectorGeometryFactory.spec.ts
│ │ │ └── vectorHeightInfo.spec.ts
│ │ ├── flight
│ │ │ ├── flightCollection.spec.ts
│ │ │ ├── flightHelpers.spec.ts
│ │ │ ├── flightInstance.spec.ts
│ │ │ ├── flightPlayer.spec.ts
│ │ │ ├── flightVisualizer.spec.ts
│ │ │ └── getDummyFlightInstance.ts
│ │ ├── geometryHelpers.spec.ts
│ │ ├── hiddenObject.spec.ts
│ │ ├── indexedCollection.spec.js
│ │ ├── layerCollection.spec.ts
│ │ ├── mapCollection.spec.js
│ │ ├── math.spec.ts
│ │ ├── overrideCollection.spec.js
│ │ ├── projection.spec.js
│ │ ├── rotation.spec.ts
│ │ ├── urlHelpers.spec.ts
│ │ ├── vcsTemplate.spec.ts
│ │ └── viewpoint.spec.ts
│ ├── vcsApp.spec.ts
│ ├── vcsEvent.spec.ts
│ ├── vcsModule.spec.ts
│ ├── vcsObject.spec.js
│ └── vectorCluster
│ │ ├── vectorClusterCesiumContext.spec.ts
│ │ ├── vectorClusterGroup.spec.ts
│ │ ├── vectorClusterGroupCesiumImpl.spec.ts
│ │ ├── vectorClusterGroupCollection.spec.ts
│ │ ├── vectorClusterGroupImpl.spec.ts
│ │ ├── vectorClusterGroupObliqueImpl.spec.ts
│ │ ├── vectorClusterGroupOpenlayersImpl.spec.ts
│ │ └── vectorClusterStyleItem.spec.ts
└── vcs.js
├── tsconfig.json
├── typedoc.json
└── types
├── rbush-knn.d.ts
└── rbush.d.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | coverage/
2 | node_modules/
3 | build/types/vcs.d.ts
4 | build/types/Cesium_module.d.ts
5 | test-results.xml
6 | docs/
7 | dist/
8 | .tests/
9 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/jsLibraryMappings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/jsLinters/eslint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/prettier.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcmap-core.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | index.d.ts
2 | build/types/vcs.d.ts
3 | build/types/Cesium_module.d.ts
4 | coverage/
5 | docs/
6 | dist/
7 | .tests/
8 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 virtualcitySYSTEMS
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/build/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "no-console": "off",
4 | "import/no-extraneous-dependencies": "off"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/build/postBuild.js:
--------------------------------------------------------------------------------
1 | import { dirname, join as joinPath, basename } from 'node:path';
2 | import { fileURLToPath } from 'node:url';
3 | import { readFile, writeFile, appendFile } from 'node:fs/promises';
4 | import { EOL } from 'node:os';
5 |
6 | const dirName = dirname(fileURLToPath(import.meta.url));
7 | const rootDir = joinPath(dirName, '..');
8 | const distDir = joinPath(rootDir, 'dist');
9 |
10 | const augmentations = [
11 | joinPath(rootDir, 'src', 'cesium', 'cesium.d.ts'),
12 | joinPath(rootDir, 'src', 'ol', 'ol.d.ts'),
13 | joinPath(rootDir, 'src', 'ol', 'geojson.d.ts'),
14 | ];
15 |
16 | async function moveAugmentation(filePath) {
17 | let content = await readFile(filePath, 'utf-8');
18 | content = content.replace(/from\s'\.\.\//g, "from './src/");
19 | const distName = joinPath(distDir, basename(filePath));
20 | await writeFile(distName, content);
21 | }
22 | async function moveAugmentations() {
23 | await Promise.all(augmentations.map(moveAugmentation));
24 | const importStatements = augmentations
25 | .map((a) => basename(a))
26 | .map((name) => `import './${name}';`);
27 |
28 | const indexFileName = joinPath(distDir, 'index.d.ts');
29 | await appendFile(indexFileName, importStatements.join(EOL));
30 | }
31 |
32 | moveAugmentations()
33 | .then(() => {
34 | console.log('Augmentations moved.');
35 | })
36 | .catch((e) => {
37 | console.error('Failed to move augmentations.');
38 | console.error(e);
39 | });
40 |
--------------------------------------------------------------------------------
/documentation/VcsLayer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualcitySYSTEMS/map-core/861d2a016536d461cccd0a8fcf6fcd6ddeec249f/documentation/VcsLayer.png
--------------------------------------------------------------------------------
/documentation/renderScreenshot.md:
--------------------------------------------------------------------------------
1 | # Render Screenshot Utility Documentation
2 |
3 | ## Overview
4 |
5 | print provides functionality to create screenshots from different map types (Cesium, Openlayers, Oblique) and handle the resulting image blob. The main function described here is `renderScreenshot`.
6 |
7 | ## Functions
8 |
9 | ### `renderScreenshot`
10 |
11 | This function prepares the map for a screenshot and returns a canvas element with the rendered image.
12 |
13 | #### Parameters
14 |
15 | - `app` (`VcsApp`): The VcsApp instance.
16 | - `width` (`number`): The width of the screenshot in pixels.
17 |
18 | #### Returns
19 |
20 | - `Promise`: A promise that resolves to the canvas element containing the screenshot.
21 |
22 | #### Usage
23 |
24 | ```typescript
25 | import { renderScreenshot } from './src/util/print';
26 | import VcsApp from './src/vcsApp';
27 |
28 | const app = new VcsApp();
29 | const width = 1920;
30 |
31 | renderScreenshot(app, width).then((canvas) => {
32 | // Use the canvas element
33 | });
34 | ```
35 |
36 | ## Notes
37 |
38 | - Ensure that the map instance is properly initialized before calling this function.
39 | - The function supports different map types including Cesium, Openlayers, and Oblique.
40 | - The function handles the preparation and resetting of the map state before and after taking the screenshot.
41 |
--------------------------------------------------------------------------------
/documentation/vcsApp.md:
--------------------------------------------------------------------------------
1 | # VcsApp
2 |
3 | The [VcsApp](../src/vcsApp.ts) is the main class of a VC Map application.
4 | One or multiple instances of a VcsApp can (co)exist and be embedded in a Website.
5 |
6 | The VcsApp implements the module concept, which allows to build modular applications.
7 | It has the capability to serialize and deserialize its modules.
8 |
9 | ## Collections
10 |
11 | An VcsApp consists of the following [collections](../src/util/collection.ts) containing deserialized items defining the VcsApp's content:
12 |
13 | - modules
14 | - [maps](./maps.md)
15 | - [layers](./layers.md)
16 | - obliqueCollections
17 | - [styles](./style.md)
18 | - viewpoints
19 | - categories
20 | - hiddenObjects
21 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "es6",
4 | "target": "es6",
5 | "baseUrl": "./",
6 | "moduleResolution": "node",
7 | "paths": {
8 | "@vcmap/core": ["index.js"]
9 | }
10 | },
11 | "include": ["./src/**/*", "index"]
12 | }
13 |
--------------------------------------------------------------------------------
/src/cesium/cesium3DTileFeature.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Cesium3DTileFeature,
3 | Cesium3DTilePointFeature,
4 | } from '@vcmap-cesium/engine';
5 |
6 | Cesium3DTileFeature.prototype.getId = function getId(
7 | this: Cesium3DTileFeature,
8 | ): string | number {
9 | return (
10 | (this.getProperty('id') as string | number) ||
11 | `${this.content.url}${this._batchId}`
12 | ); // XXX there is a new property `featureId` on the Cesium3DTileset. this may cause issues when picking b3dm.
13 | };
14 |
15 | // eslint-disable-next-line import/prefer-default-export
16 | export function getAttributes(
17 | this: Cesium3DTileFeature | Cesium3DTilePointFeature,
18 | ): Record {
19 | if ((this.tileset.asset as { version: string })?.version === '1.1') {
20 | const attributes: Record = {};
21 | this.getPropertyIds().forEach((id) => {
22 | attributes[id] = this.getProperty(id);
23 | });
24 | return attributes;
25 | }
26 | return this.getProperty('attributes') as Record;
27 | }
28 |
29 | Cesium3DTileFeature.prototype.getAttributes = getAttributes;
30 |
--------------------------------------------------------------------------------
/src/cesium/cesium3DTilePointFeature.ts:
--------------------------------------------------------------------------------
1 | import { Cesium3DTilePointFeature } from '@vcmap-cesium/engine';
2 | import { getAttributes } from './cesium3DTileFeature.js';
3 |
4 | Cesium3DTilePointFeature.prototype.getId = function getId(
5 | this: Cesium3DTilePointFeature,
6 | ): string | number {
7 | return (
8 | (this.getProperty('id') as string | number) ||
9 | `${this.content.url}${this._batchId}`
10 | );
11 | };
12 |
13 | Cesium3DTilePointFeature.prototype.getAttributes = getAttributes;
14 |
--------------------------------------------------------------------------------
/src/cesium/clippingPolygon.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Cartesian3,
3 | defined,
4 | ClippingPolygon,
5 | Rectangle,
6 | } from '@vcmap-cesium/engine';
7 |
8 | function equalArrayCartesian3(
9 | flatPositions: number[],
10 | cartesian3s: Cartesian3[],
11 | ): boolean {
12 | if (defined(flatPositions) !== defined(cartesian3s)) {
13 | return false;
14 | }
15 | if (flatPositions.length !== cartesian3s.length * 3) {
16 | return false;
17 | }
18 | const n = cartesian3s.length;
19 | for (let i = 0; i < n; i++) {
20 | if (
21 | flatPositions[i * 3] !== cartesian3s[i].x ||
22 | flatPositions[i * 3 + 1] !== cartesian3s[i].y ||
23 | flatPositions[i * 3 + 2] !== cartesian3s[i].z
24 | ) {
25 | return false;
26 | }
27 | }
28 | return true;
29 | }
30 | // eslint-disable-next-line @typescript-eslint/unbound-method
31 | const originalComputeRectangle = ClippingPolygon.prototype.computeRectangle;
32 | ClippingPolygon.prototype.computeRectangle = function computeRectangle(
33 | result,
34 | ): Rectangle {
35 | if (equalArrayCartesian3(this._cachedPackedCartesians, this.positions)) {
36 | return Rectangle.clone(this._cachedRectangle, result);
37 | }
38 | this._cachedPackedCartesians = Cartesian3.packArray(
39 | this.positions,
40 | new Array(this.positions.length * 3),
41 | );
42 | const rectangle = originalComputeRectangle.call(this, result);
43 | this._cachedRectangle = Rectangle.clone(rectangle);
44 | return rectangle;
45 | };
46 |
--------------------------------------------------------------------------------
/src/cesium/clippingPolygonCollection.ts:
--------------------------------------------------------------------------------
1 | import { ClippingPolygonCollection } from '@vcmap-cesium/engine';
2 |
3 | ClippingPolygonCollection.prototype.setDirty = function setDirty(): void {
4 | this._totalPositions = -1;
5 | };
6 |
--------------------------------------------------------------------------------
/src/cesium/entity.ts:
--------------------------------------------------------------------------------
1 | import { Entity } from '@vcmap-cesium/engine';
2 |
3 | Entity.prototype.getId = function getId(this: Entity): string | number {
4 | return this.id;
5 | };
6 |
7 | /**
8 | * To be used for cesium 3D style functions
9 | */
10 | Entity.prototype.getProperty = function getProperty(
11 | this: Entity,
12 | property: string,
13 | ): any {
14 | return this[property as keyof Entity];
15 | };
16 |
17 | Entity.prototype.getAttributes = function getAttributes(): Record<
18 | string,
19 | unknown
20 | > {
21 | return this.properties ?? {};
22 | };
23 |
24 | /**
25 | * To be used for cesium 3D style functions
26 | */
27 | Entity.prototype.getPropertyInherited = function getPropertyInherited(
28 | this: Entity,
29 | property: string,
30 | ): any {
31 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return
32 | return this.getProperty(property);
33 | };
34 |
--------------------------------------------------------------------------------
/src/cesium/wallpaperMaterial.js:
--------------------------------------------------------------------------------
1 | import { Material, Cartesian2 } from '@vcmap-cesium/engine';
2 |
3 | /**
4 | * @file Wallpaper Material to implement openlayers pattern support in cesium
5 | */
6 |
7 | // Call this once at application startup
8 | // eslint-disable-next-line no-underscore-dangle
9 | Material._materialCache.addMaterial('Wallpaper', {
10 | fabric: {
11 | type: 'Wallpaper',
12 | uniforms: {
13 | image: Material.DefaultImageId,
14 | anchor: new Cartesian2(0, 0),
15 | },
16 | components: {
17 | diffuse:
18 | 'texture2D(image, fract((gl_FragCoord.xy - anchor.xy) / vec2(imageDimensions.xy))).rgb',
19 | alpha:
20 | 'texture2D(image, fract((gl_FragCoord.xy - anchor.xy) / vec2(imageDimensions.xy))).a',
21 | },
22 | },
23 | translucent: false,
24 | });
25 |
26 | // //Create an instance and assign to anything that has a material property.
27 | // //scene - the scene
28 | // //image - the image (I think both a url or Image object are supported)
29 | // //anchor - A Cartesian3 that is the most southern and westard point of the geometry
30 | // var WallPaperMaterialProperty = function(scene, image, anchor) {
31 | // this._scene = scene;
32 | // this._image = image;
33 | // this._anchor = anchor;
34 | // this.definitionChanged = new Cesium.Event();
35 | // this.isConstant = true;
36 | // };
37 | //
38 | // WallPaperMaterialProperty.prototype.getType = function(time) {
39 | // return 'Wallpaper';
40 | // };
41 | //
42 | // WallPaperMaterialProperty.prototype.getValue = function(time, result) {
43 | // if (!Cesium.defined(result)) {
44 | // result = {
45 | // image : undefined,
46 | // anchor : undefined
47 | // };
48 | // }
49 | //
50 | // result.image = this._image;
51 | // result.anchor = Cesium.SceneTransforms.wgs84ToDrawingBufferCoordinates(this._scene, this._anchor, result.anchor);
52 | // if(Cesium.defined(result.anchor)){
53 | // result.anchor.x = Math.floor(result.anchor.x);
54 | // result.anchor.y = Math.floor(result.anchor.y);
55 | // } else {
56 | // result.anchor = new Cesium.Cartesian2(0, 0);
57 | // }
58 | // return result;
59 | // };
60 | //
61 | // WallPaperMaterialProperty.prototype.equals = function(other) {
62 | // return this === other || //
63 | // (other instanceof WallPaperMaterialProperty && //
64 | // this._image === other._image);
65 | // };
66 |
--------------------------------------------------------------------------------
/src/featureProvider/featureProviderSymbols.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Added to ol.Feature, if they are not part of a layer, but provided by an {@link AbstractFeatureProvider}.
3 | */
4 | export const isProvidedFeature: unique symbol = Symbol('isProvidedFeature');
5 |
6 | /**
7 | * Added to ol.Feature, if a {@link AbstractFeatureProvider} provides more than one feature for one location.
8 | * The provided feature is a cluster feature. The single features can be accessed by `feature.get('features')`.
9 | */
10 | export const isProvidedClusterFeature = Symbol('isProvidedClusterFeature');
11 |
--------------------------------------------------------------------------------
/src/featureProvider/tileProviderFeatureProvider.ts:
--------------------------------------------------------------------------------
1 | import type { Coordinate } from 'ol/coordinate.js';
2 | import type { Feature } from 'ol/index.js';
3 | import AbstractFeatureProvider, {
4 | type AbstractFeatureProviderOptions,
5 | } from './abstractFeatureProvider.js';
6 | import { featureProviderClassRegistry } from '../classRegistry.js';
7 | import type TileProvider from '../layer/tileProvider/tileProvider.js';
8 |
9 | export type TileProviderFeatureProviderOptions =
10 | AbstractFeatureProviderOptions & {
11 | tileProvider: TileProvider;
12 | };
13 |
14 | class TileProviderFeatureProvider extends AbstractFeatureProvider {
15 | static get className(): string {
16 | return 'TileProviderFeatureProvider';
17 | }
18 |
19 | tileProvider: TileProvider;
20 |
21 | /**
22 | * @param layerName
23 | * @param options
24 | */
25 | constructor(layerName: string, options: TileProviderFeatureProviderOptions) {
26 | super(layerName, options);
27 |
28 | this.mapTypes = ['CesiumMap'];
29 | this.tileProvider = options.tileProvider;
30 | }
31 |
32 | async getFeaturesByCoordinate(
33 | coordinate: Coordinate,
34 | resolution: number,
35 | headers?: Record,
36 | ): Promise {
37 | const features = await this.tileProvider.getFeaturesByCoordinate(
38 | coordinate,
39 | resolution,
40 | headers,
41 | );
42 | const checkShow = (feature: Feature): boolean =>
43 | this.style ? !!this.style.cesiumStyle.show.evaluate(feature) : true;
44 | return features.filter((feature) => {
45 | return (
46 | this.vectorProperties.getAllowPicking(feature) && checkShow(feature)
47 | );
48 | });
49 | }
50 |
51 | destroy(): void {
52 | this.tileProvider.destroy();
53 | super.destroy();
54 | }
55 | }
56 |
57 | export default TileProviderFeatureProvider;
58 | featureProviderClassRegistry.registerClass(
59 | TileProviderFeatureProvider.className,
60 | TileProviderFeatureProvider,
61 | );
62 |
--------------------------------------------------------------------------------
/src/global.d.ts:
--------------------------------------------------------------------------------
1 | import type VcsApp from './vcsApp.js';
2 | import { mouseOverSymbol } from './util/editor/editorSymbols.js';
3 | // eslint-disable-next-line import/no-named-default
4 | import type { default as VcsModule, VcsModuleConfig } from './vcsModule.js';
5 |
6 | declare global {
7 | interface Window {
8 | vcs: {
9 | apps: Map;
10 | createModuleFromConfig: (config: VcsModuleConfig) => VcsModule;
11 | };
12 | opera?: string;
13 | }
14 | interface CSSStyleDeclaration {
15 | [mouseOverSymbol]?: string;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/interaction/featureProviderInteraction.ts:
--------------------------------------------------------------------------------
1 | import type { Coordinate } from 'ol/coordinate.js';
2 |
3 | import Point from 'ol/geom/Point.js';
4 | import Feature from 'ol/Feature.js';
5 | import AbstractInteraction, {
6 | type InteractionEvent,
7 | } from './abstractInteraction.js';
8 | import {
9 | EventType,
10 | ModificationKeyType,
11 | PointerKeyType,
12 | } from './interactionType.js';
13 | import {
14 | isProvidedClusterFeature,
15 | isProvidedFeature,
16 | } from '../featureProvider/featureProviderSymbols.js';
17 |
18 | /**
19 | * @group Interaction
20 | */
21 | class FeatureProviderInteraction extends AbstractInteraction {
22 | constructor() {
23 | super(EventType.CLICK, ModificationKeyType.ALL, PointerKeyType.ALL);
24 |
25 | this.setActive();
26 | }
27 |
28 | // eslint-disable-next-line class-methods-use-this
29 | async pipe(event: InteractionEvent): Promise {
30 | if (event.feature) {
31 | return event;
32 | }
33 |
34 | const layersWithProvider = [...event.map.layerCollection]
35 | .filter((l) => {
36 | return (
37 | l.featureProvider &&
38 | l.active &&
39 | l.isSupported(event.map) &&
40 | l.featureProvider.isSupported(event.map)
41 | );
42 | })
43 | .reverse();
44 |
45 | if (layersWithProvider.length > 0) {
46 | const resolution = event.map.getCurrentResolution(
47 | event.position as Coordinate,
48 | );
49 | // TODO make sure the layers are rendered, check min/max RenderingResolution
50 | const features = (
51 | await Promise.all(
52 | layersWithProvider.map(
53 | (l) =>
54 | l.featureProvider?.getFeaturesByCoordinate?.(
55 | event.position as Coordinate,
56 | resolution,
57 | l.headers,
58 | ),
59 | ),
60 | )
61 | )
62 | .filter((f) => !!f)
63 | .flat();
64 | if (features.length === 1) {
65 | event.feature = features[0];
66 | } else if (features.length > 1) {
67 | const feature = new Feature({ features });
68 | feature[isProvidedFeature] = true; // backward compatibility, may remove in future
69 | feature[isProvidedClusterFeature] = true;
70 | feature.setGeometry(new Point(event.position as Coordinate));
71 | event.feature = feature;
72 | }
73 | }
74 |
75 | return event;
76 | }
77 | }
78 |
79 | export default FeatureProviderInteraction;
80 |
--------------------------------------------------------------------------------
/src/interaction/interactionType.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Enumeration of modification key types
3 | */
4 | export enum ModificationKeyType {
5 | NONE = 2,
6 | ALT = 4,
7 | CTRL = 8,
8 | SHIFT = 16,
9 | ALL = NONE | ALT | CTRL | SHIFT,
10 | }
11 |
12 | /**
13 | * Enumeration of pointer event types
14 | */
15 | export enum EventType {
16 | NONE = 0,
17 | CLICK = 32,
18 | DBLCLICK = 64,
19 | DRAG = 128,
20 | DRAGSTART = 256,
21 | DRAGEND = 512,
22 | MOVE = 1024,
23 | DRAGEVENTS = DRAG | DRAGSTART | DRAGEND,
24 | CLICKMOVE = CLICK | MOVE,
25 | ALL = CLICK | DBLCLICK | DRAG | DRAGSTART | DRAGEND | MOVE,
26 | }
27 |
28 | /**
29 | * Enumeration of pointer keys.
30 | */
31 | export enum PointerKeyType {
32 | LEFT = 2048,
33 | RIGHT = 4096,
34 | MIDDLE = 8192,
35 | ALL = LEFT | RIGHT | MIDDLE,
36 | }
37 |
38 | /**
39 | * Enumeration of pointer key events.
40 | */
41 | export enum PointerEventType {
42 | DOWN = 1,
43 | UP = 2,
44 | MOVE = 3,
45 | }
46 |
--------------------------------------------------------------------------------
/src/layer/cesium/openStreetMapCesiumImpl.ts:
--------------------------------------------------------------------------------
1 | import {
2 | OpenStreetMapImageryProvider,
3 | ImageryLayer as CesiumImageryLayer,
4 | } from '@vcmap-cesium/engine';
5 | import RasterLayerCesiumImpl from './rasterLayerCesiumImpl.js';
6 |
7 | /**
8 | * represents a specific OpenStreetMapLayer layer for cesium.
9 | */
10 | class OpenStreetMapCesiumImpl extends RasterLayerCesiumImpl {
11 | static get className(): string {
12 | return 'OpenStreetMapCesiumImpl';
13 | }
14 |
15 | getCesiumLayer(): Promise {
16 | const layerOptions = this.getCesiumLayerOptions();
17 | return Promise.resolve(
18 | new CesiumImageryLayer(
19 | new OpenStreetMapImageryProvider({ maximumLevel: this.maxLevel }),
20 | layerOptions,
21 | ),
22 | );
23 | }
24 | }
25 |
26 | export default OpenStreetMapCesiumImpl;
27 |
--------------------------------------------------------------------------------
/src/layer/cesium/resourceHelper.ts:
--------------------------------------------------------------------------------
1 | import { Resource } from '@vcmap-cesium/engine';
2 |
3 | // eslint-disable-next-line import/prefer-default-export
4 | export function getResourceOrUrl(
5 | url: string,
6 | headers?: Record,
7 | ): string | Resource {
8 | if (headers) {
9 | return new Resource({
10 | url,
11 | headers,
12 | });
13 | }
14 | return url;
15 | }
16 |
--------------------------------------------------------------------------------
/src/layer/cesium/singleImageCesiumImpl.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Rectangle,
3 | SingleTileImageryProvider,
4 | ImageryLayer,
5 | } from '@vcmap-cesium/engine';
6 | import RasterLayerCesiumImpl from './rasterLayerCesiumImpl.js';
7 | import { wgs84Projection } from '../../util/projection.js';
8 | import type { SingleImageImplementationOptions } from '../singleImageLayer.js';
9 | import type CesiumMap from '../../map/cesiumMap.js';
10 | import { getResourceOrUrl } from './resourceHelper.js';
11 |
12 | /**
13 | * represents a specific Cesium SingleTileImagery Layer class.
14 | */
15 | class SingleImageCesiumImpl extends RasterLayerCesiumImpl {
16 | static get className(): string {
17 | return 'SingleImageCesiumImpl';
18 | }
19 |
20 | credit: string | undefined;
21 |
22 | constructor(map: CesiumMap, options: SingleImageImplementationOptions) {
23 | super(map, options);
24 | this.credit = options.credit;
25 | }
26 |
27 | async getCesiumLayer(): Promise {
28 | const options: SingleTileImageryProvider.fromUrlOptions = {
29 | credit: this.credit,
30 | };
31 |
32 | const extent = this.extent?.getCoordinatesInProjection(wgs84Projection);
33 | if (extent) {
34 | options.rectangle = Rectangle.fromDegrees(
35 | extent[0],
36 | extent[1],
37 | extent[2],
38 | extent[3],
39 | );
40 | }
41 |
42 | const imageryProvider = await SingleTileImageryProvider.fromUrl(
43 | getResourceOrUrl(this.url!, this.headers),
44 | options,
45 | );
46 | const layerOptions = this.getCesiumLayerOptions();
47 | layerOptions.rectangle = options.rectangle;
48 | return new ImageryLayer(imageryProvider, layerOptions);
49 | }
50 | }
51 |
52 | export default SingleImageCesiumImpl;
53 |
--------------------------------------------------------------------------------
/src/layer/cesium/terrainCesiumImpl.ts:
--------------------------------------------------------------------------------
1 | import type { CesiumTerrainProvider } from '@vcmap-cesium/engine';
2 | import LayerImplementation from '../layerImplementation.js';
3 | import { vcsLayerName } from '../layerSymbols.js';
4 | import { getTerrainProviderForUrl } from '../terrainHelpers.js';
5 | import CesiumMap from '../../map/cesiumMap.js';
6 | import type { TerrainImplementationOptions } from '../terrainLayer.js';
7 |
8 | /**
9 | * TerrainLayer implementation for {@link CesiumMap}
10 | */
11 | class TerrainCesiumImpl extends LayerImplementation {
12 | static get className(): string {
13 | return 'TerrainCesiumImpl';
14 | }
15 |
16 | requestVertexNormals: boolean;
17 |
18 | requestWaterMask: boolean;
19 |
20 | terrainProvider: CesiumTerrainProvider | undefined = undefined;
21 |
22 | constructor(map: CesiumMap, options: TerrainImplementationOptions) {
23 | super(map, options);
24 |
25 | this.requestVertexNormals = options.requestVertexNormals;
26 | this.requestWaterMask = options.requestWaterMask;
27 | }
28 |
29 | async initialize(): Promise {
30 | if (!this.initialized) {
31 | this.terrainProvider = await getTerrainProviderForUrl(
32 | this.url!,
33 | {
34 | requestVertexNormals: this.requestVertexNormals,
35 | requestWaterMask: this.requestWaterMask,
36 | },
37 | this.headers,
38 | );
39 | this.terrainProvider[vcsLayerName] = this.name;
40 | }
41 | return super.initialize();
42 | }
43 |
44 | async activate(): Promise {
45 | await super.activate();
46 | if (this.active && this.terrainProvider) {
47 | this.map.setTerrainProvider(this.terrainProvider);
48 | }
49 | }
50 |
51 | deactivate(): void {
52 | super.deactivate();
53 | if (this.terrainProvider) {
54 | this.map.unsetTerrainProvider(this.terrainProvider);
55 | }
56 | }
57 |
58 | destroy(): void {
59 | if (this.terrainProvider) {
60 | this.map.unsetTerrainProvider(this.terrainProvider);
61 | }
62 | this.terrainProvider = undefined;
63 | super.destroy();
64 | }
65 | }
66 |
67 | export default TerrainCesiumImpl;
68 |
--------------------------------------------------------------------------------
/src/layer/cesium/tmsCesiumImpl.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Rectangle,
3 | GeographicTilingScheme,
4 | TileMapServiceImageryProvider,
5 | ImageryLayer as CesiumImageryLayer,
6 | } from '@vcmap-cesium/engine';
7 | import RasterLayerCesiumImpl from './rasterLayerCesiumImpl.js';
8 | import { wgs84Projection } from '../../util/projection.js';
9 | import { TilingScheme } from '../rasterLayer.js';
10 | import type CesiumMap from '../../map/cesiumMap.js';
11 | import type { TMSImplementationOptions } from '../tmsLayer.js';
12 | import { getResourceOrUrl } from './resourceHelper.js';
13 |
14 | /**
15 | * TmsLayer implementation for {@link CesiumMap}.
16 | */
17 | class TmsCesiumImpl extends RasterLayerCesiumImpl {
18 | static get className(): string {
19 | return 'TmsCesiumImpl';
20 | }
21 |
22 | format: string;
23 |
24 | constructor(map: CesiumMap, options: TMSImplementationOptions) {
25 | super(map, options);
26 | this.format = options.format;
27 | }
28 |
29 | async getCesiumLayer(): Promise {
30 | const options: TileMapServiceImageryProvider.ConstructorOptions = {
31 | fileExtension: this.format,
32 | maximumLevel: this.maxLevel,
33 | minimumLevel: this.minLevel,
34 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
35 | // @ts-ignore
36 | show: false,
37 | };
38 |
39 | if (this.extent && this.extent.isValid()) {
40 | const extent = this.extent.getCoordinatesInProjection(wgs84Projection);
41 | options.rectangle = Rectangle.fromDegrees(
42 | extent[0],
43 | extent[1],
44 | extent[2],
45 | extent[3],
46 | );
47 | }
48 | if (this.tilingSchema === TilingScheme.GEOGRAPHIC) {
49 | options.tilingScheme = new GeographicTilingScheme();
50 | }
51 | const imageryProvider = await TileMapServiceImageryProvider.fromUrl(
52 | getResourceOrUrl(this.url!, this.headers),
53 | options,
54 | );
55 |
56 | const layerOptions = this.getCesiumLayerOptions();
57 | return new CesiumImageryLayer(imageryProvider, layerOptions);
58 | }
59 | }
60 |
61 | export default TmsCesiumImpl;
62 |
--------------------------------------------------------------------------------
/src/layer/cesium/vcsTile/vcsChildTile.ts:
--------------------------------------------------------------------------------
1 | import { QuadtreeTile, TileBoundingRegion } from '@vcmap-cesium/engine';
2 | import {
3 | getTileBoundingRegion,
4 | VcsTile,
5 | VcsTileState,
6 | VcsTileType,
7 | } from './vcsTileHelpers.js';
8 | import type CesiumMap from '../../../map/cesiumMap.js';
9 |
10 | export default class VcsChildTile implements VcsTile {
11 | state = VcsTileState.LOADING;
12 |
13 | type = VcsTileType.CHILD;
14 |
15 | tileBoundingRegion: TileBoundingRegion;
16 |
17 | private _tile: QuadtreeTile;
18 |
19 | constructor(tile: QuadtreeTile, map: CesiumMap) {
20 | this.tileBoundingRegion = getTileBoundingRegion(tile, map);
21 | this.state = VcsTileState.READY;
22 | this._tile = tile;
23 | }
24 |
25 | get show(): boolean {
26 | return this._tile.parent?.data?.show ?? false;
27 | }
28 |
29 | // eslint-disable-next-line class-methods-use-this,@typescript-eslint/no-empty-function
30 | set show(_show: boolean) {}
31 | }
32 |
--------------------------------------------------------------------------------
/src/layer/cesium/vcsTile/vcsNoDataTile.ts:
--------------------------------------------------------------------------------
1 | import { QuadtreeTile, TileBoundingRegion } from '@vcmap-cesium/engine';
2 | import type CesiumMap from '../../../map/cesiumMap.js';
3 | import {
4 | getTileBoundingRegion,
5 | VcsTile,
6 | VcsTileState,
7 | VcsTileType,
8 | } from './vcsTileHelpers.js';
9 |
10 | export default class VcsNoDataTile implements VcsTile {
11 | state = VcsTileState.LOADING;
12 |
13 | type = VcsTileType.NO_DATA;
14 |
15 | tileBoundingRegion: TileBoundingRegion;
16 |
17 | constructor(tile: QuadtreeTile, map: CesiumMap) {
18 | this.tileBoundingRegion = getTileBoundingRegion(tile, map);
19 |
20 | this.state = VcsTileState.READY;
21 | }
22 |
23 | // eslint-disable-next-line class-methods-use-this
24 | get show(): boolean {
25 | return false;
26 | }
27 |
28 | // eslint-disable-next-line class-methods-use-this,@typescript-eslint/no-empty-function
29 | set show(_show: boolean) {}
30 | }
31 |
--------------------------------------------------------------------------------
/src/layer/cesium/vectorTileCesiumImpl.ts:
--------------------------------------------------------------------------------
1 | import {
2 | PrimitiveCollection,
3 | QuadtreePrimitive,
4 | SplitDirection,
5 | } from '@vcmap-cesium/engine';
6 | import StyleItem from '../../style/styleItem.js';
7 | import LayerImplementation from '../layerImplementation.js';
8 | import type CesiumMap from '../../map/cesiumMap.js';
9 | import {
10 | VectorTileImplementation,
11 | VectorTileImplementationOptions,
12 | } from '../vectorTileLayer.js';
13 | import { vcsLayerName } from '../layerSymbols.js';
14 | import VcsQuadtreeTileProvider from './vcsTile/vcsQuadtreeTileProvider.js';
15 |
16 | export default class VectorTileCesiumImpl
17 | extends LayerImplementation
18 | implements VectorTileImplementation
19 | {
20 | static get className(): string {
21 | return 'VectorTileCesiumImpl';
22 | }
23 |
24 | private _quadtreeProvider: VcsQuadtreeTileProvider;
25 |
26 | private _quadtreePrimitive: QuadtreePrimitive;
27 |
28 | private _primitiveCollection = new PrimitiveCollection();
29 |
30 | constructor(map: CesiumMap, options: VectorTileImplementationOptions) {
31 | super(map, options);
32 | this._quadtreeProvider = new VcsQuadtreeTileProvider(
33 | map,
34 | this._primitiveCollection,
35 | options,
36 | );
37 | this._quadtreePrimitive = new QuadtreePrimitive({
38 | tileProvider: this._quadtreeProvider,
39 | });
40 | this._primitiveCollection.add(this._quadtreePrimitive);
41 | this._primitiveCollection[vcsLayerName] = this.name;
42 | this._primitiveCollection.show = false;
43 | }
44 |
45 | updateTiles(_tiles: string[], featureVisibility: boolean): void {
46 | if (!featureVisibility) {
47 | this._quadtreePrimitive.invalidateAllTiles(); // XXX this we can do bette
48 | }
49 | }
50 |
51 | async initialize(): Promise {
52 | if (!this.initialized) {
53 | this.map.addPrimitiveCollection(this._primitiveCollection);
54 | }
55 | await super.initialize();
56 | }
57 |
58 | async activate(): Promise {
59 | this._primitiveCollection.show = true;
60 | return super.activate();
61 | }
62 |
63 | deactivate(): void {
64 | this._primitiveCollection.show = false;
65 | super.deactivate();
66 | }
67 |
68 | updateStyle(style: StyleItem, _silent?: boolean): void {
69 | this._quadtreeProvider.updateStyle(style);
70 | this._quadtreePrimitive.invalidateAllTiles();
71 | }
72 |
73 | updateSplitDirection(direction: SplitDirection): void {
74 | this._quadtreeProvider.updateSplitDirection(direction);
75 | this._quadtreePrimitive.invalidateAllTiles();
76 | }
77 |
78 | destroy(): void {
79 | if (!this.isDestroyed) {
80 | this._quadtreeProvider.destroy();
81 | this._quadtreePrimitive.invalidateAllTiles();
82 | if (this.map.initialized) {
83 | this.map.removePrimitiveCollection(this._primitiveCollection);
84 | } else {
85 | this._primitiveCollection.destroy();
86 | }
87 | }
88 |
89 | super.destroy();
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/layer/cesium/wmsCesiumImpl.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ImageryLayer as CesiumImageryLayer,
3 | Rectangle,
4 | WebMercatorTilingScheme,
5 | WebMapServiceImageryProvider,
6 | } from '@vcmap-cesium/engine';
7 | import type { Size } from 'ol/size.js';
8 |
9 | import RasterLayerCesiumImpl from './rasterLayerCesiumImpl.js';
10 | import { wgs84Projection } from '../../util/projection.js';
11 | import type { WMSImplementationOptions } from '../wmsLayer.js';
12 | import type CesiumMap from '../../map/cesiumMap.js';
13 | import { getResourceOrUrl } from './resourceHelper.js';
14 |
15 | /**
16 | * represents a specific Cesium WmsCesiumImpl Layer class.
17 | */
18 | class WmsCesiumImpl extends RasterLayerCesiumImpl {
19 | static get className(): string {
20 | return 'WmsCesiumImpl';
21 | }
22 |
23 | parameters: Record;
24 |
25 | highResolution: boolean;
26 |
27 | tileSize: Size;
28 |
29 | constructor(map: CesiumMap, options: WMSImplementationOptions) {
30 | super(map, options);
31 | this.parameters = options.parameters;
32 | this.highResolution = options.highResolution;
33 | this.tileSize = options.tileSize;
34 | }
35 |
36 | getCesiumLayer(): Promise {
37 | const parameters = { ...this.parameters };
38 | if (this.highResolution) {
39 | parameters.width = String(this.tileSize[0] * 2);
40 | parameters.height = String(this.tileSize[1] * 2);
41 | }
42 | const options: WebMapServiceImageryProvider.ConstructorOptions = {
43 | url: getResourceOrUrl(this.url!, this.headers),
44 | layers: parameters.LAYERS,
45 | minimumLevel: this.minLevel,
46 | maximumLevel: this.maxLevel,
47 | parameters,
48 | tileWidth: this.tileSize[0],
49 | tileHeight: this.tileSize[1],
50 | };
51 |
52 | if (this.extent && this.extent.isValid()) {
53 | const extent = this.extent.getCoordinatesInProjection(wgs84Projection);
54 | if (extent) {
55 | options.rectangle = Rectangle.fromDegrees(
56 | extent[0],
57 | extent[1],
58 | extent[2],
59 | extent[3],
60 | );
61 | }
62 | }
63 | if (this.tilingSchema === 'mercator') {
64 | options.tilingScheme = new WebMercatorTilingScheme();
65 | }
66 |
67 | const imageryProvider = new WebMapServiceImageryProvider(options);
68 | const layerOptions = this.getCesiumLayerOptions();
69 | return Promise.resolve(
70 | new CesiumImageryLayer(imageryProvider, layerOptions),
71 | );
72 | }
73 | }
74 |
75 | export default WmsCesiumImpl;
76 |
--------------------------------------------------------------------------------
/src/layer/featureStoreFeatureVisibility.ts:
--------------------------------------------------------------------------------
1 | import FeatureVisibility, { HighlightStyleType } from './featureVisibility.js';
2 | import type FeatureStoreLayerChanges from './featureStoreLayerChanges.js';
3 |
4 | export default class FeatureStoreFeatureVisibility extends FeatureVisibility {
5 | private _changeTracker: FeatureStoreLayerChanges;
6 |
7 | constructor(changeTracker: FeatureStoreLayerChanges) {
8 | super();
9 | this._changeTracker = changeTracker;
10 | }
11 |
12 | highlight(toHighlight: Record): void {
13 | const isTracking = this._changeTracker.active;
14 | if (isTracking) {
15 | this._changeTracker.pauseTracking('changefeature');
16 | }
17 | super.highlight(toHighlight);
18 | if (isTracking) {
19 | this._changeTracker.track();
20 | }
21 | }
22 |
23 | unHighlight(toUnHighlight: (string | number)[]): void {
24 | const isTracking = this._changeTracker.active;
25 | if (isTracking) {
26 | this._changeTracker.pauseTracking('changefeature');
27 | }
28 | super.unHighlight(toUnHighlight);
29 | if (isTracking) {
30 | this._changeTracker.track();
31 | }
32 | }
33 |
34 | hideObjects(toHide: (string | number)[]): void {
35 | const isTracking = this._changeTracker.active;
36 | if (isTracking) {
37 | this._changeTracker.pauseTracking('changefeature');
38 | }
39 | super.hideObjects(toHide);
40 | if (isTracking) {
41 | this._changeTracker.track();
42 | }
43 | }
44 |
45 | showObjects(unHide: (string | number)[]): void {
46 | const isTracking = this._changeTracker.active;
47 | if (isTracking) {
48 | this._changeTracker.pauseTracking('changefeature');
49 | }
50 | super.showObjects(unHide);
51 | if (isTracking) {
52 | this._changeTracker.track();
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/layer/featureStoreLayerState.ts:
--------------------------------------------------------------------------------
1 | export const featureStoreStateSymbol = Symbol('vcsFeatureType');
2 |
3 | /**
4 | * Enumeration of feature store item states
5 | */
6 | export type FeatureStoreLayerState =
7 | | 'dynamic'
8 | | 'static'
9 | | 'edited'
10 | | 'deleted'
11 | | 'removed';
12 |
--------------------------------------------------------------------------------
/src/layer/flatGeobufHelpers.ts:
--------------------------------------------------------------------------------
1 | import Feature from 'ol/Feature.js';
2 | import { fromFeature } from 'flatgeobuf/lib/mjs/ol/feature.js';
3 | import { HttpReader } from 'flatgeobuf/lib/mjs/http-reader.js';
4 | import Projection, {
5 | mercatorProjection,
6 | parseEPSGCode,
7 | } from '../util/projection.js';
8 | import Extent from '../util/extent.js';
9 | import { alreadyTransformedToMercator } from './vectorSymbols.js';
10 |
11 | export async function getValidReader(
12 | url: string,
13 | projection: Projection,
14 | ): Promise {
15 | const reader = await HttpReader.open(url, false);
16 | const { crs } = reader.header;
17 | if (crs) {
18 | const epsgCode = parseEPSGCode(crs.code, crs.org ?? undefined);
19 | if (epsgCode !== projection.epsg) {
20 | throw new Error(
21 | `The crs of the data does not match the projection of the layer. Data crs: ${epsgCode}, layer projection: ${projection.epsg}`,
22 | );
23 | }
24 | }
25 | return reader;
26 | }
27 |
28 | export async function getOlFeatures(
29 | reader: HttpReader,
30 | projection: Projection,
31 | extent: Extent,
32 | ): Promise {
33 | const features = [];
34 | const isMercator = projection.epsg === mercatorProjection.epsg;
35 | const dataExtent = extent.getCoordinatesInProjection(projection);
36 |
37 | for await (const feature of reader.selectBbox({
38 | minX: dataExtent[0],
39 | minY: dataExtent[1],
40 | maxX: dataExtent[2],
41 | maxY: dataExtent[3],
42 | })) {
43 | const olFeature = fromFeature(
44 | feature.id,
45 | feature.feature,
46 | reader.header,
47 | ) as Feature;
48 | const geometry = olFeature.getGeometry();
49 | if (geometry && !isMercator) {
50 | geometry.transform(projection.proj, mercatorProjection.proj);
51 | geometry[alreadyTransformedToMercator] = true;
52 | }
53 |
54 | features.push(olFeature);
55 | }
56 |
57 | return features;
58 | }
59 |
--------------------------------------------------------------------------------
/src/layer/flatGeobufLayer.ts:
--------------------------------------------------------------------------------
1 | import VectorLayer, { VectorOptions } from './vectorLayer.js';
2 | import { wgs84Projection } from '../util/projection.js';
3 | import { layerClassRegistry } from '../classRegistry.js';
4 | import Extent from '../util/extent.js';
5 | import { getOlFeatures, getValidReader } from './flatGeobufHelpers.js';
6 |
7 | export type FlatGeobufLayerOptions = VectorOptions & {
8 | url: string | Record;
9 | };
10 |
11 | export default class FlatGeobufLayer extends VectorLayer {
12 | static get className(): string {
13 | return 'FlatGeobufLayer';
14 | }
15 |
16 | static getDefaultOptions(): FlatGeobufLayerOptions {
17 | return {
18 | ...super.getDefaultOptions(),
19 | url: '',
20 | };
21 | }
22 |
23 | private _dataFetchedPromise: Promise | undefined;
24 |
25 | async initialize(): Promise {
26 | if (!this.initialized) {
27 | await super.initialize();
28 |
29 | if (this._url) {
30 | await this.fetchData();
31 | }
32 | }
33 | }
34 |
35 | async fetchData(): Promise {
36 | if (this._dataFetchedPromise) {
37 | return this._dataFetchedPromise;
38 | }
39 |
40 | const reader = await getValidReader(this.url, this.projection);
41 | let resolve: () => void;
42 | const promise = new Promise((r) => {
43 | resolve = r;
44 | });
45 | this._dataFetchedPromise = promise;
46 | const worldExtent = new Extent({
47 | coordinates: Extent.WGS_84_EXTENT,
48 | projection: wgs84Projection.toJSON(),
49 | });
50 | const features = await getOlFeatures(reader, this.projection, worldExtent);
51 | if (this._dataFetchedPromise === promise) {
52 | this.addFeatures(features);
53 | }
54 | resolve!();
55 | return this._dataFetchedPromise;
56 | }
57 |
58 | async reload(): Promise {
59 | if (this._dataFetchedPromise) {
60 | this._dataFetchedPromise = undefined;
61 | await this.fetchData();
62 | }
63 | return this.forceRedraw();
64 | }
65 | }
66 | layerClassRegistry.registerClass(FlatGeobufLayer.className, FlatGeobufLayer);
67 |
--------------------------------------------------------------------------------
/src/layer/layerImplementation.ts:
--------------------------------------------------------------------------------
1 | import VcsObject from '../vcsObject.js';
2 | import LayerState from './layerState.js';
3 | import type VcsMap from '../map/vcsMap.js';
4 | import type { LayerImplementationOptions } from './layer.js';
5 |
6 | /**
7 | * represents an implementation for a Layer for a specific Map
8 | */
9 | class LayerImplementation extends VcsObject {
10 | static get className(): string {
11 | return 'LayerImplementation';
12 | }
13 |
14 | private _map: M | undefined;
15 |
16 | url: string | undefined;
17 |
18 | protected _state: LayerState = LayerState.INACTIVE;
19 |
20 | private _initialized = false;
21 |
22 | headers?: Record;
23 |
24 | constructor(map: M, options: LayerImplementationOptions) {
25 | super(options);
26 | this._map = map;
27 | this.url = options.url;
28 | this.headers = options.headers;
29 | }
30 |
31 | get map(): M {
32 | if (!this._map) {
33 | throw new Error('Accessing destroyed implementation');
34 | }
35 | return this._map;
36 | }
37 |
38 | /**
39 | * Whether this implementation has been initialized (e.g. activated at least once)
40 | */
41 | get initialized(): boolean {
42 | return this._initialized;
43 | }
44 |
45 | get active(): boolean {
46 | return this._state === LayerState.ACTIVE;
47 | }
48 |
49 | get loading(): boolean {
50 | return this._state === LayerState.LOADING;
51 | }
52 |
53 | /**
54 | * interface to initialize this implementation, is used to setup elements which have to be created only once.
55 | * Has to set this.initialized = true;
56 | */
57 | initialize(): Promise {
58 | this._initialized = true;
59 | return Promise.resolve();
60 | }
61 |
62 | /**
63 | * activates the implementation, if the map is also active. calls initialize (only use internally)
64 | * Once the promise resolves, the layer can still be inactive, if deactivate was called while initializing the layer.
65 | */
66 | async activate(): Promise {
67 | if (this.map.active && !this.active) {
68 | this._state = LayerState.LOADING;
69 | await this.initialize();
70 | if (this.loading) {
71 | this._state = LayerState.ACTIVE;
72 | }
73 | }
74 | }
75 |
76 | /**
77 | * deactivates the implementation (only use internally)
78 | */
79 | deactivate(): void {
80 | this._state = LayerState.INACTIVE;
81 | }
82 |
83 | /**
84 | * destroys this implementation, after destroying the implementation cannot be used anymore.
85 | */
86 | destroy(): void {
87 | this._initialized = false;
88 | this._state = LayerState.INACTIVE;
89 | this._map = undefined;
90 | super.destroy();
91 | }
92 | }
93 |
94 | export default LayerImplementation;
95 |
--------------------------------------------------------------------------------
/src/layer/layerState.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Enumeration of possible layer states.
3 | * State machine: inactive <-> loading -> active -> inactive
4 | */
5 | enum LayerState {
6 | INACTIVE = 1,
7 | ACTIVE = 2,
8 | LOADING = 4,
9 | }
10 |
11 | export default LayerState;
12 |
--------------------------------------------------------------------------------
/src/layer/layerSymbols.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Symbol to declare a layers name on its visualizations, e.g. ol.layer.Layer, Cesium.Cesium3DTileset*
3 | */
4 | export const vcsLayerName: unique symbol = Symbol('vcsLayerName');
5 |
6 | /**
7 | * Symbol added to Cesium3DTilesets to suppress picking.
8 | */
9 | export const allowPicking: unique symbol = Symbol('allowPicking');
10 |
--------------------------------------------------------------------------------
/src/layer/oblique/layerObliqueImpl.ts:
--------------------------------------------------------------------------------
1 | import type { Layer as OLLayer } from 'ol/layer.js';
2 | import LayerImplementation from '../layerImplementation.js';
3 | import { vcsLayerName } from '../layerSymbols.js';
4 | import type ObliqueMap from '../../map/obliqueMap.js';
5 |
6 | class LayerObliqueImpl extends LayerImplementation {
7 | olLayer: OLLayer | null = null;
8 |
9 | initialize(): Promise {
10 | if (!this.initialized) {
11 | this.olLayer = this.getOLLayer();
12 | this.olLayer[vcsLayerName] = this.name;
13 | this.map.addOLLayer(this.olLayer);
14 | }
15 | return super.initialize();
16 | }
17 |
18 | async activate(): Promise {
19 | await super.activate();
20 | if (this.active && this.olLayer) {
21 | this.olLayer.setVisible(true);
22 | }
23 | }
24 |
25 | deactivate(): void {
26 | super.deactivate();
27 | if (this.olLayer) {
28 | this.olLayer.setVisible(false);
29 | }
30 | }
31 |
32 | /**
33 | * returns the ol Layer
34 | */
35 | // eslint-disable-next-line class-methods-use-this
36 | getOLLayer(): OLLayer {
37 | throw new Error();
38 | }
39 |
40 | destroy(): void {
41 | if (this.olLayer) {
42 | this.map.removeOLLayer(this.olLayer);
43 | }
44 | this.olLayer = null;
45 | super.destroy();
46 | }
47 | }
48 |
49 | export default LayerObliqueImpl;
50 |
--------------------------------------------------------------------------------
/src/layer/openlayers/loadFunctionHelpers.ts:
--------------------------------------------------------------------------------
1 | import type { LoadFunction } from 'ol/Tile.js';
2 | import type { ImageTile } from 'ol';
3 | import TileState from 'ol/TileState.js';
4 | import { getInitForUrl, requestObjectUrl } from '../../util/fetch.js';
5 |
6 | // eslint-disable-next-line import/prefer-default-export
7 | export function getTileLoadFunction(
8 | headers: Record,
9 | ): LoadFunction {
10 | return function tileLoadFunction(imageTile, src): void {
11 | const image = (imageTile as ImageTile).getImage() as HTMLImageElement;
12 | const init = getInitForUrl(src, headers);
13 | requestObjectUrl(src, init)
14 | .then((blobUrl) => {
15 | image.src = blobUrl;
16 | image.onload = (): void => {
17 | URL.revokeObjectURL(blobUrl);
18 | };
19 | })
20 | .catch(() => {
21 | imageTile.setState(TileState.ERROR);
22 | });
23 | };
24 | }
25 |
--------------------------------------------------------------------------------
/src/layer/openlayers/openStreetMapOpenlayersImpl.ts:
--------------------------------------------------------------------------------
1 | import Tile from 'ol/layer/Tile.js';
2 | import OSM from 'ol/source/OSM.js';
3 | import RasterLayerOpenlayersImpl from './rasterLayerOpenlayersImpl.js';
4 |
5 | /**
6 | * represents a specific OpenStreetMapLayer layer for openlayers.
7 | */
8 | class OpenStreetMapOpenlayersImpl extends RasterLayerOpenlayersImpl {
9 | static get className(): string {
10 | return 'OpenStreetMapOpenlayersImpl';
11 | }
12 |
13 | getOLLayer(): Tile {
14 | return new Tile({
15 | opacity: this.opacity,
16 | source: new OSM({
17 | maxZoom: this.maxLevel,
18 | }),
19 | minZoom: this.minRenderingLevel,
20 | maxZoom: this.maxRenderingLevel,
21 | });
22 | }
23 | }
24 |
25 | export default OpenStreetMapOpenlayersImpl;
26 |
--------------------------------------------------------------------------------
/src/layer/openlayers/rasterLayerOpenlayersImpl.ts:
--------------------------------------------------------------------------------
1 | import LayerOpenlayersImpl from './layerOpenlayersImpl.js';
2 | import type {
3 | RasterLayerImplementation,
4 | RasterLayerImplementationOptions,
5 | TilingScheme,
6 | } from '../rasterLayer.js';
7 | import type Extent from '../../util/extent.js';
8 | import type OpenlayersMap from '../../map/openlayersMap.js';
9 |
10 | class RasterLayerOpenlayersImpl
11 | extends LayerOpenlayersImpl
12 | implements RasterLayerImplementation
13 | {
14 | static get className(): string {
15 | return 'RasterLayerOpenlayersImpl';
16 | }
17 |
18 | minLevel: number;
19 |
20 | maxLevel: number;
21 |
22 | minRenderingLevel: number | undefined;
23 |
24 | maxRenderingLevel: number | undefined;
25 |
26 | tilingSchema: TilingScheme;
27 |
28 | extent: Extent;
29 |
30 | opacity: number;
31 |
32 | constructor(map: OpenlayersMap, options: RasterLayerImplementationOptions) {
33 | super(map, options);
34 | this.minLevel = options.minLevel;
35 | this.maxLevel = options.maxLevel;
36 | this.minRenderingLevel = options.minRenderingLevel;
37 | this.maxRenderingLevel = options.maxRenderingLevel;
38 | this.tilingSchema = options.tilingSchema;
39 | this.extent = options.extent as Extent;
40 | this.opacity = options.opacity;
41 | }
42 |
43 | updateOpacity(opacity: number): void {
44 | this.opacity = opacity;
45 | if (this.initialized) {
46 | this.olLayer!.setOpacity(this.opacity);
47 | }
48 | }
49 | }
50 |
51 | export default RasterLayerOpenlayersImpl;
52 |
--------------------------------------------------------------------------------
/src/layer/openlayers/singleImageOpenlayersImpl.ts:
--------------------------------------------------------------------------------
1 | import ImageLayer from 'ol/layer/Image.js';
2 | import { TrustedServers } from '@vcmap-cesium/engine';
3 | import ImageStatic, {
4 | type Options as ImageStaticOptions,
5 | } from 'ol/source/ImageStatic.js';
6 | import RasterLayerOpenlayersImpl from './rasterLayerOpenlayersImpl.js';
7 | import { wgs84Projection } from '../../util/projection.js';
8 | import { isSameOrigin } from '../../util/urlHelpers.js';
9 | import type { SingleImageImplementationOptions } from '../singleImageLayer.js';
10 | import type OpenlayersMap from '../../map/openlayersMap.js';
11 | import { getInitForUrl, requestObjectUrl } from '../../util/fetch.js';
12 |
13 | /**
14 | * represents a specific OpenLayers SingleImageLayer Layer class.
15 | */
16 | class SingleImageOpenlayersImpl extends RasterLayerOpenlayersImpl {
17 | static get className(): string {
18 | return 'SingleImageOpenlayersImpl';
19 | }
20 |
21 | credit: string | undefined;
22 |
23 | constructor(map: OpenlayersMap, options: SingleImageImplementationOptions) {
24 | super(map, options);
25 | this.credit = options.credit;
26 | }
27 |
28 | /**
29 | * returns the ol Layer
30 | */
31 | getOLLayer(): ImageLayer {
32 | const options: ImageStaticOptions = {
33 | attributions: this.credit,
34 | url: this.url as string,
35 | projection: 'EPSG:4326',
36 | imageExtent: this.extent.getCoordinatesInProjection(wgs84Projection),
37 | };
38 | if (TrustedServers.contains(options.url)) {
39 | options.crossOrigin = 'use-credentials';
40 | } else if (!isSameOrigin(this.url as string)) {
41 | options.crossOrigin = 'anonymous';
42 | }
43 |
44 | if (this.headers) {
45 | options.imageLoadFunction = (imageWrapper, src): void => {
46 | const init = getInitForUrl(src, this.headers);
47 | requestObjectUrl(src, init)
48 | .then((blobUrl) => {
49 | const image = imageWrapper.getImage() as HTMLImageElement;
50 | image.src = blobUrl;
51 | image.onload = (): void => {
52 | URL.revokeObjectURL(blobUrl);
53 | };
54 | })
55 | .catch(() => {
56 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
57 | // @ts-ignore
58 | // eslint-disable-next-line @typescript-eslint/no-unsafe-call,no-underscore-dangle
59 | imageWrapper.handleImageError_();
60 | });
61 | };
62 | }
63 |
64 | return new ImageLayer({
65 | source: new ImageStatic(options),
66 | opacity: this.opacity,
67 | minZoom: this.minRenderingLevel,
68 | maxZoom: this.maxRenderingLevel,
69 | });
70 | }
71 | }
72 |
73 | export default SingleImageOpenlayersImpl;
74 |
--------------------------------------------------------------------------------
/src/layer/openlayers/tileDebugOpenlayersImpl.ts:
--------------------------------------------------------------------------------
1 | import Tile from 'ol/layer/Tile.js';
2 | import TileDebug from 'ol/source/TileDebug.js';
3 | import LayerOpenlayersImpl from './layerOpenlayersImpl.js';
4 | import { VectorTileImplementation } from '../vectorTileLayer.js';
5 | import type StyleItem from '../../style/styleItem.js';
6 |
7 | /**
8 | * layer Implementation to render tile boundaries.
9 | */
10 | class TileDebugOpenlayersImpl
11 | extends LayerOpenlayersImpl
12 | implements VectorTileImplementation
13 | {
14 | static get className(): string {
15 | return 'TileDebugOpenlayersImpl';
16 | }
17 |
18 | // eslint-disable-next-line class-methods-use-this
19 | getOLLayer(): Tile {
20 | return new Tile({
21 | source: new TileDebug(),
22 | });
23 | }
24 |
25 | // eslint-disable-next-line class-methods-use-this,no-unused-vars
26 | updateStyle(_styleItem: StyleItem, _silent?: boolean): void {}
27 |
28 | // eslint-disable-next-line class-methods-use-this,no-unused-vars
29 | updateTiles(_args: string[]): void {}
30 | }
31 |
32 | export default TileDebugOpenlayersImpl;
33 |
--------------------------------------------------------------------------------
/src/layer/openlayers/tmsOpenlayersImpl.ts:
--------------------------------------------------------------------------------
1 | import { TrustedServers } from '@vcmap-cesium/engine';
2 | import XYZ, { type Options as XYZOptions } from 'ol/source/XYZ.js';
3 | import Tile from 'ol/layer/Tile.js';
4 | import { type Options as TileOptions } from 'ol/layer/BaseTile.js';
5 | import type { Size } from 'ol/size.js';
6 | import { mercatorProjection } from '../../util/projection.js';
7 | import RasterLayerOpenlayersImpl from './rasterLayerOpenlayersImpl.js';
8 | import { TilingScheme } from '../rasterLayer.js';
9 | import { isSameOrigin } from '../../util/urlHelpers.js';
10 | import type { TMSImplementationOptions } from '../tmsLayer.js';
11 | import type OpenlayersMap from '../../map/openlayersMap.js';
12 | import { getTileLoadFunction } from './loadFunctionHelpers.js';
13 |
14 | /**
15 | * TmsLayer implementation for {@link OpenlayersMap}.
16 | */
17 | class TmsOpenlayersImpl extends RasterLayerOpenlayersImpl {
18 | static get className(): string {
19 | return 'TmsOpenlayersImpl';
20 | }
21 |
22 | format: string;
23 |
24 | tileSize: Size;
25 |
26 | /**
27 | * @param map
28 | * @param options
29 | */
30 | constructor(map: OpenlayersMap, options: TMSImplementationOptions) {
31 | super(map, options);
32 | this.format = options.format;
33 | this.tileSize = options.tileSize;
34 | }
35 |
36 | getOLLayer(): Tile {
37 | const sourceOptions: XYZOptions = {
38 | tileUrlFunction: (tileCoord) => {
39 | const baseUrl = this.url!.replace(/\/$/, '');
40 | const y = (1 << tileCoord[0]) - tileCoord[2] - 1;
41 | return `${baseUrl}/${tileCoord[0]}/${tileCoord[1]}/${y}.${this.format}`;
42 | },
43 | tileSize: this.tileSize,
44 | minZoom: this.minLevel,
45 | maxZoom: this.maxLevel,
46 | wrapX: false,
47 | };
48 | if (TrustedServers.contains(this.url as string)) {
49 | sourceOptions.crossOrigin = 'use-credentials';
50 | } else if (!isSameOrigin(this.url as string)) {
51 | sourceOptions.crossOrigin = 'anonymous';
52 | }
53 | if (this.tilingSchema === TilingScheme.GEOGRAPHIC) {
54 | sourceOptions.projection = 'EPSG:4326';
55 | }
56 | if (this.headers) {
57 | sourceOptions.tileLoadFunction = getTileLoadFunction(this.headers);
58 | }
59 |
60 | const tileOptions: TileOptions = {
61 | source: new XYZ(sourceOptions),
62 | opacity: this.opacity,
63 | minZoom: this.minRenderingLevel,
64 | maxZoom: this.maxRenderingLevel,
65 | };
66 | if (this.extent && this.extent.isValid()) {
67 | tileOptions.extent =
68 | this.extent.getCoordinatesInProjection(mercatorProjection);
69 | }
70 | return new Tile(tileOptions);
71 | }
72 | }
73 |
74 | export default TmsOpenlayersImpl;
75 |
--------------------------------------------------------------------------------
/src/layer/openlayers/wmsOpenlayersImpl.ts:
--------------------------------------------------------------------------------
1 | import Tile from 'ol/layer/Tile.js';
2 | import type { Size } from 'ol/size.js';
3 | import type TileWMS from 'ol/source/TileWMS.js';
4 | import ImageWMS from 'ol/source/ImageWMS.js';
5 | import ImageLayer from 'ol/layer/Image.js';
6 | import RasterLayerOpenlayersImpl from './rasterLayerOpenlayersImpl.js';
7 | import { getImageWMSSource, getWMSSource } from '../wmsHelpers.js';
8 | import type { WMSImplementationOptions } from '../wmsLayer.js';
9 | import type OpenlayersMap from '../../map/openlayersMap.js';
10 | import { mercatorProjection } from '../../util/projection.js';
11 |
12 | /**
13 | * represents a specific Cesium WmsOpenlayersImpl Layer class.
14 | */
15 | class WmsOpenlayersImpl extends RasterLayerOpenlayersImpl {
16 | static get className(): string {
17 | return 'WmsOpenlayersImpl';
18 | }
19 |
20 | parameters: Record;
21 |
22 | version: string;
23 |
24 | tileSize: Size;
25 |
26 | singleImage2d: boolean;
27 |
28 | constructor(map: OpenlayersMap, options: WMSImplementationOptions) {
29 | super(map, options);
30 | this.parameters = options.parameters;
31 | this.version = options.version;
32 | this.tileSize = options.tileSize;
33 | this.singleImage2d = options.singleImage2d;
34 | }
35 |
36 | getOLLayer(): Tile | ImageLayer {
37 | if (this.singleImage2d) {
38 | return new ImageLayer({
39 | extent: this.extent.getCoordinatesInProjection(mercatorProjection),
40 | visible: false,
41 | source: getImageWMSSource({
42 | url: this.url as string,
43 | parameters: this.parameters,
44 | tilingSchema: this.tilingSchema,
45 | version: this.version,
46 | headers: this.headers,
47 | }),
48 | opacity: this.opacity,
49 | minZoom: this.minRenderingLevel,
50 | maxZoom: this.maxRenderingLevel,
51 | });
52 | }
53 | return new Tile({
54 | visible: false,
55 | source: getWMSSource({
56 | url: this.url as string,
57 | parameters: this.parameters,
58 | version: this.version,
59 | extent: this.extent,
60 | tileSize: this.tileSize,
61 | minLevel: this.minLevel,
62 | maxLevel: this.maxLevel,
63 | tilingSchema: this.tilingSchema,
64 | headers: this.headers,
65 | }),
66 | opacity: this.opacity,
67 | minZoom: this.minRenderingLevel,
68 | maxZoom: this.maxRenderingLevel,
69 | });
70 | }
71 | }
72 |
73 | export default WmsOpenlayersImpl;
74 |
--------------------------------------------------------------------------------
/src/layer/tileLoadedHelper.ts:
--------------------------------------------------------------------------------
1 | import type { Globe } from '@vcmap-cesium/engine';
2 | import CesiumTilesetCesiumImpl from './cesium/cesiumTilesetCesiumImpl.js';
3 | import CesiumTilesetLayer from './cesiumTilesetLayer.js';
4 | import FeatureStoreLayer from './featureStoreLayer.js';
5 |
6 | function waitForImplTilesLoaded(
7 | impl: CesiumTilesetCesiumImpl,
8 | timeout?: number,
9 | ): Promise {
10 | return new Promise((resolve) => {
11 | let timeoutNr: number | undefined | NodeJS.Timeout;
12 | const remover =
13 | impl.cesium3DTileset?.allTilesLoaded.addEventListener(() => {
14 | if (timeoutNr) {
15 | clearTimeout(timeoutNr);
16 | }
17 | remover();
18 | resolve();
19 | }) ?? ((): void => {});
20 |
21 | if (timeout != null) {
22 | timeoutNr = setTimeout(() => {
23 | remover();
24 | resolve();
25 | }, timeout);
26 | }
27 | });
28 | }
29 |
30 | export async function tiledLayerLoaded(
31 | layer: CesiumTilesetLayer | FeatureStoreLayer,
32 | timeout?: number,
33 | ): Promise {
34 | const impls = layer
35 | .getImplementations()
36 | .filter((i) => i instanceof CesiumTilesetCesiumImpl);
37 | if (!layer.active || impls.every((i) => i.cesium3DTileset?.tilesLoaded)) {
38 | return;
39 | }
40 |
41 | await Promise.all(impls.map((i) => waitForImplTilesLoaded(i, timeout)));
42 | }
43 |
44 | export function globeLoaded(globe: Globe, timeout?: number): Promise {
45 | if (globe.tilesLoaded) {
46 | return Promise.resolve();
47 | }
48 |
49 | return new Promise((resolve) => {
50 | let timeoutNr: number | undefined | NodeJS.Timeout;
51 | const remover = globe.tileLoadProgressEvent.addEventListener((count) => {
52 | if (count < 1) {
53 | if (timeoutNr) {
54 | clearTimeout(timeoutNr);
55 | }
56 | remover();
57 | resolve();
58 | }
59 | });
60 |
61 | if (timeout != null) {
62 | timeoutNr = setTimeout(() => {
63 | remover();
64 | resolve();
65 | }, timeout);
66 | }
67 | });
68 | }
69 |
--------------------------------------------------------------------------------
/src/layer/tileProvider/staticFeatureTileProvider.ts:
--------------------------------------------------------------------------------
1 | import { Feature } from 'ol';
2 | import TileProvider, { TileProviderOptions } from './tileProvider.js';
3 |
4 | export type StaticFeatureTileProviderOptions = Omit<
5 | TileProviderOptions,
6 | 'baseLevels'
7 | > & {
8 | features: Feature[];
9 | };
10 |
11 | export default class StaticFeatureTileProvider extends TileProvider {
12 | static get className(): string {
13 | return 'StaticFeatureTileProvider';
14 | }
15 |
16 | static getDefaultOptions(): StaticFeatureTileProviderOptions {
17 | return {
18 | ...TileProvider.getDefaultOptions(),
19 | features: [],
20 | };
21 | }
22 |
23 | private _features: Feature[];
24 |
25 | constructor(options: StaticFeatureTileProviderOptions) {
26 | const defaultOptions = StaticFeatureTileProvider.getDefaultOptions();
27 | super({ ...options, baseLevels: [0] });
28 | this._features = options.features || defaultOptions.features;
29 | }
30 |
31 | // eslint-disable-next-line no-unused-vars
32 | loader(
33 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
34 | _x: number,
35 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
36 | _y: number,
37 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
38 | _z: number,
39 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
40 | _headers?: Record,
41 | ): Promise {
42 | return Promise.resolve(this._features);
43 | }
44 |
45 | toJSON(): StaticFeatureTileProviderOptions {
46 | const config: TileProviderOptions = super.toJSON();
47 |
48 | delete config.baseLevels;
49 | const staticFeatureConfig: StaticFeatureTileProviderOptions = {
50 | ...structuredClone(config),
51 | features: this._features,
52 | };
53 |
54 | return staticFeatureConfig;
55 | }
56 |
57 | destroy(): void {
58 | this._features = [];
59 | super.destroy();
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/layer/tileProvider/staticGeojsonTileProvider.ts:
--------------------------------------------------------------------------------
1 | import type { GeoJSONObject } from 'ol/format/GeoJSON.js';
2 | import type { Feature } from 'ol/index.js';
3 | import { parseGeoJSON } from '../geojsonHelpers.js';
4 | import TileProvider, { TileProviderOptions } from './tileProvider.js';
5 | import { getInitForUrl, requestJson } from '../../util/fetch.js';
6 | import { tileProviderClassRegistry } from '../../classRegistry.js';
7 |
8 | export type StaticGeoJSONTileProviderOptions = TileProviderOptions & {
9 | url: string;
10 | };
11 |
12 | /**
13 | * Loads the provided geojson url and tiles the content in memory, data is only requested once
14 | */
15 | class StaticGeoJSONTileProvider extends TileProvider {
16 | static get className(): string {
17 | return 'StaticGeoJSONTileProvider';
18 | }
19 |
20 | static getDefaultOptions(): StaticGeoJSONTileProviderOptions {
21 | return {
22 | ...TileProvider.getDefaultOptions(),
23 | url: '',
24 | baseLevels: [0],
25 | };
26 | }
27 |
28 | url: string;
29 |
30 | constructor(options: StaticGeoJSONTileProviderOptions) {
31 | const defaultOptions = StaticGeoJSONTileProvider.getDefaultOptions();
32 | super({ ...options, baseLevels: defaultOptions.baseLevels });
33 |
34 | this.url = options.url || defaultOptions.url;
35 | }
36 |
37 | // eslint-disable-next-line no-unused-vars
38 | async loader(
39 | _x: number,
40 | _y: number,
41 | _z: number,
42 | headers?: Record,
43 | ): Promise {
44 | const init = getInitForUrl(this.url, headers);
45 | const data = await requestJson(this.url, init);
46 | const { features } = parseGeoJSON(data, { dynamicStyle: true });
47 | return features;
48 | }
49 |
50 | toJSON(): StaticGeoJSONTileProviderOptions {
51 | const config: Partial = super.toJSON();
52 | delete config.baseLevels;
53 |
54 | if (this.url) {
55 | config.url = this.url;
56 | }
57 | return config as StaticGeoJSONTileProviderOptions;
58 | }
59 | }
60 |
61 | export default StaticGeoJSONTileProvider;
62 | tileProviderClassRegistry.registerClass(
63 | StaticGeoJSONTileProvider.className,
64 | StaticGeoJSONTileProvider,
65 | );
66 |
--------------------------------------------------------------------------------
/src/layer/vectorSymbols.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Attached to a geometry to indicate, it is already in mercator and not the layers default projection
3 | */
4 | export const alreadyTransformedToMercator: unique symbol = Symbol(
5 | 'alreadyTransformedToMercator',
6 | );
7 |
8 | /**
9 | * Attached to a geometry to indicate, it is already in oblique image coordiantes and not mercator
10 | */
11 | export const alreadyTransformedToImage: unique symbol = Symbol(
12 | 'alreadyTransformedToImage',
13 | );
14 |
15 | /**
16 | * Attached to an ol/Feature to reference the underlying oblique geometry
17 | */
18 | export const obliqueGeometry: unique symbol = Symbol('obliqueGeometry');
19 |
20 | /**
21 | * Attached to an ol/Feature which should only exist in oblqie coordinates and not be transformed to mercator on change
22 | */
23 | export const doNotTransform: unique symbol = Symbol('doNotTransform');
24 |
25 | /**
26 | * Attached to oblique features to reference the underlying original ol/Feature
27 | */
28 | export const originalFeatureSymbol: unique symbol = Symbol('OriginalFeature');
29 |
30 | /**
31 | * Attached to mercator or oblique geometries which are polygons but have a circular counterpart. Used to not
32 | * mess up circle drawing in oblique
33 | */
34 | export const actuallyIsCircle: unique symbol = Symbol('ActuallyIsCircle');
35 |
36 | /**
37 | * Can be attached to features to have the primitives be created sync instead of async. Use this
38 | * for faster response times to changes. Do not use this on bulk insertion etc. since sync creation blocks
39 | * the rendering thread
40 | */
41 | export const createSync: unique symbol = Symbol('createSync');
42 |
43 | /**
44 | * Can be present on ol/Feature to indicate the current primitives / billboards / models / labels associated with this feature
45 | */
46 | export const primitives: unique symbol = Symbol('primitives');
47 |
--------------------------------------------------------------------------------
/src/map/mapState.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * The state of a map.
3 | * State machine: inactive <-> loading -> active -> inactive
4 | */
5 | enum MapState {
6 | INACTIVE = 1,
7 | ACTIVE = 2,
8 | LOADING = 4,
9 | }
10 |
11 | export default MapState;
12 |
--------------------------------------------------------------------------------
/src/map/navigation/controller/controller.ts:
--------------------------------------------------------------------------------
1 | import { Math as CesiumMath } from '@vcmap-cesium/engine';
2 | import { v4 as uuidv4 } from 'uuid';
3 | import {
4 | ControllerInput,
5 | checkThreshold,
6 | multiplyComponents,
7 | } from './controllerInput.js';
8 |
9 | export type ControllerOptions = {
10 | id: string;
11 | scales?: ControllerInput;
12 | inputThreshold?: number;
13 | };
14 |
15 | class Controller {
16 | static get className(): string {
17 | return 'Controller';
18 | }
19 |
20 | static getDefaultOptions(): ControllerOptions {
21 | return {
22 | id: '',
23 | scales: undefined,
24 | inputThreshold: CesiumMath.EPSILON1,
25 | };
26 | }
27 |
28 | readonly id: string;
29 |
30 | scales?: ControllerInput;
31 |
32 | inputThreshold: number;
33 |
34 | constructor(options: ControllerOptions) {
35 | const defaultOptions = Controller.getDefaultOptions();
36 |
37 | this.id = options.id || uuidv4();
38 | this.scales = options.scales || defaultOptions.scales;
39 | this.inputThreshold =
40 | options.inputThreshold || defaultOptions.inputThreshold!;
41 | }
42 |
43 | // eslint-disable-next-line class-methods-use-this
44 | setMapTarget(_target: HTMLElement | null): void {}
45 |
46 | // eslint-disable-next-line class-methods-use-this
47 | getControllerInput(): ControllerInput | null {
48 | return null;
49 | }
50 |
51 | getInputs(): ControllerInput | null {
52 | const input = this.getControllerInput();
53 | if (input) {
54 | if (checkThreshold(input, this.inputThreshold)) {
55 | return this.scales
56 | ? multiplyComponents(input, this.scales, input)
57 | : input;
58 | }
59 | }
60 | return null;
61 | }
62 |
63 | toJSON(): ControllerOptions {
64 | const defaultOptions = Controller.getDefaultOptions();
65 | const config: ControllerOptions = {
66 | id: this.id,
67 | };
68 | if (this.scales) {
69 | config.scales = this.scales;
70 | }
71 | if (defaultOptions.inputThreshold !== this.inputThreshold) {
72 | config.inputThreshold = this.inputThreshold;
73 | }
74 | return config;
75 | }
76 |
77 | // eslint-disable-next-line class-methods-use-this
78 | destroy(): void {}
79 | }
80 |
81 | export default Controller;
82 |
--------------------------------------------------------------------------------
/src/map/navigation/easingHelper.ts:
--------------------------------------------------------------------------------
1 | import { type Movement } from './navigation.js';
2 | import {
3 | ControllerInput,
4 | getZeroInput,
5 | lerpRound,
6 | } from './controller/controllerInput.js';
7 |
8 | const inputScratch = getZeroInput();
9 |
10 | export type NavigationEasing = {
11 | startTime: number;
12 | target: ControllerInput;
13 | getMovementAtTime(time: number): {
14 | movement: Movement;
15 | finished: boolean;
16 | };
17 | };
18 |
19 | export function createEasing(
20 | startTime: number,
21 | duration: number,
22 | origin: ControllerInput = getZeroInput(),
23 | target: ControllerInput = getZeroInput(),
24 | ): NavigationEasing {
25 | return {
26 | startTime,
27 | target,
28 | getMovementAtTime(time: number): {
29 | movement: Movement;
30 | finished: boolean;
31 | } {
32 | const normalizedTime = (time - startTime) / duration;
33 | if (normalizedTime < 1) {
34 | const movement: Movement = {
35 | time: normalizedTime,
36 | duration,
37 | input: lerpRound(origin, target, normalizedTime, inputScratch, 3),
38 | };
39 | return { movement, finished: time >= startTime + duration };
40 | }
41 | return {
42 | movement: {
43 | time: normalizedTime,
44 | duration,
45 | input: structuredClone(target),
46 | },
47 | finished: true,
48 | };
49 | },
50 | };
51 | }
52 |
--------------------------------------------------------------------------------
/src/map/navigation/navigationImpl.ts:
--------------------------------------------------------------------------------
1 | import VcsMap from '../vcsMap.js';
2 | import { Movement } from './navigation.js';
3 |
4 | export type NavigationImplOptions = {
5 | /**
6 | * base translation speed in m/s
7 | */
8 | baseTranSpeed?: number;
9 | /**
10 | * base rotation speed in rad/s
11 | */
12 | baseRotSpeed?: number;
13 | };
14 |
15 | class NavigationImpl {
16 | static get className(): string {
17 | return 'NavigationImpl';
18 | }
19 |
20 | static getDefaultOptions(): NavigationImplOptions {
21 | return {
22 | baseTranSpeed: 0.02, // 20 m/s
23 | baseRotSpeed: 0.02, // 20 rad/s
24 | };
25 | }
26 |
27 | protected _map: M;
28 |
29 | /**
30 | * base translation speed in m/s
31 | */
32 | baseTranSpeed: number;
33 |
34 | /**
35 | * base rotation speed in rad/s
36 | */
37 | baseRotSpeed: number;
38 |
39 | constructor(map: M, options?: NavigationImplOptions) {
40 | const defaultOptions = NavigationImpl.getDefaultOptions();
41 | this._map = map;
42 | this.baseTranSpeed =
43 | options?.baseTranSpeed || defaultOptions.baseTranSpeed!;
44 | this.baseRotSpeed = options?.baseRotSpeed || defaultOptions.baseRotSpeed!;
45 | }
46 |
47 | /**
48 | * Update the camera movement and rotation with easing applied.
49 | */
50 | // eslint-disable-next-line class-methods-use-this,@typescript-eslint/no-unused-vars
51 | update(_movement: Movement): void {}
52 |
53 | toJSON(): NavigationImplOptions {
54 | const defaultOptions = NavigationImpl.getDefaultOptions();
55 | const config: NavigationImplOptions = {};
56 | if (this.baseTranSpeed !== defaultOptions.baseTranSpeed) {
57 | config.baseTranSpeed = this.baseTranSpeed;
58 | }
59 | if (this.baseRotSpeed !== defaultOptions.baseRotSpeed) {
60 | config.baseRotSpeed = this.baseRotSpeed;
61 | }
62 | return config;
63 | }
64 | }
65 |
66 | export default NavigationImpl;
67 |
--------------------------------------------------------------------------------
/src/map/navigation/openlayersNavigation.ts:
--------------------------------------------------------------------------------
1 | import OpenlayersMap from '../openlayersMap.js';
2 | import NavigationImpl, { NavigationImplOptions } from './navigationImpl.js';
3 | import { Movement } from './navigation.js';
4 | import { moveView } from './viewHelper.js';
5 |
6 | export type OpenlayersNavigationOptions = NavigationImplOptions;
7 |
8 | class OpenlayersNavigation extends NavigationImpl {
9 | static get className(): string {
10 | return 'OpenlayersNavigation';
11 | }
12 |
13 | static getDefaultOptions(): OpenlayersNavigationOptions {
14 | return { ...NavigationImpl.getDefaultOptions() };
15 | }
16 |
17 | update(movement: Movement): void {
18 | moveView(this._map, movement.input, this.baseTranSpeed);
19 | }
20 | }
21 |
22 | export default OpenlayersNavigation;
23 |
--------------------------------------------------------------------------------
/src/map/navigation/viewHelper.ts:
--------------------------------------------------------------------------------
1 | import BaseOLMap from '../baseOLMap.js';
2 | import { getScaleFromDistance } from './cameraHelper.js';
3 | import { ControllerInput } from './controller/controllerInput.js';
4 |
5 | // eslint-disable-next-line import/prefer-default-export
6 | export function moveView(
7 | map: BaseOLMap,
8 | input: ControllerInput,
9 | baseTranSpeed: number,
10 | ): void {
11 | const view = map.olMap?.getView();
12 | if (view) {
13 | if (Math.abs(input.up) > 0) {
14 | const zoom = view.getZoom();
15 | if (zoom) {
16 | view.setZoom(zoom - input.up * baseTranSpeed);
17 | }
18 | }
19 |
20 | if (Math.abs(input.forward) > 0 || Math.abs(input.right) > 0) {
21 | const distance = map.getViewpointSync()?.distance ?? 16;
22 | const scale = getScaleFromDistance(distance);
23 | const center = view.getCenter();
24 | if (center) {
25 | view.setCenter([
26 | center[0] + input.right * baseTranSpeed * scale,
27 | center[1] + input.forward * baseTranSpeed * scale,
28 | ]);
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/moduleIdSymbol.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/prefer-default-export
2 | export const moduleIdSymbol: unique symbol = Symbol('moduleId');
3 |
--------------------------------------------------------------------------------
/src/oblique/defaultObliqueCollection.ts:
--------------------------------------------------------------------------------
1 | import type { Coordinate } from 'ol/coordinate.js';
2 | import ObliqueCollection from './obliqueCollection.js';
3 | import ObliqueImage, { isDefaultImageSymbol } from './obliqueImage.js';
4 | import ObliqueImageMeta from './obliqueImageMeta.js';
5 | import { ObliqueViewDirection } from './obliqueViewDirection.js';
6 | import { mercatorProjection } from '../util/projection.js';
7 |
8 | const defaultMeta = new ObliqueImageMeta({
9 | name: 'defaultObliqueMeta',
10 | size: [512, 512],
11 | tileSize: [512, 512],
12 | tileResolution: [1],
13 | projection: mercatorProjection,
14 | format: 'png',
15 | url: '',
16 | });
17 |
18 | /**
19 | * This is a special oblique collection wich is shown, if no other oblique collection is set on an ObliqueMap map.
20 | * It will render a single image which indicates that no images can be loaded.
21 | */
22 | class DefaultObliqueCollection extends ObliqueCollection {
23 | constructor() {
24 | super({});
25 | }
26 |
27 | getImageForCoordinate(
28 | mercatorCoordinate: Coordinate,
29 | _viewDirection: ObliqueViewDirection,
30 | ): ObliqueImage {
31 | const groundCoordinates = [
32 | [mercatorCoordinate[0] - 100, mercatorCoordinate[1] - 100, 0],
33 | [mercatorCoordinate[0] + 100, mercatorCoordinate[1] - 100, 0],
34 | [mercatorCoordinate[0] + 100, mercatorCoordinate[1] + 100, 0],
35 | [mercatorCoordinate[0] - 100, mercatorCoordinate[1] + 100, 0],
36 | ];
37 |
38 | const image = new ObliqueImage({
39 | meta: defaultMeta,
40 | viewDirection: ObliqueViewDirection.NORTH,
41 | viewDirectionAngle: 0,
42 | name: this.name,
43 | groundCoordinates,
44 | centerPointOnGround: mercatorCoordinate,
45 | });
46 |
47 | image[isDefaultImageSymbol] = true;
48 | return image;
49 | }
50 | }
51 |
52 | export default DefaultObliqueCollection;
53 |
--------------------------------------------------------------------------------
/src/oblique/obliqueViewDirection.ts:
--------------------------------------------------------------------------------
1 | export enum ObliqueViewDirection {
2 | NORTH = 1,
3 | EAST = 2,
4 | SOUTH = 3,
5 | WEST = 4,
6 | NADIR = 5,
7 | }
8 |
9 | export const obliqueViewDirectionNames = {
10 | north: ObliqueViewDirection.NORTH,
11 | east: ObliqueViewDirection.EAST,
12 | south: ObliqueViewDirection.SOUTH,
13 | west: ObliqueViewDirection.WEST,
14 | nadir: ObliqueViewDirection.NADIR,
15 | };
16 |
17 | export function getDirectionName(
18 | direction: ObliqueViewDirection,
19 | ): string | undefined {
20 | const entry = Object.entries(obliqueViewDirectionNames).find(
21 | ([, namedDirection]) => namedDirection === direction,
22 | );
23 |
24 | return entry?.[0];
25 | }
26 |
--------------------------------------------------------------------------------
/src/ol/geojson.d.ts:
--------------------------------------------------------------------------------
1 | import { GeoJsonProperties, Geometry } from 'geojson';
2 | import type { VcsMeta } from '../layer/vectorProperties.js';
3 | import { FeatureStoreLayerState } from '../layer/featureStoreLayerState.js';
4 |
5 | declare module 'geojson' {
6 | interface Point {
7 | olcs_radius?: number;
8 | }
9 |
10 | interface Feature<
11 | G extends Geometry | null = Geometry,
12 | P = GeoJsonProperties,
13 | > {
14 | _id?: string;
15 | radius?: G extends Point ? number : never;
16 | vcsMeta?: VcsMeta;
17 | state?: FeatureStoreLayerState;
18 | }
19 |
20 | interface FeatureCollection {
21 | crs?:
22 | | { type: 'name'; properties: { name: string } }
23 | | { type: 'EPSG'; properties: { code: string } };
24 | vcsMeta?: VcsMeta;
25 | vcsEmbeddedIcons?: string[];
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/ol/geom/circle.ts:
--------------------------------------------------------------------------------
1 | import type { Coordinate } from 'ol/coordinate.js';
2 | import type { GeometryLayout } from 'ol/geom/Geometry.js';
3 | import Circle from 'ol/geom/Circle.js';
4 | import { check } from '@vcsuite/check';
5 | import { cartesian2DDistance, cartesian3DDistance } from '../../util/math.js';
6 |
7 | /**
8 | * @returns {Array} returns an Array where the first coordinate is the center, and the second the center with an x offset of radius
9 | */
10 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
11 | // @ts-ignore
12 | Circle.prototype.getCoordinates = function getCoordinates(
13 | this: Circle,
14 | ): Coordinate[] {
15 | return [this.getCenter(), this.getLastCoordinate()];
16 | };
17 |
18 | /**
19 | * @param {Array} coordinates - array of length two. The first coordinate is treated as the center, the second as the center with an x offset of radius
20 | * @param {import("ol/geom/Geometry").GeometryLayout=} optLayout
21 | */
22 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
23 | // @ts-ignore
24 | Circle.prototype.setCoordinates = function setCoordinates(
25 | this: Circle,
26 | coordinates: [Coordinate, Coordinate],
27 | optLayout?: GeometryLayout,
28 | ): void {
29 | check(coordinates as [Coordinate, Coordinate], [[Number]]);
30 | check(coordinates.length, 2);
31 |
32 | const layout = optLayout || this.getLayout();
33 | const getRadius = /XYM?/.test(layout)
34 | ? cartesian2DDistance
35 | : cartesian3DDistance;
36 | this.setCenterAndRadius(
37 | coordinates[0],
38 | getRadius(coordinates[0], coordinates[1]),
39 | optLayout,
40 | );
41 | };
42 |
--------------------------------------------------------------------------------
/src/ol/source/ClusterEnhancedVectorSource.ts:
--------------------------------------------------------------------------------
1 | import VectorSource from 'ol/source/Vector.js';
2 | import Feature from 'ol/Feature.js';
3 |
4 | /**
5 | * @class
6 | * @extends {import("ol/source").Vector}
7 | * @memberOf ol
8 | */
9 | class ClusterEnhancedVectorSource extends VectorSource {
10 | /**
11 | * @param {import("ol").Feature} feature
12 | * @param {boolean=} silent
13 | */
14 | removeFeature(feature: Feature, silent?: boolean): void {
15 | if (!feature) {
16 | return;
17 | }
18 | const removed = this.removeFeatureInternal(feature);
19 | if (removed && !silent) {
20 | this.changed();
21 | }
22 | }
23 |
24 | /**
25 | * @param {import("ol").Feature} feature
26 | * @param {boolean=} silent
27 | */
28 | addFeature(feature: Feature, silent?: boolean): void {
29 | this.addFeatureInternal(feature);
30 | if (!silent) {
31 | this.changed();
32 | }
33 | }
34 | }
35 |
36 | export default ClusterEnhancedVectorSource;
37 |
--------------------------------------------------------------------------------
/src/ol/source/VcsCluster.ts:
--------------------------------------------------------------------------------
1 | import Cluster, { type Options } from 'ol/source/Cluster.js';
2 | import Feature from 'ol/Feature.js';
3 | import { Point } from 'ol/geom.js';
4 | import { vectorClusterGroupName } from '../../vectorCluster/vectorClusterSymbols.js';
5 | import { hidden } from '../../layer/featureVisibility.js';
6 |
7 | /**
8 | * @class
9 | * @extends {import("ol/source/Cluster").default}
10 | * @memberOf ol
11 | */
12 | class VcsCluster extends Cluster {
13 | private _paused = false;
14 |
15 | constructor(
16 | props: Options,
17 | private _name: string,
18 | ) {
19 | props.geometryFunction =
20 | props.geometryFunction ??
21 | ((feature: Feature): Point | null => {
22 | if (feature[hidden]) {
23 | return null;
24 | }
25 | return feature.getGeometry() as Point;
26 | });
27 |
28 | super(props);
29 | /**
30 | * @type {boolean}
31 | */
32 | this._paused = false;
33 | }
34 |
35 | addFeatures(features: Feature[]): void {
36 | features.forEach((f) => {
37 | f[vectorClusterGroupName] = this._name;
38 | });
39 | super.addFeatures(features);
40 | }
41 |
42 | get paused(): boolean {
43 | return this._paused;
44 | }
45 |
46 | set paused(pause: boolean) {
47 | this._paused = pause;
48 | }
49 |
50 | refresh(): void {
51 | if (this._paused) {
52 | return;
53 | }
54 | super.refresh();
55 | }
56 | }
57 |
58 | export default VcsCluster;
59 |
--------------------------------------------------------------------------------
/src/style/modelFill.ts:
--------------------------------------------------------------------------------
1 | import { Fill } from 'ol/style.js';
2 |
3 | class ModelFill extends Fill {
4 | static fromFill(fill: Fill): ModelFill {
5 | return new ModelFill({ color: fill.getColor() });
6 | }
7 |
8 | toFill(result?: Fill): Fill {
9 | const fill = result ?? new Fill();
10 | fill.setColor(this.getColor());
11 | return fill;
12 | }
13 | }
14 |
15 | export default ModelFill;
16 |
--------------------------------------------------------------------------------
/src/style/shapesCategory.ts:
--------------------------------------------------------------------------------
1 | import Fill from 'ol/style/Fill.js';
2 | import Stroke from 'ol/style/Stroke.js';
3 | import RegularShape, {
4 | type Options as RegularShapeOptions,
5 | } from 'ol/style/RegularShape.js';
6 | import Circle, { type Options as CircleOptions } from 'ol/style/Circle.js';
7 | import type { VectorStyleItemImage } from './vectorStyleItem.js';
8 |
9 | export function getShapeFromOptions(
10 | options: VectorStyleItemImage,
11 | ): RegularShape | Circle {
12 | if (options.fill && !(options.fill instanceof Fill)) {
13 | options.fill = new Fill(options.fill);
14 | }
15 | if (options.stroke && !(options.stroke instanceof Stroke)) {
16 | options.stroke = new Stroke(options.stroke);
17 | }
18 | return options.points
19 | ? new RegularShape(options as RegularShapeOptions)
20 | : new Circle(options as CircleOptions);
21 | }
22 |
23 | class ShapeCategory {
24 | shapes: VectorStyleItemImage[] = [];
25 |
26 | addImage(options: VectorStyleItemImage): void {
27 | const shape = getShapeFromOptions({ ...options });
28 |
29 | const canvas = shape.getImage(1);
30 | options.src = canvas.toDataURL();
31 | this.shapes.push(options);
32 | }
33 | }
34 |
35 | /**
36 | * TODO refactor to getdefaultShapeCategory...
37 | */
38 | export const shapeCategory = new ShapeCategory();
39 | const defaultShapeOptions = {
40 | fill: new Fill({ color: [255, 255, 255, 1] }),
41 | stroke: new Stroke({ color: [0, 0, 0, 1], width: 1 }),
42 | radius: 16,
43 | };
44 | [
45 | null,
46 | { points: 3 },
47 | { points: 3, angle: Math.PI },
48 | { points: 4, angle: Math.PI / 4 },
49 | { points: 6 },
50 | ].forEach((additionalOptions) => {
51 | const shapeOptions = additionalOptions
52 | ? Object.assign(additionalOptions, defaultShapeOptions)
53 | : defaultShapeOptions;
54 |
55 | shapeCategory.addImage(shapeOptions);
56 | });
57 |
--------------------------------------------------------------------------------
/src/style/styleFactory.ts:
--------------------------------------------------------------------------------
1 | import { is, oneOf } from '@vcsuite/check';
2 | import StyleItem, { StyleItemOptions } from './styleItem.js';
3 | import {
4 | DeclarativeStyleItemOptions,
5 | defaultDeclarativeStyle,
6 | } from './declarativeStyleItem.js';
7 | import { styleClassRegistry } from '../classRegistry.js';
8 | import VectorStyleItem, { VectorStyleItemOptions } from './vectorStyleItem.js';
9 |
10 | // eslint-disable-next-line import/prefer-default-export
11 | export function getStyleOrDefaultStyle(
12 | styleOptions?:
13 | | DeclarativeStyleItemOptions
14 | | VectorStyleItemOptions
15 | | StyleItem,
16 | defaultStyle?: StyleItem,
17 | ): StyleItem {
18 | if (is(styleOptions, oneOf(StyleItem, { type: String }))) {
19 | if (styleOptions instanceof StyleItem) {
20 | return styleOptions;
21 | } else {
22 | const styleItem = styleClassRegistry.createFromTypeOptions(
23 | styleOptions as StyleItemOptions,
24 | );
25 | if (styleItem) {
26 | if (
27 | styleItem instanceof VectorStyleItem &&
28 | defaultStyle instanceof VectorStyleItem
29 | ) {
30 | return styleItem.assign(defaultStyle.clone().assign(styleItem));
31 | }
32 | return styleItem;
33 | }
34 | }
35 | }
36 |
37 | return defaultStyle || defaultDeclarativeStyle.clone();
38 | }
39 |
--------------------------------------------------------------------------------
/src/style/writeStyle.ts:
--------------------------------------------------------------------------------
1 | import VectorStyleItem, { VectorStyleItemOptions } from './vectorStyleItem.js';
2 | import DeclarativeStyleItem from './declarativeStyleItem.js';
3 | import { type VcsMeta, vcsMetaVersion } from '../layer/vectorProperties.js';
4 | import StyleItem from './styleItem.js';
5 |
6 | export function embedIconsInStyle(
7 | obj: VectorStyleItemOptions,
8 | embeddedIcons?: string[],
9 | ): VectorStyleItemOptions {
10 | if (obj.image && obj.image.src && /^data:/.test(obj.image.src)) {
11 | if (embeddedIcons) {
12 | let index = embeddedIcons.indexOf(obj.image.src);
13 | if (index === -1) {
14 | embeddedIcons.push(obj.image.src);
15 | index = embeddedIcons.length - 1;
16 | }
17 | obj.image.src = `:${index}`;
18 | } else {
19 | obj.image = {
20 | // XXX is this the correct fallback?
21 | radius: 5,
22 | };
23 | }
24 | }
25 | return obj;
26 | }
27 |
28 | function writeStyle(
29 | style: StyleItem,
30 | vcsMeta: VcsMeta = { version: vcsMetaVersion },
31 | ): VcsMeta {
32 | // XXX this entire function is not what is to be expected. feature store expects styles as refs to be possible
33 | if (style instanceof VectorStyleItem) {
34 | vcsMeta.style = embedIconsInStyle(style.toJSON(), vcsMeta.embeddedIcons);
35 | } else if (style instanceof DeclarativeStyleItem) {
36 | vcsMeta.style = style.toJSON();
37 | }
38 | return vcsMeta;
39 | }
40 |
41 | export default writeStyle;
42 |
--------------------------------------------------------------------------------
/src/util/clipping/clippingPolygonHelper.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Cesium3DTileset,
3 | ClippingPolygon,
4 | ClippingPolygonCollection,
5 | Globe,
6 | } from '@vcmap-cesium/engine';
7 | import type CesiumMap from '../../map/cesiumMap.js';
8 | import { vcsLayerName } from '../../layer/layerSymbols.js';
9 |
10 | export function getTargetTilesets(
11 | map: CesiumMap,
12 | layerNames: string[] | 'all' = 'all',
13 | ): Cesium3DTileset[] {
14 | const tilesets = map
15 | .getVisualizations()
16 | .filter((v) => v instanceof Cesium3DTileset);
17 | if (Array.isArray(layerNames)) {
18 | return tilesets.filter((v) => layerNames.includes(v[vcsLayerName]));
19 | }
20 | return tilesets;
21 | }
22 |
23 | export function addClippingPolygon(
24 | clippee: Globe | Cesium3DTileset,
25 | polygon: ClippingPolygon | undefined,
26 | ): void {
27 | if (polygon) {
28 | if (clippee.clippingPolygons === undefined) {
29 | clippee.clippingPolygons = new ClippingPolygonCollection();
30 | }
31 | if (!clippee.clippingPolygons.contains(polygon)) {
32 | clippee.clippingPolygons.setDirty();
33 | clippee.clippingPolygons.add(polygon);
34 | }
35 | }
36 | }
37 |
38 | export function removeClippingPolygon(
39 | clippee: Globe | Cesium3DTileset,
40 | polygon: ClippingPolygon | undefined,
41 | ): void {
42 | if (
43 | polygon &&
44 | clippee.clippingPolygons &&
45 | clippee.clippingPolygons.contains(polygon)
46 | ) {
47 | clippee.clippingPolygons.remove(polygon);
48 | }
49 | }
50 |
51 | export function addClippingPolygonObjectToMap(
52 | map: CesiumMap,
53 | polygon: ClippingPolygon | undefined,
54 | terrain: boolean,
55 | layerNames: string[] | 'all',
56 | ): void {
57 | if (terrain) {
58 | const globe = map.getScene()?.globe;
59 | if (globe) {
60 | addClippingPolygon(globe, polygon);
61 | }
62 | }
63 |
64 | const tilesets = getTargetTilesets(map, layerNames);
65 | tilesets.forEach((tileset) => {
66 | addClippingPolygon(tileset, polygon);
67 | });
68 | }
69 |
70 | export function removeClippingPolygonFromMap(
71 | map: CesiumMap,
72 | polygon: ClippingPolygon | undefined,
73 | terrain: boolean,
74 | layerNames: string[] | 'all',
75 | ): void {
76 | if (terrain) {
77 | const globe = map.getScene()?.globe;
78 | if (globe) {
79 | removeClippingPolygon(globe, polygon);
80 | }
81 | }
82 |
83 | const tilesets = getTargetTilesets(map, layerNames);
84 | tilesets.forEach((tileset) => {
85 | removeClippingPolygon(tileset, polygon);
86 | });
87 | }
88 |
--------------------------------------------------------------------------------
/src/util/editor/editorSymbols.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Symbol to identify a {@link Vertex}
3 | */
4 | export const vertexSymbol = Symbol('Vertex');
5 | /**
6 | * Symbol to denote the vertexes index in the vertices array. This is important for snapping & bbox operations
7 | */
8 | export const vertexIndexSymbol = Symbol('VertexIndex');
9 | /**
10 | * Symbol added to primitives and features to denote that these are handlers. It is expected, that the value of the symobl is
11 | * equal to an {@link AxisAndPlanes}
12 | */
13 | export const handlerSymbol = Symbol('Handler');
14 | /**
15 | * Symbol to identify which was the last editor mouse over handler that edited the cursor style.
16 | */
17 | export const mouseOverSymbol = Symbol('MouseOver');
18 |
--------------------------------------------------------------------------------
/src/util/editor/interactions/createPointInteraction.ts:
--------------------------------------------------------------------------------
1 | import Point from 'ol/geom/Point.js';
2 | import AbstractInteraction, {
3 | EventAfterEventHandler,
4 | } from '../../../interaction/abstractInteraction.js';
5 | import VcsEvent from '../../../vcsEvent.js';
6 | import { EventType } from '../../../interaction/interactionType.js';
7 | import {
8 | alreadyTransformedToImage,
9 | alreadyTransformedToMercator,
10 | } from '../../../layer/vectorSymbols.js';
11 | import ObliqueMap from '../../../map/obliqueMap.js';
12 | import { CreateInteraction } from '../createFeatureSession.js';
13 |
14 | /**
15 | * @extends {AbstractInteraction}
16 | * @implements {CreateInteraction}
17 | */
18 | class CreatePointInteraction
19 | extends AbstractInteraction
20 | implements CreateInteraction
21 | {
22 | private _geometry: Point | null = null;
23 |
24 | finished = new VcsEvent();
25 |
26 | created = new VcsEvent();
27 |
28 | constructor() {
29 | super(EventType.CLICK);
30 | this.setActive();
31 | }
32 |
33 | pipe(event: EventAfterEventHandler): Promise {
34 | this._geometry = new Point(event.positionOrPixel);
35 | if (event.map instanceof ObliqueMap) {
36 | this._geometry[alreadyTransformedToImage] = true;
37 | } else {
38 | this._geometry[alreadyTransformedToMercator] = true;
39 | }
40 | this.created.raiseEvent(this._geometry);
41 | this.finish();
42 | return Promise.resolve(event);
43 | }
44 |
45 | /**
46 | * Finish the current creation. Calls finish and sets the interaction to be inactive
47 | */
48 | finish(): void {
49 | if (this.active !== EventType.NONE) {
50 | this.setActive(false);
51 | this.finished.raiseEvent(this._geometry);
52 | }
53 | }
54 |
55 | destroy(): void {
56 | this.finished.destroy();
57 | this.created.destroy();
58 | super.destroy();
59 | }
60 | }
61 |
62 | export default CreatePointInteraction;
63 |
--------------------------------------------------------------------------------
/src/util/editor/interactions/editFeaturesMouseOverInteraction.ts:
--------------------------------------------------------------------------------
1 | import type { Feature } from 'ol/index.js';
2 | import { handlerSymbol, mouseOverSymbol } from '../editorSymbols.js';
3 | import AbstractInteraction, {
4 | EventAfterEventHandler,
5 | } from '../../../interaction/abstractInteraction.js';
6 | import {
7 | ModificationKeyType,
8 | EventType,
9 | } from '../../../interaction/interactionType.js';
10 | import { cursorMap } from './editGeometryMouseOverInteraction.js';
11 |
12 | /**
13 | * A class to handle mouse over effects on features for editor sessions.
14 | * @extends {AbstractInteraction}
15 | */
16 | class EditFeaturesMouseOverInteraction extends AbstractInteraction {
17 | private _currentHandler: Feature | null = null;
18 |
19 | cursorStyle: CSSStyleDeclaration | undefined;
20 |
21 | constructor() {
22 | super(EventType.MOVE, ModificationKeyType.NONE);
23 |
24 | this.setActive();
25 | }
26 |
27 | pipe(event: EventAfterEventHandler): Promise {
28 | if (event.feature && (event.feature as Feature)[handlerSymbol]) {
29 | this._currentHandler = event.feature as Feature;
30 | } else {
31 | this._currentHandler = null;
32 | }
33 | if (!this.cursorStyle && event.map?.target) {
34 | this.cursorStyle = event.map.target.style;
35 | }
36 | this._evaluate();
37 | return Promise.resolve(event);
38 | }
39 |
40 | setActive(active?: boolean | number): void {
41 | super.setActive(active);
42 | this.reset();
43 | }
44 |
45 | /**
46 | * Reset the cursorStyle to auto
47 | */
48 | reset(): void {
49 | if (this.cursorStyle && this.cursorStyle.cursor) {
50 | this.cursorStyle.cursor = cursorMap.auto;
51 | this.cursorStyle = undefined;
52 | }
53 | }
54 |
55 | private _evaluate(): void {
56 | if (!this.cursorStyle) {
57 | return;
58 | }
59 | if (this._currentHandler) {
60 | this.cursorStyle.cursor = cursorMap.translate;
61 | this.cursorStyle[mouseOverSymbol] = this.id;
62 | } else if (this.cursorStyle?.[mouseOverSymbol] === this.id) {
63 | this.cursorStyle.cursor = cursorMap.auto;
64 | delete this.cursorStyle[mouseOverSymbol];
65 | }
66 | }
67 |
68 | destroy(): void {
69 | this.reset();
70 | super.destroy();
71 | }
72 | }
73 |
74 | export default EditFeaturesMouseOverInteraction;
75 |
--------------------------------------------------------------------------------
/src/util/editor/interactions/ensureHandlerSelectionInteraction.ts:
--------------------------------------------------------------------------------
1 | import type { Feature } from 'ol/index.js';
2 | import type { Scene } from '@vcmap-cesium/engine';
3 | import AbstractInteraction, {
4 | EventAfterEventHandler,
5 | } from '../../../interaction/abstractInteraction.js';
6 | import { EventType } from '../../../interaction/interactionType.js';
7 | import { handlerSymbol } from '../editorSymbols.js';
8 | import CesiumMap from '../../../map/cesiumMap.js';
9 |
10 | /**
11 | * This interaction ensure a potential handler is dragged in 3D when it is obscured by a transparent feature.
12 | * It uses drillPick on MOVE if: the map is 3D, there is a feature at said position, there is a feature selected in
13 | * the feature selection & the feature at the position is _not_ a handler
14 | */
15 | class EnsureHandlerSelectionInteraction extends AbstractInteraction {
16 | private _featureSelection: Feature[];
17 |
18 | /**
19 | * @param selectedFeatures Reference to the selected features.
20 | */
21 | constructor(selectedFeatures: Feature[]) {
22 | super(EventType.DRAGSTART | EventType.MOVE);
23 |
24 | this._featureSelection = selectedFeatures;
25 | }
26 |
27 | pipe(event: EventAfterEventHandler): Promise {
28 | if (
29 | event.feature &&
30 | this._featureSelection.length > 0 &&
31 | !(event.feature as Feature)[handlerSymbol] &&
32 | event.map instanceof CesiumMap
33 | ) {
34 | const scene = event.map.getScene() as Scene;
35 | const drillPicks = scene.drillPick(
36 | event.windowPosition,
37 | undefined,
38 | 10,
39 | 10,
40 | ) as { primitive?: { olFeature?: Feature } }[];
41 | const handler = drillPicks.find((p) => {
42 | return p?.primitive?.olFeature?.[handlerSymbol];
43 | });
44 | if (handler) {
45 | event.feature = handler.primitive!.olFeature;
46 | }
47 | }
48 | return Promise.resolve(event);
49 | }
50 | }
51 |
52 | export default EnsureHandlerSelectionInteraction;
53 |
--------------------------------------------------------------------------------
/src/util/editor/interactions/removeVertexInteraction.ts:
--------------------------------------------------------------------------------
1 | import AbstractInteraction, {
2 | EventAfterEventHandler,
3 | } from '../../../interaction/abstractInteraction.js';
4 | import {
5 | EventType,
6 | ModificationKeyType,
7 | } from '../../../interaction/interactionType.js';
8 | import VcsEvent from '../../../vcsEvent.js';
9 | import { isVertex, Vertex } from '../editorHelpers.js';
10 |
11 | /**
12 | * This interaction will raise the passed in event for each feature clicked with the vertex symbol
13 | * @extends {AbstractInteraction}
14 | */
15 | class RemoveVertexInteraction extends AbstractInteraction {
16 | vertexRemoved = new VcsEvent();
17 |
18 | constructor() {
19 | super(EventType.CLICK, ModificationKeyType.SHIFT);
20 | this.setActive();
21 | }
22 |
23 | pipe(event: EventAfterEventHandler): Promise {
24 | if (isVertex(event.feature)) {
25 | this.vertexRemoved.raiseEvent(event.feature);
26 | }
27 | return Promise.resolve(event);
28 | }
29 |
30 | destroy(): void {
31 | this.vertexRemoved.destroy();
32 | super.destroy();
33 | }
34 | }
35 |
36 | export default RemoveVertexInteraction;
37 |
--------------------------------------------------------------------------------
/src/util/editor/interactions/rightClickInteraction.ts:
--------------------------------------------------------------------------------
1 | import AbstractInteraction, {
2 | InteractionEvent,
3 | } from '../../../interaction/abstractInteraction.js';
4 | import {
5 | EventType,
6 | ModificationKeyType,
7 | PointerKeyType,
8 | } from '../../../interaction/interactionType.js';
9 | import VcsEvent from '../../../vcsEvent.js';
10 |
11 | function timeout(ms: number): Promise {
12 | return new Promise((resolve) => {
13 | setTimeout(resolve, ms);
14 | });
15 | }
16 |
17 | export default class RightClickInteraction extends AbstractInteraction {
18 | rightClicked = new VcsEvent();
19 |
20 | eventChainFinished = new VcsEvent();
21 |
22 | constructor() {
23 | super(EventType.CLICK, ModificationKeyType.NONE, PointerKeyType.RIGHT);
24 | }
25 |
26 | async pipe(event: InteractionEvent): Promise {
27 | this.rightClicked.raiseEvent();
28 | event.chainEnded?.addEventListener(() => {
29 | this.eventChainFinished.raiseEvent();
30 | });
31 | // we need to wait a bit, otherwise the changing features in the rightClicked Event do not take effect before
32 | // the next interaction.
33 | await timeout(0);
34 | return event;
35 | }
36 |
37 | destroy(): void {
38 | this.rightClicked.destroy();
39 | this.eventChainFinished.destroy();
40 | super.destroy();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/util/editor/interactions/translateVertexInteraction.ts:
--------------------------------------------------------------------------------
1 | import AbstractInteraction, {
2 | EventAfterEventHandler,
3 | } from '../../../interaction/abstractInteraction.js';
4 | import {
5 | EventType,
6 | ModificationKeyType,
7 | } from '../../../interaction/interactionType.js';
8 | import VcsEvent from '../../../vcsEvent.js';
9 | import { isVertex, Vertex } from '../editorHelpers.js';
10 | import { emptyStyle } from '../../../style/styleHelpers.js';
11 |
12 | /**
13 | * Class to translate a vertex. Will call the passed in vertex changed event with the changed vertex.
14 | * Will modify the vertex in place
15 | */
16 | class TranslateVertexInteraction extends AbstractInteraction {
17 | readonly vertexChanged = new VcsEvent();
18 |
19 | private _vertex: Vertex | null = null;
20 |
21 | constructor() {
22 | super(
23 | EventType.DRAGEVENTS,
24 | ModificationKeyType.NONE | ModificationKeyType.CTRL,
25 | );
26 | this.setActive();
27 | }
28 |
29 | pipe(event: EventAfterEventHandler): Promise {
30 | if (this._vertex) {
31 | this._vertex.getGeometry()!.setCoordinates(event.positionOrPixel);
32 | this.vertexChanged.raiseEvent(this._vertex);
33 |
34 | if (event.type & EventType.DRAGEND) {
35 | const vertex = this._vertex;
36 | setTimeout(() => {
37 | // timeout to avoid picking the vertex in pickFromRay on the next pass
38 | vertex.setStyle(undefined);
39 | });
40 | this._vertex = null;
41 | }
42 | } else if (event.type & EventType.DRAGSTART && isVertex(event.feature)) {
43 | this._vertex = event.feature;
44 | this._vertex.setStyle(emptyStyle);
45 | }
46 | return Promise.resolve(event);
47 | }
48 |
49 | destroy(): void {
50 | this.vertexChanged.destroy();
51 | super.destroy();
52 | }
53 | }
54 |
55 | export default TranslateVertexInteraction;
56 |
--------------------------------------------------------------------------------
/src/util/editor/transformation/transformationTypes.ts:
--------------------------------------------------------------------------------
1 | import { Color } from '@vcmap-cesium/engine';
2 | import type { Coordinate } from 'ol/coordinate.js';
3 | import type { Feature } from 'ol/index.js';
4 |
5 | /**
6 | * Handlers are map specific transformation handlers wich enable the use of the transformation interactions.
7 | * There visualization is {@link TransformationMode} specific. Do not create these handlers yourself
8 | * use {@link createTransformationHandler} instead.
9 | */
10 | export type Handlers = {
11 | show: boolean;
12 | /**
13 | * update the center of the handlers
14 | */
15 | setCenter(center: Coordinate): void;
16 | /**
17 | * highlight the given axis
18 | */
19 | showAxis: AxisAndPlanes;
20 | /**
21 | * display Z axis handlers in grey and do not allow them to be picked
22 | */
23 | greyOutZ: boolean;
24 | destroy(): void;
25 | };
26 |
27 | export type TransformationHandler = {
28 | translate(x: number, y: number, z: number): void;
29 | /**
30 | * Copy of the handlers current center
31 | */
32 | readonly center: Coordinate;
33 | showAxis: AxisAndPlanes;
34 | showing: boolean;
35 | setFeatures(feature: Feature[]): void;
36 | destroy(): void;
37 | };
38 |
39 | export enum AxisAndPlanes {
40 | X = 'X',
41 | Y = 'Y',
42 | Z = 'Z',
43 | XY = 'XY',
44 | XZ = 'XZ',
45 | YZ = 'YZ',
46 | XYZ = 'XYZ',
47 | NONE = 'NONE',
48 | }
49 | export enum TransformationMode {
50 | TRANSLATE = 'translate',
51 | ROTATE = 'rotate',
52 | SCALE = 'scale',
53 | EXTRUDE = 'extrude',
54 | }
55 |
56 | export const greyedOutColor = Color.GRAY.withAlpha(0.5);
57 |
58 | export function is1DAxis(axis: AxisAndPlanes): boolean {
59 | return (
60 | axis === AxisAndPlanes.X ||
61 | axis === AxisAndPlanes.Y ||
62 | axis === AxisAndPlanes.Z
63 | );
64 | }
65 |
66 | export function is2DAxis(axis: AxisAndPlanes): boolean {
67 | return (
68 | axis === AxisAndPlanes.XY ||
69 | axis === AxisAndPlanes.XZ ||
70 | axis === AxisAndPlanes.YZ
71 | );
72 | }
73 |
74 | export function is3DAxis(axis: AxisAndPlanes): boolean {
75 | return axis === AxisAndPlanes.XYZ;
76 | }
77 |
--------------------------------------------------------------------------------
/src/util/editor/validateGeoemetry.ts:
--------------------------------------------------------------------------------
1 | import type { Geometry, LineString, Polygon, Point, Circle } from 'ol/geom.js';
2 | import { validateLineString } from '../featureconverter/lineStringToCesium.js';
3 | import { validatePolygon } from '../featureconverter/polygonToCesium.js';
4 | import { validatePoint } from '../featureconverter/pointToCesium.js';
5 | import { validateCircle } from '../featureconverter/circleToCesium.js';
6 |
7 | export default function geometryIsValid(geometry?: Geometry): boolean {
8 | if (!geometry) {
9 | return false;
10 | }
11 | const type = geometry.getType();
12 | if (type === 'LineString') {
13 | return validateLineString(geometry as LineString);
14 | } else if (type === 'Polygon') {
15 | return validatePolygon(geometry as Polygon);
16 | } else if (type === 'Point') {
17 | return validatePoint(geometry as Point);
18 | } else if (type === 'Circle') {
19 | return validateCircle(geometry as Circle);
20 | }
21 | return false;
22 | }
23 |
--------------------------------------------------------------------------------
/src/util/featureconverter/arcToCesium.ts:
--------------------------------------------------------------------------------
1 | import { ArcType, HeightReference } from '@vcmap-cesium/engine';
2 | import type { Coordinate } from 'ol/coordinate.js';
3 | import type { LineString } from 'ol/geom.js';
4 |
5 | import {
6 | createGroundLineGeometries,
7 | createLineGeometries,
8 | createOutlineGeometries,
9 | createSolidGeometries,
10 | validateLineString,
11 | getGeometryOptions as getLineStringGeometryOptions,
12 | } from './lineStringToCesium.js';
13 | import {
14 | mercatorToCartesianTransformerForHeightInfo,
15 | VectorHeightInfo,
16 | } from './vectorHeightInfo.js';
17 | import {
18 | PolylineGeometryOptions,
19 | VectorGeometryFactory,
20 | } from './vectorGeometryFactory.js';
21 |
22 | /**
23 | * Creates the positions & arcType option for the PolylineGeometry
24 | */
25 | function getGeometryOptions(
26 | coords: Coordinate[],
27 | _geometry: LineString,
28 | heightInfo: VectorHeightInfo,
29 | ): PolylineGeometryOptions {
30 | const coordinateTransformer =
31 | mercatorToCartesianTransformerForHeightInfo(heightInfo);
32 | const positions = coords.map(coordinateTransformer);
33 | return { positions, arcType: ArcType.NONE };
34 | }
35 |
36 | /**
37 | * @param arcCoords - the coordinates of the arc to use instead of the geometries coordinates if height mode is absolute
38 | * @param altitudeMode
39 | */
40 | // eslint-disable-next-line import/prefer-default-export
41 | export function getArcGeometryFactory(
42 | arcCoords: Coordinate[],
43 | altitudeMode: HeightReference,
44 | ): VectorGeometryFactory<'arc'> {
45 | return {
46 | type: 'arc',
47 | getGeometryOptions:
48 | altitudeMode === HeightReference.NONE
49 | ? getGeometryOptions.bind(null, arcCoords)
50 | : getLineStringGeometryOptions,
51 | createSolidGeometries,
52 | createOutlineGeometries,
53 | createFillGeometries(): never[] {
54 | return [];
55 | },
56 | createGroundLineGeometries,
57 | createLineGeometries,
58 | validateGeometry: validateLineString,
59 | };
60 | }
61 |
--------------------------------------------------------------------------------
/src/util/fetch.ts:
--------------------------------------------------------------------------------
1 | import { TrustedServers } from '@vcmap-cesium/engine';
2 |
3 | export async function requestUrl(
4 | url: string,
5 | init?: RequestInit,
6 | ): Promise {
7 | const response = await fetch(url, init);
8 | if (!response.ok) {
9 | throw new Error(
10 | `Failed fetching url ${url} with status: ${response.status}`,
11 | );
12 | }
13 | return response;
14 | }
15 |
16 | export async function requestJson(
17 | url: string,
18 | init?: RequestInit,
19 | ): Promise {
20 | const response = await requestUrl(url, init);
21 | return response.json() as Promise;
22 | }
23 |
24 | export async function requestArrayBuffer(
25 | url: string,
26 | init?: RequestInit,
27 | ): Promise {
28 | const response = await requestUrl(url, init);
29 | return response.arrayBuffer();
30 | }
31 |
32 | export async function requestObjectUrl(
33 | url: string,
34 | init?: RequestInit,
35 | ): Promise {
36 | const response = await requestUrl(url, init);
37 | const blob = await response.blob();
38 | return URL.createObjectURL(blob);
39 | }
40 |
41 | export function getInitForUrl(
42 | url: string,
43 | headers?: Record,
44 | ): RequestInit {
45 | const init: RequestInit = {};
46 | if (headers) {
47 | init.headers = headers;
48 | }
49 | if (TrustedServers.contains(url)) {
50 | init.credentials = 'include';
51 | }
52 | return init;
53 | }
54 |
--------------------------------------------------------------------------------
/src/util/flight/flightCollection.ts:
--------------------------------------------------------------------------------
1 | import Collection from '../collection.js';
2 | import { createFlightPlayer, FlightPlayer } from './flightPlayer.js';
3 | import VcsEvent from '../../vcsEvent.js';
4 | import FlightInstance from './flightInstance.js';
5 | import type VcsApp from '../../vcsApp.js';
6 |
7 | /**
8 | * A collection of flights. Provides playFlight API, which returns a FlightPlayer.
9 | * Emits playerChanged event, whenever another flight is played.
10 | */
11 | class FlightCollection extends Collection {
12 | private readonly _app: VcsApp;
13 |
14 | private _player: FlightPlayer | undefined;
15 |
16 | playerChanged: VcsEvent;
17 |
18 | private _playerDestroyedListener: () => void;
19 |
20 | constructor(app: VcsApp) {
21 | super();
22 |
23 | this._app = app;
24 | this._player = undefined;
25 | this.playerChanged = new VcsEvent();
26 | this._playerDestroyedListener = (): void => {};
27 | }
28 |
29 | get player(): FlightPlayer | undefined {
30 | return this._player;
31 | }
32 |
33 | remove(item: FlightInstance): void {
34 | if (this._player?.flightInstanceName === item.name) {
35 | this._player.stop();
36 | this._player.destroy();
37 | }
38 | super.remove(item);
39 | }
40 |
41 | /**
42 | * Creates a FlightPlayer for a flight instance, if not already existing for provided instance
43 | * @param flight
44 | */
45 | async setPlayerForFlight(
46 | flight: FlightInstance,
47 | ): Promise {
48 | if (this._player?.flightInstanceName === flight.name) {
49 | return this._player;
50 | } else if (this._player) {
51 | this._playerDestroyedListener();
52 | this._player.stop();
53 | this._player.destroy();
54 | }
55 | this._player = await createFlightPlayer(flight, this._app);
56 | this.playerChanged.raiseEvent(this._player);
57 | this._playerDestroyedListener = this._player.destroyed.addEventListener(
58 | () => {
59 | this._player = undefined;
60 | this.playerChanged.raiseEvent(undefined);
61 | },
62 | );
63 | return this._player;
64 | }
65 |
66 | destroy(): void {
67 | if (this._player) {
68 | this._player.stop();
69 | this._player.destroy();
70 | this._player = undefined;
71 | }
72 | this.playerChanged.destroy();
73 | this._playerDestroyedListener();
74 | super.destroy();
75 | }
76 | }
77 |
78 | export default FlightCollection;
79 |
--------------------------------------------------------------------------------
/src/util/hiddenObjects.ts:
--------------------------------------------------------------------------------
1 | import type GlobalHider from '../layer/globalHider.js';
2 | import makeOverrideCollection, {
3 | OverrideCollection,
4 | } from './overrideCollection.js';
5 | import Collection from './collection.js';
6 | import { moduleIdSymbol } from '../moduleIdSymbol.js';
7 |
8 | export type HiddenObject = {
9 | id: string;
10 | [moduleIdSymbol]?: string;
11 | };
12 |
13 | export function createHiddenObjectsCollection(
14 | getDynamicModuleId: () => string,
15 | globalHider: GlobalHider,
16 | ): OverrideCollection {
17 | const collection = makeOverrideCollection(
18 | new Collection('id'),
19 | getDynamicModuleId,
20 | );
21 |
22 | collection.added.addEventListener(({ id }) => {
23 | globalHider.hideObjects([id]);
24 | });
25 |
26 | collection.replaced.addEventListener(({ new: item }) => {
27 | globalHider.showObjects([item.id]);
28 | });
29 |
30 | collection.removed.addEventListener(({ id }) => {
31 | globalHider.showObjects([id]);
32 | });
33 |
34 | return collection;
35 | }
36 |
--------------------------------------------------------------------------------
/src/util/isMobile.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * returns true if we are on a mobile device including tablets
3 | */
4 | // eslint-disable-next-line import/prefer-default-export
5 | export function isMobile(): boolean {
6 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
7 | // @ts-ignore
8 | const agent =
9 | navigator.userAgent || navigator.vendor || (window.opera as string);
10 | return (
11 | /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(
12 | agent,
13 | ) ||
14 | /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test(
15 | agent.substring(0, 4),
16 | )
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/src/util/locale.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/prefer-default-export */
2 | /**
3 | * returns the default browserLocale, if not possible 'en'
4 | */
5 | export function detectBrowserLocale(): string {
6 | if (navigator.language) {
7 | const lang = navigator.language;
8 | return lang.substring(0, 2);
9 | }
10 | return 'en';
11 | }
12 |
--------------------------------------------------------------------------------
/src/util/urlHelpers.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/prefer-default-export
2 | export function isSameOrigin(source: string): boolean {
3 | const { location } = window;
4 | const url = new URL(
5 | source,
6 | `${location.protocol}//${location.host}${location.pathname}`,
7 | );
8 | // for instance data: URIs have no host information and are implicitly same origin
9 | // see https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#inherited_origins
10 | if (!url.host) {
11 | return true;
12 | }
13 | return url.protocol === location.protocol && url.host === location.host;
14 | }
15 |
--------------------------------------------------------------------------------
/src/vcsEvent.ts:
--------------------------------------------------------------------------------
1 | type Listener = (event: T) => Promise | void;
2 |
3 | class VcsEvent {
4 | private _listeners: Set> = new Set();
5 |
6 | /**
7 | * The number of listeners
8 | */
9 | get numberOfListeners(): number {
10 | return this._listeners.size;
11 | }
12 |
13 | /**
14 | * Adds an event listener. An event listener can only be added once.
15 | * A listener added multiple times will only be called once.
16 | * @param listener
17 | * @returns - remove callback. call this function to remove the listener
18 | */
19 | addEventListener(listener: Listener): () => void {
20 | this._listeners.add(listener);
21 | return () => {
22 | this.removeEventListener(listener);
23 | };
24 | }
25 |
26 | /**
27 | * Removes the provided listener
28 | * @param listener
29 | * @returns - whether a listener was removed
30 | */
31 | removeEventListener(listener: Listener): boolean {
32 | if (this._listeners.has(listener)) {
33 | this._listeners.delete(listener);
34 | return true;
35 | }
36 |
37 | return false;
38 | }
39 |
40 | /**
41 | * Raise the event, calling all listeners, if a listener is removed in between calling listeners, the listener is not
42 | * called.
43 | * @param event
44 | */
45 | raiseEvent(event: T): void {
46 | [...this._listeners].forEach((cb) => {
47 | if (this._listeners.has(cb)) {
48 | // eslint-disable-next-line no-void
49 | void cb(event);
50 | }
51 | });
52 | }
53 |
54 | async awaitRaisedEvent(event: T): Promise {
55 | const promises: (void | Promise)[] = [];
56 | [...this._listeners].forEach((cb) => {
57 | if (this._listeners.has(cb)) {
58 | promises.push(cb(event));
59 | }
60 | });
61 | await Promise.all(promises);
62 | }
63 |
64 | /**
65 | * clears all listeners
66 | */
67 | destroy(): void {
68 | this._listeners.clear();
69 | }
70 | }
71 |
72 | export default VcsEvent;
73 |
--------------------------------------------------------------------------------
/src/vcsObject.ts:
--------------------------------------------------------------------------------
1 | import { v4 as uuidv4 } from 'uuid';
2 | import { getLogger, type Logger } from '@vcsuite/logger';
3 | import { moduleIdSymbol } from './moduleIdSymbol.js';
4 |
5 | export type VcsObjectOptions = {
6 | /**
7 | * the type of object, typically only used in configs
8 | */
9 | type?: string;
10 | /**
11 | * name of the object, if not given a uuid is generated, is used for the framework functions getObjectByName
12 | */
13 | name?: string;
14 | /**
15 | * key value store for framework independent values per Object
16 | */
17 | properties?: Record;
18 | };
19 |
20 | /**
21 | * baseclass for all Objects
22 | */
23 | class VcsObject {
24 | static get className(): string {
25 | return 'VcsObject';
26 | }
27 |
28 | /**
29 | * unique Name
30 | */
31 | readonly name: string;
32 |
33 | properties: Record;
34 |
35 | isDestroyed: boolean;
36 |
37 | [moduleIdSymbol]?: string;
38 |
39 | constructor(options: VcsObjectOptions) {
40 | this.name = options.name || uuidv4();
41 | this.properties = options.properties || {};
42 | this.isDestroyed = false;
43 | }
44 |
45 | get className(): string {
46 | return (this.constructor as typeof VcsObject).className;
47 | }
48 |
49 | getLogger(): Logger {
50 | return getLogger(this.className);
51 | }
52 |
53 | toJSON(): VcsObjectOptions {
54 | const config: VcsObjectOptions = {
55 | type: this.className,
56 | name: this.name,
57 | };
58 |
59 | if (Object.keys(this.properties).length > 0) {
60 | config.properties = JSON.parse(JSON.stringify(this.properties)) as Record<
61 | string,
62 | unknown
63 | >;
64 | }
65 |
66 | return config;
67 | }
68 |
69 | destroy(): void {
70 | this.isDestroyed = true;
71 | this.properties = {};
72 | }
73 | }
74 |
75 | export default VcsObject;
76 |
--------------------------------------------------------------------------------
/src/vectorCluster/vectorClusterGroupCollection.ts:
--------------------------------------------------------------------------------
1 | import { check } from '@vcsuite/check';
2 | import VectorClusterGroup from './vectorClusterGroup.js';
3 | import Collection from '../util/collection.js';
4 | import GlobalHider from '../layer/globalHider.js';
5 |
6 | export default class VectorClusterGroupCollection extends Collection {
7 | /**
8 | * The global hider for this collection.
9 | */
10 | private _globalHider: GlobalHider;
11 |
12 | constructor(globalHider: GlobalHider) {
13 | super();
14 | this._globalHider = globalHider;
15 |
16 | this.added.addEventListener((g) => {
17 | g.setGlobalHider(this._globalHider);
18 | });
19 | this.removed.addEventListener((g) => {
20 | g.setGlobalHider();
21 | });
22 | }
23 |
24 | /**
25 | * The current global hider of these layers
26 | */
27 | get globalHider(): GlobalHider {
28 | return this._globalHider;
29 | }
30 |
31 | /**
32 | * The current global hider of these layers
33 | * @param globalHider
34 | */
35 | set globalHider(globalHider: GlobalHider) {
36 | check(globalHider, GlobalHider);
37 |
38 | this._globalHider = globalHider;
39 | this._array.forEach((vectorClusterGroup) => {
40 | vectorClusterGroup.setGlobalHider(this._globalHider);
41 | });
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/vectorCluster/vectorClusterGroupObliqueImpl.ts:
--------------------------------------------------------------------------------
1 | import OLVectorLayer from 'ol/layer/Vector.js';
2 | import VectorClusterGroupImpl from './vectorClusterGroupImpl.js';
3 | import ObliqueMap from '../map/obliqueMap.js';
4 | import VcsCluster from '../ol/source/VcsCluster.js';
5 | import { VectorClusterGroupImplementationOptions } from './vectorClusterGroup.js';
6 | import {
7 | createSourceObliqueSync,
8 | SourceObliqueSync,
9 | } from '../layer/oblique/sourceObliqueSync.js';
10 | import { vectorClusterGroupName } from './vectorClusterSymbols.js';
11 |
12 | export default class VectorClusterGroupObliqueImpl extends VectorClusterGroupImpl {
13 | private _clusterSource: VcsCluster;
14 |
15 | private _olLayer: OLVectorLayer | undefined;
16 |
17 | private _sourceObliqueSync: SourceObliqueSync;
18 |
19 | constructor(
20 | map: ObliqueMap,
21 | options: VectorClusterGroupImplementationOptions,
22 | ) {
23 | super(map, options);
24 |
25 | this._sourceObliqueSync = createSourceObliqueSync(options.source, map);
26 | this._clusterSource = new VcsCluster(
27 | {
28 | source: this._sourceObliqueSync.obliqueSource,
29 | distance: options.clusterDistance,
30 | },
31 | this.name,
32 | );
33 | }
34 |
35 | get clusterSource(): VcsCluster {
36 | return this._clusterSource;
37 | }
38 |
39 | get olLayer(): OLVectorLayer | undefined {
40 | return this._olLayer;
41 | }
42 |
43 | async initialize(): Promise {
44 | if (!this.initialized) {
45 | const olLayer = new OLVectorLayer({
46 | visible: false,
47 | source: this._clusterSource,
48 | style: this.style,
49 | });
50 | olLayer[vectorClusterGroupName] = this.name;
51 | this._olLayer = olLayer;
52 | this.map.addOLLayer(this._olLayer);
53 | }
54 | await super.initialize();
55 | }
56 |
57 | async activate(): Promise {
58 | await super.activate();
59 | if (this.active) {
60 | this._olLayer?.setVisible(true);
61 | this._clusterSource.paused = false;
62 | this._clusterSource.refresh();
63 | this._sourceObliqueSync.activate();
64 | }
65 | }
66 |
67 | deactivate(): void {
68 | super.deactivate();
69 | this._olLayer?.setVisible(false);
70 | this._clusterSource.paused = true;
71 | this._sourceObliqueSync.deactivate();
72 | }
73 |
74 | destroy(): void {
75 | if (this._olLayer) {
76 | this.map.removeOLLayer(this._olLayer);
77 | }
78 | this._olLayer = undefined;
79 |
80 | this._sourceObliqueSync.destroy();
81 | super.destroy();
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/vectorCluster/vectorClusterGroupOpenlayersImpl.ts:
--------------------------------------------------------------------------------
1 | import OLVectorLayer from 'ol/layer/Vector.js';
2 | import { VectorClusterGroupImplementationOptions } from './vectorClusterGroup.js';
3 | import OpenlayersMap from '../map/openlayersMap.js';
4 | import VcsCluster from '../ol/source/VcsCluster.js';
5 | import VectorClusterGroupImpl from './vectorClusterGroupImpl.js';
6 | import { vectorClusterGroupName } from './vectorClusterSymbols.js';
7 |
8 | export default class VectorClusterGroupOpenlayersImpl extends VectorClusterGroupImpl {
9 | static get className(): string {
10 | return 'VectorClusterGroupOpenlayersImpl';
11 | }
12 |
13 | private _clusterSource: VcsCluster;
14 |
15 | private _olLayer: OLVectorLayer | undefined;
16 |
17 | constructor(
18 | map: OpenlayersMap,
19 | options: VectorClusterGroupImplementationOptions,
20 | ) {
21 | super(map, options);
22 | this._clusterSource = new VcsCluster(
23 | {
24 | source: options.source,
25 | distance: options.clusterDistance,
26 | },
27 | this.name,
28 | );
29 | this._clusterSource.paused = true;
30 | }
31 |
32 | async initialize(): Promise {
33 | if (!this.initialized) {
34 | const olLayer = new OLVectorLayer({
35 | visible: false,
36 | source: this._clusterSource,
37 | style: this.style,
38 | });
39 |
40 | olLayer[vectorClusterGroupName] = this.name;
41 | this._olLayer = olLayer;
42 | this.map.addOLLayer(this._olLayer);
43 | }
44 | await super.initialize();
45 | }
46 |
47 | get clusterSource(): VcsCluster {
48 | return this._clusterSource;
49 | }
50 |
51 | get olLayer(): OLVectorLayer | undefined {
52 | return this._olLayer;
53 | }
54 |
55 | async activate(): Promise {
56 | await super.activate();
57 | if (this.active) {
58 | this._olLayer?.setVisible(true);
59 | this._clusterSource.paused = false;
60 | this._clusterSource.refresh();
61 | }
62 | }
63 |
64 | deactivate(): void {
65 | super.deactivate();
66 | this._olLayer?.setVisible(false);
67 | this._clusterSource.paused = true;
68 | }
69 |
70 | destroy(): void {
71 | if (this._olLayer) {
72 | this.map.removeOLLayer(this._olLayer);
73 | }
74 | this._olLayer = undefined;
75 |
76 | this._clusterSource.clear(true);
77 | this._clusterSource.dispose();
78 | super.destroy();
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/vectorCluster/vectorClusterSymbols.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/prefer-default-export
2 | export const vectorClusterGroupName = Symbol('vectorClusterGroupName');
3 |
--------------------------------------------------------------------------------
/tests/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["@vcsuite/eslint-config/mocha"],
3 | "rules": {
4 | "import/extensions": ["error", "always"],
5 | "import/no-unresolved": "off"
6 | },
7 | "globals": {
8 | "expect": "readonly",
9 | "sinon": "readonly",
10 | "createCanvas": "readonly"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/tests/data/dynamicPointCzml.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "document",
4 | "name": "CZML Point - Time Dynamic",
5 | "version": "1.0",
6 | "clock": {
7 | "interval": "2012-08-04T16:00:00Z/2012-08-04T16:00:32Z",
8 | "currentTime": "2012-08-04T16:00:00Z",
9 | "multiplier": 1
10 | }
11 | },
12 | {
13 | "id": "point",
14 | "availability": "2012-08-04T16:00:00Z/2012-08-04T16:00:32Z",
15 | "position": {
16 | "epoch": "2012-08-04T16:00:00Z",
17 | "cartographicDegrees": [
18 | 0, 13.418745425028446, 52.499061993250024, 50, 1, 13.4187361511943,
19 | 52.49911931469231, 50, 2, 13.418708686080118, 52.499174433233264, 50, 3,
20 | 13.418664085154827, 52.49922523071035, 50, 4, 13.418604062406976,
21 | 52.499269755023136, 50, 5, 13.418530924477107, 52.499306295148614, 50,
22 | 6, 13.418447482014843, 52.499333446891654, 50, 7, 13.418356941667218,
23 | 52.499350166844096, 50, 8, 13.418262782849014, 52.499355812479706, 50,
24 | 9, 13.418168624030812, 52.499350166844096, 50, 10, 13.418078083683186,
25 | 52.499333446891654, 50, 11, 13.417994641220924, 52.499306295148614, 50,
26 | 12, 13.417921503291051, 52.499269755023136, 50, 13, 13.417861480543202,
27 | 52.49922523071035, 50, 14, 13.417816879617913, 52.499174433233264, 50,
28 | 15, 13.417789414503728, 52.49911931469231, 50, 16, 13.417780140669585,
29 | 52.499061993250024, 50, 17, 13.417789414503728, 52.49900467173299, 50,
30 | 18, 13.417816879617913, 52.49894955297921, 50, 19, 13.417861480543202,
31 | 52.49889875518363, 50, 20, 13.417921503291051, 52.49885423049514, 50,
32 | 21, 13.417994641220924, 52.498817689993956, 50, 22, 13.418078083683186,
33 | 52.49879053793239, 50, 23, 13.418168624030812, 52.4987738177671, 50, 24,
34 | 13.418262782849014, 52.49876817205677, 50, 25, 13.418356941667218,
35 | 52.4987738177671, 50, 26, 13.418447482014843, 52.49879053793239, 50, 27,
36 | 13.418530924477107, 52.498817689993956, 50, 28, 13.418604062406976,
37 | 52.49885423049514, 50, 29, 13.418664085154827, 52.49889875518363, 50,
38 | 30, 13.418708686080118, 52.49894955297921, 50, 31, 13.4187361511943,
39 | 52.49900467173299, 50, 32, 13.418745425028446, 52.499061993250024, 50
40 | ]
41 | },
42 | "point": {
43 | "color": {
44 | "rgba": [255, 255, 255, 255]
45 | },
46 | "outlineColor": {
47 | "rgba": [255, 0, 0, 255]
48 | },
49 | "outlineWidth": 4,
50 | "pixelSize": 20
51 | }
52 | }
53 | ]
54 |
--------------------------------------------------------------------------------
/tests/data/oblique/tiledImageData/image.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "v3.5-3-gda4794e_64bit",
3 | "info": "- the corner points of the images are sorted like [lower left, lower right, upper right, upper left] always with respect to north - the view direction angle is either calculated from camera pose and center on ground or the center of footprint edges in view direction - if the images have same size the data can be found in generalImageInfo otherwise the data is stored in the data regarding the images - view-direction is north, east, south, west with 0, 1, 2, 3",
4 | "generalImageInfo": {
5 | "width": 11608,
6 | "height": 8708,
7 | "tile-resolution": [32, 16, 8, 4, 2, 1],
8 | "crs": "+proj=utm +zone=33 +ellps=GRS80 +units=m +no_defs ",
9 | "tile-width": 512,
10 | "tile-height": 512,
11 | "cameraParameter": [
12 | {
13 | "name": "119_front",
14 | "principal-point": [5805.89, 4373.07],
15 | "pixel-size": [0.0046, 0.0046],
16 | "radial-distorsion-expected-2-found": [
17 | 0.0000141899, -0.00369415, 0.0000585035, 0.00000290641, -2.00567e-9
18 | ],
19 | "radial-distorsion-found-2-expected": [
20 | -0.0000126063, 0.00370512, -0.0000585165, -0.00000299588, 4.76671e-9
21 | ]
22 | },
23 | {
24 | "name": "116_back",
25 | "principal-point": [5813.5, 4348.07],
26 | "pixel-size": [0.0046, 0.0046],
27 | "radial-distorsion-expected-2-found": [
28 | 0.0000141899, -0.00369415, 0.0000585035, 0.00000290641, -2.00567e-9
29 | ],
30 | "radial-distorsion-found-2-expected": [
31 | -0.0000126063, 0.00370512, -0.0000585165, -0.00000299588, 4.76671e-9
32 | ]
33 | },
34 | {
35 | "name": "111_right",
36 | "principal-point": [5813.07, 4338.72],
37 | "pixel-size": [0.0046, 0.0046],
38 | "radial-distorsion-expected-2-found": [
39 | 0.0000141899, -0.00369415, 0.0000585035, 0.00000290641, -2.00567e-9
40 | ],
41 | "radial-distorsion-found-2-expected": [
42 | -0.0000126063, 0.00370512, -0.0000585165, -0.00000299588, 4.76671e-9
43 | ]
44 | },
45 | {
46 | "name": "110_left",
47 | "principal-point": [5793.07, 4378.5],
48 | "pixel-size": [0.0046, 0.0046],
49 | "radial-distorsion-expected-2-found": [
50 | 0.0000141899, -0.00369415, 0.0000585035, 0.00000290641, -2.00567e-9
51 | ],
52 | "radial-distorsion-found-2-expected": [
53 | -0.0000126063, 0.00370512, -0.0000585165, -0.00000299588, 4.76671e-9
54 | ]
55 | }
56 | ]
57 | },
58 | "availableTiles": [
59 | "12/2199/1342",
60 | "12/2199/1343",
61 | "12/2199/1344",
62 | "12/2200/1342",
63 | "12/2200/1343",
64 | "12/2200/1344",
65 | "12/2201/1342",
66 | "12/2201/1343",
67 | "12/2201/1344"
68 | ],
69 | "tileLevel": 12
70 | }
71 |
--------------------------------------------------------------------------------
/tests/data/terrain/13/8800/6485.terrain:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualcitySYSTEMS/map-core/861d2a016536d461cccd0a8fcf6fcd6ddeec249f/tests/data/terrain/13/8800/6485.terrain
--------------------------------------------------------------------------------
/tests/data/terrain/13/8800/6486.terrain:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualcitySYSTEMS/map-core/861d2a016536d461cccd0a8fcf6fcd6ddeec249f/tests/data/terrain/13/8800/6486.terrain
--------------------------------------------------------------------------------
/tests/data/terrain/13/8801/6485.terrain:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualcitySYSTEMS/map-core/861d2a016536d461cccd0a8fcf6fcd6ddeec249f/tests/data/terrain/13/8801/6485.terrain
--------------------------------------------------------------------------------
/tests/data/terrain/13/8801/6486.terrain:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualcitySYSTEMS/map-core/861d2a016536d461cccd0a8fcf6fcd6ddeec249f/tests/data/terrain/13/8801/6486.terrain
--------------------------------------------------------------------------------
/tests/data/terrain/layer.json:
--------------------------------------------------------------------------------
1 | {
2 | "tilejson": "2.1.0",
3 | "qmc-version": "3.5-0-g0f89f13_64bit_QMC_virtualcitySYSTEMS_GmbH",
4 | "version": "1.1536156513599457",
5 | "format": "quantized-mesh-1.0",
6 | "scheme": "tms",
7 | "extensions": ["octvertexnormals"],
8 | "tiles": ["{z}/{x}/{y}.terrain?v={version}"],
9 | "minzoom": 0,
10 | "maxzoom": 13,
11 | "bounds": [-180.0, -90.0, 180.0, 90.0],
12 | "projection": "EPSG:4326",
13 | "available": [
14 | [
15 | {
16 | "endY": 0,
17 | "endX": 1,
18 | "startX": 0,
19 | "startY": 0
20 | }
21 | ],
22 | [
23 | {
24 | "endY": 1,
25 | "endX": 2,
26 | "startX": 2,
27 | "startY": 1
28 | }
29 | ],
30 | [
31 | {
32 | "endY": 3,
33 | "endX": 4,
34 | "startX": 4,
35 | "startY": 3
36 | }
37 | ],
38 | [
39 | {
40 | "endY": 6,
41 | "endX": 8,
42 | "startX": 8,
43 | "startY": 6
44 | }
45 | ],
46 | [
47 | {
48 | "endY": 12,
49 | "endX": 17,
50 | "startX": 17,
51 | "startY": 12
52 | }
53 | ],
54 | [
55 | {
56 | "endY": 25,
57 | "endX": 34,
58 | "startX": 34,
59 | "startY": 25
60 | }
61 | ],
62 | [
63 | {
64 | "endY": 50,
65 | "endX": 68,
66 | "startX": 68,
67 | "startY": 50
68 | }
69 | ],
70 | [
71 | {
72 | "endY": 101,
73 | "endX": 137,
74 | "startX": 137,
75 | "startY": 101
76 | }
77 | ],
78 | [
79 | {
80 | "endY": 202,
81 | "endX": 275,
82 | "startX": 275,
83 | "startY": 202
84 | }
85 | ],
86 | [
87 | {
88 | "endY": 405,
89 | "endX": 550,
90 | "startX": 550,
91 | "startY": 405
92 | }
93 | ],
94 | [
95 | {
96 | "endY": 810,
97 | "endX": 1100,
98 | "startX": 1100,
99 | "startY": 810
100 | }
101 | ],
102 | [
103 | {
104 | "endY": 1621,
105 | "endX": 2200,
106 | "startX": 2200,
107 | "startY": 1621
108 | }
109 | ],
110 | [
111 | {
112 | "endY": 3243,
113 | "endX": 4400,
114 | "startX": 4400,
115 | "startY": 3242
116 | }
117 | ],
118 | [
119 | {
120 | "endY": 6486,
121 | "endX": 8801,
122 | "startX": 8800,
123 | "startY": 6485
124 | }
125 | ]
126 | ]
127 | }
128 |
--------------------------------------------------------------------------------
/tests/data/tile.pbf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualcitySYSTEMS/map-core/861d2a016536d461cccd0a8fcf6fcd6ddeec249f/tests/data/tile.pbf
--------------------------------------------------------------------------------
/tests/data/wgs84Points.fgb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualcitySYSTEMS/map-core/861d2a016536d461cccd0a8fcf6fcd6ddeec249f/tests/data/wgs84Points.fgb
--------------------------------------------------------------------------------
/tests/setup.js:
--------------------------------------------------------------------------------
1 | import chai from 'chai';
2 | import sinonChai from 'sinon-chai';
3 | import sinon from 'sinon';
4 | import canvas from 'canvas';
5 | import canvasBindings from 'canvas/lib/bindings.js';
6 | import fetch from 'node-fetch';
7 | import ResizeObserverPolyfill from 'resize-observer-polyfill';
8 |
9 | chai.use(sinonChai);
10 |
11 | global.XMLHttpRequest = window.XMLHttpRequest;
12 | global.expect = chai.expect;
13 | global.sinon = sinon;
14 |
15 | global.requestAnimationFrame = window.requestAnimationFrame;
16 | global.cancelAnimationFrame = window.cancelAnimationFrame;
17 | global.canvaslibrary = canvas;
18 | global.CESIUM_BASE_URL = 'cesium/Source/';
19 | global.FileReader = window.FileReader;
20 | global.DOMParser = window.DOMParser;
21 | global.fetch = fetch;
22 | global.ResizeObserver = ResizeObserverPolyfill;
23 | global.ShadowRoot = Function;
24 |
25 | Object.assign(canvas, {
26 | CanvasGradient: canvasBindings.CanvasGradient,
27 | CanvasPattern: canvasBindings.CanvasPattern,
28 | });
29 | ['CanvasRenderingContext2D', 'CanvasPattern', 'CanvasGradient'].forEach(
30 | (obj) => {
31 | global[obj] = canvas[obj];
32 | },
33 | );
34 | global.createCanvas = canvas.createCanvas;
35 |
--------------------------------------------------------------------------------
/tests/setupJsdom.js:
--------------------------------------------------------------------------------
1 | import jsdomGlobal from 'jsdom-global';
2 |
3 | jsdomGlobal(undefined, {
4 | pretendToBeVisual: true,
5 | url: 'http://localhost',
6 | referrer: 'http://localhost',
7 | });
8 |
--------------------------------------------------------------------------------
/tests/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2022",
4 | "module": "node16",
5 | "incremental": true,
6 | "lib": ["esnext", "dom"],
7 | "allowJs": true,
8 | "checkJs": false,
9 | "jsx": "preserve",
10 | "outDir": "../.tests",
11 | "rootDir": "..",
12 | /* Strict Type-Checking Options */
13 | "strict": true,
14 | "noImplicitAny": true,
15 | "strictNullChecks": true,
16 |
17 | /* Additional Checks */
18 | "noImplicitReturns": false,
19 | "noFallthroughCasesInSwitch": false,
20 |
21 | /* Module Resolution Options */
22 | "baseUrl": ".",
23 | "paths": {
24 | "rbush-knn": ["../types/rbush-knn"],
25 | "rbush": ["../types/rbush"]
26 | },
27 | "moduleResolution": "node16",
28 | "resolveJsonModule": true,
29 | "esModuleInterop": true,
30 | "preserveSymlinks": true,
31 | "allowSyntheticDefaultImports": true,
32 | "types": ["mocha", "node"]
33 | },
34 | "include": ["../types/", "../src/", "../index.ts", "."],
35 | "exclude": ["../dist", "../.tests", "../build"]
36 | }
37 |
--------------------------------------------------------------------------------
/tests/unit/helpers/getFileNameFromUrl.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import { fileURLToPath } from 'url';
3 |
4 | /**
5 | * @param {string} url
6 | * @param {string} fileName
7 | * @returns {string}
8 | */
9 | export default function getFileNameFromUrl(url, fileName) {
10 | const dirName = fileURLToPath(url);
11 | return path.join(dirName, fileName);
12 | }
13 |
--------------------------------------------------------------------------------
/tests/unit/helpers/helpers.ts:
--------------------------------------------------------------------------------
1 | import { Math as CesiumMath } from '@vcmap-cesium/engine';
2 | import { expect } from 'chai';
3 |
4 | /**
5 | * helper function to wait for a timeout use: await timeout(1);
6 | */
7 | export function timeout(ms: number): Promise {
8 | return new Promise((resolve) => {
9 | setTimeout(resolve, ms);
10 | });
11 | }
12 |
13 | export function arrayCloseTo(
14 | numbers: T,
15 | expectedNumbers: T,
16 | epsilon = CesiumMath.EPSILON8,
17 | message = '',
18 | ): void {
19 | expect(numbers.length).to.equal(expectedNumbers.length);
20 | numbers.forEach((c, index) => {
21 | expect(c).to.be.closeTo(
22 | expectedNumbers[index],
23 | epsilon,
24 | `Array at index ${index}${message}`,
25 | );
26 | });
27 | }
28 |
--------------------------------------------------------------------------------
/tests/unit/helpers/imageHelpers.js:
--------------------------------------------------------------------------------
1 | /**
2 | * black Pixel dataURI
3 | * @type {string}
4 | */
5 | export const blackPixelURI =
6 | 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQYV2NgYGD4DwABBAEAcCBlCwAAAABJRU5ErkJggg==';
7 | /**
8 | * green Pixel dataURI
9 | * @type {string}
10 | */
11 | export const greenPixelURI =
12 | 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQYV2Ng+M/wHwAEAQH/Xi7hpQAAAABJRU5ErkJggg==';
13 | /**
14 | * red Pixel dataURI
15 | * @type {string}
16 | */
17 | export const redPixelURI =
18 | 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQYV2P4z8DwHwAFAAH/plybXQAAAABJRU5ErkJggg==';
19 | /**
20 | * blue Pixel dataURI
21 | * @type {string}
22 | */
23 | export const bluePixelURI =
24 | 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQYV2NgYPj/HwADAgH/ybKt7gAAAABJRU5ErkJggg==';
25 |
--------------------------------------------------------------------------------
/tests/unit/helpers/importJSON.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 |
3 | /**
4 | * @param {string} fileName
5 | * @returns {Promise