import { Injectable } from '@angular/core' ; import Cartesian3 from 'cesium/Source/Core/Cartesian3' ; import CesiumTerrainProvider from 'cesium/Source/Core/CesiumTerrainProvider' ; import GeoJsonDataSource from 'cesium/Source/DataSources/GeoJsonDataSource' ; import GetFeatureInfoFormat from 'cesium/Source/Scene/GetFeatureInfoFormat' ; import ImageLayer from 'ol/layer/Image' ; import ImageSource from 'ol/source/Image' ; import KmlDataSource from 'cesium/Source/DataSources/KmlDataSource' ; import Layer from 'ol/layer/Layer' ; import OpenStreetMapImageryProvider from 'cesium/Source/Scene/OpenStreetMapImageryProvider' ; import Resource from 'cesium/Source/Core/Resource' ; import TileLayer from 'ol/layer/Tile' ; import TileSource from 'ol/source/Tile' ; import VectorLayer from 'ol/layer/Vector' ; import VectorSource from 'ol/source/Vector' ; import Viewer from 'cesium/Source/Widgets/Viewer/Viewer' ; import WebMapServiceImageryProvider from 'cesium/Source/Scene/WebMapServiceImageryProvider' ; import WebMercatorTilingScheme from 'cesium/Source/Core/WebMercatorTilingScheme' ; import createWorldTerrain from 'cesium/Source/Core/createWorldTerrain' ; import dayjs from 'dayjs' ; import { DataSource , ImageryLayer } from 'cesium' ; import { GeoJSON , KML } from 'ol/format' ; import { Geometry } from 'ol/geom' ; import { Group } from 'ol/layer' ; import { HsConfig , HsEventBusService , HsMapService , HsUtilsService , getDimensions , getMinimumTerrainLevel , getTitle , } from 'hslayers-ng' ; import { HsLayerUtilsService } from 'hslayers-ng' ; import { ImageWMS , Source } from 'ol/source' ; import { OSM , TileWMS } from 'ol/source' ; import { OlCesiumObjectMapItem } from './ol-cesium-object-map-item.class' ; import { default as proj4 } from 'proj4' ; import { HsCesiumConfig } from './hscesium-config.service' ; import { ParamCacheMapItem } from './param-cache-map-item.class' ; import { generateUuid } from 'hslayers-ng' ; param proxy param maxResolution param HsUtilsService param projection function MyProxy ( { proxy , maxResolution , HsUtilsService , projection } ) { this . proxy = proxy ; this . maxResolution = maxResolution ; this . HsUtilsService = HsUtilsService ; this . projection = projection ; } @ Injectable ( { providedIn : 'root' , } ) export class HsCesiumLayersService { layersToBeDeleted = [ ] ; viewer : Viewer ; ol2CsMappings : Array < OlCesiumObjectMapItem > = [ ] ; paramCaches : Array < ParamCacheMapItem > = [ ] ; constructor ( public HsMapService , public HsConfig , public HsUtilsService , public HsEventBusService , public HsCesiumConfig , private HsLayerUtilsService ) { } init ( viewer ) { this . viewer = viewer ; this . ol2CsMappings = [ ] ; this . paramCaches = [ ] ; this . defineProxy ( ) ; this . setupEvents ( ) ; } defineProxy ( ) { MyProxy . prototype . getURL = function ( resource ) { const blank_url = `${ this . proxy }${ window . location . protocol }//${ window . location . host }${ window . location . pathname }img/blank.png` ; const prefix = this . proxy . indexOf ( '?' ) === - 1 && this . proxy . indexOf ( 'hsproxy' ) > - 1 ? '?' : '' ; if ( this . maxResolution <= 8550 ) { if ( resource . indexOf ( 'bbox=0%2C0%2C45' ) > - 1 || resource . indexOf ( 'bbox=0, 45' ) > - 1 ) { return blank_url ; } else { const params = this . HsUtilsService . getParamsFromUrl ( resource ) ; const bbox = params . bbox . split ( ',' ) ; const dist = Math . sqrt ( Math . pow ( bbox [ 0 ] - bbox [ 2 ] , 2 ) + Math . pow ( bbox [ 1 ] - bbox [ 3 ] , 2 ) ) ; if ( this . projection == 'EPSG:3857' ) { if ( dist > 1000000 ) { return blank_url ; } } if ( this . projection == 'EPSG:4326' ) { if ( dist > 1 ) { return blank_url ; } } } } resource = resource . replace ( /fromcrs/gm , 'FROMCRS' ) ; if ( resource . indexOf ( 'proxy4ows' ) > - 1 ) { return resource ; } return ( this . proxy + prefix + ( this . proxy . indexOf ( 'hsproxy' ) > - 1 ? encodeURIComponent ( resource ) : resource ) ) ; } ; } param version param srs param crs getProjectFromVersion ( version , srs , crs ) { if ( version == '1.1.1' ) { return srs ; } if ( version == '1.3.1' ) { return crs ; } } async setupEvents ( ) { this . HsEventBusService . LayerManagerBaseLayerVisibilityChanges . subscribe ( ( data ) => { if ( data && data . type && data . type == 'terrain' ) { if ( data . url == 'https://assets.agi.com/stk-terrain/v1/tilesets/world/tiles' ) { const terrain_provider = createWorldTerrain ( this . HsCesiumConfig . createWorldTerrainOptions ) ; this . viewer . terrainProvider = terrain_provider ; } else { this . viewer . terrainProvider = new CesiumTerrainProvider ( { url : data . url , } ) ; } } } ) ; this . repopulateLayers ( ) ; const map = await this . HsMapService . loaded ( ) ; map . getLayers ( ) . on ( 'add' , ( e ) => { const lyr = e . element ; this . processOlLayer ( lyr ) ; } ) ; } public async repopulateLayers ( ) { if ( this . viewer . isDestroyed ( ) ) { return ; } if ( this . HsConfig . default_layers !== undefined ) { this . HsConfig . default_layers . forEach ( ( l ) => this . processOlLayer ( l ) ) ; } if ( this . HsConfig . box_layers ) { this . HsConfig . box_layers . forEach ( ( l ) => this . processOlLayer ( l ) ) ; } const map = await this . HsMapService . loaded ( ) ; map . getLayers ( ) . forEach ( ( lyr ) => { const cesiumLayer = this . findCesiumLayer ( lyr ) ; if ( cesiumLayer == undefined ) { this . processOlLayer ( lyr ) ; } } ) ; } serializeVectorLayerToGeoJson ( ol_source ) { const f = new GeoJSON ( ) ; const cesiumLayer = < DataSource > this . findCesiumLayer ( ol_source ) ; const features = ol_source . getFeatures ( ) ; features . forEach ( ( feature ) => { const featureId = feature . getId ( ) ; if ( featureId == undefined ) { const id = generateUuid ( ) ; feature . setId ( id ) ; feature . set ( 'HsCesiumFeatureId' , id ) ; return ; } if ( typeof cesiumLayer . entities . getById ( featureId . toString ( ) ) != 'undefined' ) { features . splice ( features . indexOf ( feature ) , 1 ) ; } else { } } ) ; const to_remove = [ ] ; cesiumLayer . entities . values . forEach ( ( entity ) => { if ( ol_source . getFeatureById ( entity . id ) === null ) { to_remove . push ( entity . id ) ; } } ) ; while ( to_remove . length > 0 ) { const id = to_remove . pop ( ) ; cesiumLayer . entities . removeById ( id ) ; } const json = f . writeFeaturesObject ( features ) ; if ( this . currentMapProjCode ( ) == 'EPSG:3857' ) { json . crs = { type : 'name' , properties : { name : 'EPSG:3857' , } , } ; } return json ; } currentMapProjCode ( ) { if ( this . HsMapService . map ) { return this . HsMapService . getCurrentProj ( ) . getCode ( ) ; } else { this . HsConfig . default_view . getProjection ( ) . getCode ( ) ; } } findCesiumLayer ( ol ) { const found = this . ol2CsMappings . filter ( ( m ) => m . olObject == ol ) ; if ( found . length > 0 ) { return found [ 0 ] . csObject ; } } findOlLayer ( cs ) { const found = this . ol2CsMappings . filter ( ( m ) => m . csObject == cs && this . HsUtilsService . instOf ( m . olObject , Layer ) ) ; if ( found . length > 0 ) { return found [ 0 ] . olObject ; } } findOlSource ( cs ) { const found = this . ol2CsMappings . filter ( ( m ) => m . csObject == cs && this . HsUtilsService . instOf ( m . olObject , Source ) ) ; if ( found . length > 0 ) { return found [ 0 ] . olObject ; } } linkOlLayerToCesiumLayer ( ol_layer , cesium_layer ) { this . ol2CsMappings . push ( { olObject : ol_layer , csObject : cesium_layer } ) ; ol_layer . on ( 'change:visible' , ( e ) => { const cesiumLayer = this . findCesiumLayer ( e . target as Layer < Source > ) ; cesiumLayer . show = ol_layer . getVisible ( ) ; } ) ; ol_layer . on ( 'change:opacity' , ( e ) => { const cesiumLayer = this . findCesiumLayer ( e . target as Layer < Source > ) ; if ( this . HsUtilsService . instOf ( cesiumLayer , ImageryLayer ) ) { ( < ImageryLayer > cesiumLayer ) . alpha = ol_layer . getOpacity ( ) ; } } ) ; } linkOlSourceToCesiumDatasource ( ol_source , cesium_layer ) { this . ol2CsMappings . push ( { olObject : ol_source , csObject : cesium_layer } ) ; this . syncFeatures ( ol_source ) ; ( ol_source as any ) . on ( 'features:loaded' , ( e ) => { const cesiumLayer = this . findCesiumLayer ( e . target as Source ) ; if ( cesiumLayer ) { this . syncFeatures ( e . target as VectorSource < Geometry > ) ; } } ) ; } syncFeatures ( ol_source ) { const tmp_source = new GeoJsonDataSource ( 'tmp' ) ; GeoJsonDataSource . crsNames [ 'EPSG:3857' ] = function ( coordinates ) { const firstProjection = 'PROJCS["WGS 84 / Pseudo-Mercator",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]],PROJECTION["Mercator_1SP"],PARAMETER["central_meridian",0],PARAMETER["scale_factor",1],PARAMETER["false_easting",0],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["X",EAST],AXIS["Y",NORTH],EXTENSION["PROJ4","+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext  +no_defs"],AUTHORITY["EPSG","3857"]]' ; const secondProjection = 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]' ; const xa = coordinates [ 0 ] ; const ya = coordinates [ 1 ] ; const newCoordinates = proj4 ( firstProjection , secondProjection , [ xa , ya ] ) ; return Cartesian3 . fromDegrees ( newCoordinates [ 0 ] , newCoordinates [ 1 ] , 0 ) ; } ; const promise = tmp_source . load ( this . serializeVectorLayerToGeoJson ( ol_source ) , { clampToGround : true , } ) ; promise . then ( ( source ) => { const cesiumLayer = < DataSource > this . findCesiumLayer ( ol_source ) ; source . entities . values . forEach ( ( entity ) => { try { if ( cesiumLayer . entities . getById ( entity . id ) == undefined ) { cesiumLayer . entities . add ( entity ) ; } } catch ( ex ) { if ( console ) { console . error ( ex . toString ( ) ) ; } } } ) ; ol_source . get ( 'cesiumStyler' ) ( cesiumLayer ) ; } ) ; } async processOlLayer ( lyr ) { if ( ! this . viewer ) { return ; } if ( this . HsUtilsService . instOf ( lyr , Group ) ) { for ( const sub_lyr of ( < Group > lyr ) . getLayers ( ) . getArray ( ) as Layer < Source > [ ] ) { this . processOlLayer ( sub_lyr ) ; } } else { lyr . setVisible ( this . HsMapService . layerTitleInArray ( lyr , this . HsMapService . visibleLayersInUrl ) || lyr . getVisible ( ) ) ; if ( this . HsUtilsService . instOf ( lyr , ImageLayer ) ) { if ( this . HsUtilsService . instOf ( ( lyr as ImageLayer < ImageSource > ) . getSource ( ) , ImageWMS ) ) { this . HsMapService . proxifyLayerLoader ( lyr , false ) ; } } if ( this . HsUtilsService . instOf ( lyr , TileLayer ) ) { if ( this . HsUtilsService . instOf ( ( lyr as TileLayer < TileSource > ) . getSource ( ) , TileWMS ) ) { this . HsMapService . proxifyLayerLoader ( lyr , true ) ; } } const cesium_layer = await this . convertOlToCesiumProvider ( lyr as Layer < Source > ) ; if ( cesium_layer ) { if ( this . HsUtilsService . instOf ( cesium_layer , ImageryLayer ) ) { this . linkOlLayerToCesiumLayer ( lyr as Layer < Source > , cesium_layer as ImageryLayer ) ; this . viewer . imageryLayers . add ( < ImageryLayer > cesium_layer ) ; } else if ( ( this . HsUtilsService . instOf ( cesium_layer , GeoJsonDataSource ) || this . HsUtilsService . instOf ( cesium_layer , KmlDataSource ) ) && this . viewer . dataSources ) { this . viewer . dataSources . add ( < DataSource > cesium_layer ) ; if ( getTitle ( lyr as Layer < Source > ) != 'Point clicked' ) { this . linkOlSourceToCesiumDatasource ( ( lyr as VectorLayer < VectorSource < Geometry > > ) . getSource ( ) , cesium_layer ) ; } } } } } async convertOlToCesiumProvider ( ol_lyr ) { if ( this . HsUtilsService . instOf ( ol_lyr . getSource ( ) , OSM ) ) { return new ImageryLayer ( new OpenStreetMapImageryProvider ( { } ) , { show : ol_lyr . getVisible ( ) , minimumTerrainLevel : getMinimumTerrainLevel ( ol_lyr ) || 15 , } ) ; } else if ( this . HsUtilsService . instOf ( ol_lyr . getSource ( ) , TileWMS ) ) { return this . createTileProvider ( ol_lyr ) ; } else if ( this . HsUtilsService . instOf ( ol_lyr . getSource ( ) , ImageWMS ) ) { return this . createSingleImageProvider ( ol_lyr as ImageLayer < ImageSource > ) ; } else if ( this . HsUtilsService . instOf ( ol_lyr , VectorLayer ) ) { const dataSource = await this . createVectorDataSource ( ol_lyr as VectorLayer < VectorSource < Geometry > > ) ; return dataSource ; } else { if ( console ) { console . error ( 'Unsupported layer type for layer: ' , ol_lyr , 'in Cesium converter' ) ; } } } async createVectorDataSource ( ol_lyr ) { if ( ol_lyr . getSource ( ) . getFormat ( ) && this . HsUtilsService . instOf ( ol_lyr . getSource ( ) . getFormat ( ) , KML ) ) { if ( this . HsUtilsService . isFunction ( ol_lyr . getSource ( ) . getUrl ( ) ) ) { console . warn ( 'FeatureUrlFunction is currently not supported in synchronizing features from Ol layer to Cesium' ) ; return ; } const url = < string > ol_lyr . getSource ( ) . getUrl ( ) ; return await KmlDataSource . load ( url , { camera : this . viewer . scene . camera , canvas : this . viewer . scene . canvas , clampToGround : ol_lyr . getSource ( ) . get ( 'clampToGround' ) || true , } ) ; } else { const new_source = new GeoJsonDataSource ( getTitle ( ol_lyr ) ) ; this . ol2CsMappings . push ( { olObject : ol_lyr , csObject : new_source } ) ; ol_lyr . on ( 'change:visible' , ( e ) => { const cesiumLayer = this . findCesiumLayer ( e . target as Layer < Source > ) ; cesiumLayer . show = ol_lyr . getVisible ( ) ; } ) ; return new_source ; } } createTileProvider ( ol_lyr ) { const src = ol_lyr . getSource ( ) ; const params = JSON . parse ( JSON . stringify ( this . HsLayerUtilsService . getLayerParams ( ol_lyr ) ) ) ; params . VERSION = params . VERSION || '1.1.1' ; if ( params . VERSION . indexOf ( '1.1.' ) == 0 ) { params . CRS = 'EPSG:4326' ; } if ( params . VERSION . indexOf ( '1.3.' ) == 0 ) { params . SRS = 'EPSG:4326' ; } params . FROMCRS = 'EPSG:4326' ; const prmCache = { url : new Resource ( { url : src . getUrls ( ) [ 0 ] , proxy : new MyProxy ( { proxy : this . getProxyFromConfig ( ) , maxResolution : ol_lyr . getMaxResolution ( ) , HsUtilsService : this . HsUtilsService , projection : this . getProjectionFromParams ( params ) , } ) , } ) , layers : src . getParams ( ) . LAYERS , dimensions : getDimensions ( ol_lyr ) , getFeatureInfoFormats : [ new GetFeatureInfoFormat ( 'text' , 'text/plain' ) ] , enablePickFeatures : true , parameters : params , getFeatureInfoParameters : { VERSION : params . VERSION , CRS : 'EPSG:4326' , FROMCRS : 'EPSG:4326' , } , minimumTerrainLevel : params . minimumTerrainLevel || 12 , maximumLevel : params . maximumLevel , minimumLevel : params . minimumLevel , } ; const tmp = new ImageryLayer ( new WebMapServiceImageryProvider ( this . removeUnwantedParams ( prmCache ) ) , { alpha : ol_lyr . getOpacity ( ) || 0.7 , show : ol_lyr . getVisible ( ) , } ) ; this . paramCaches . push ( { imageryLayer : tmp , cache : prmCache } ) ; return tmp ; } private getProxyFromConfig ( ) { return this . HsConfig . proxyPrefix ? this . HsConfig . proxyPrefix : '/proxy/' ; } private getProjectionFromParams ( params ) { return this . getProjectFromVersion ( params . version , params . srs , params . crs ) ; } createSingleImageProvider ( ol_lyr ) { const src = < ImageWMS > ol_lyr . getSource ( ) ; const params = Object . assign ( { } , src . getParams ( ) ) ; params . VERSION = params . VERSION || '1.1.1' ; if ( params . VERSION . indexOf ( '1.1.' ) == 0 ) { params . CRS = 'EPSG:4326' ; delete params . SRS ; } if ( params . VERSION . indexOf ( '1.3.' ) == 0 ) { params . SRS = 'EPSG:4326' ; delete params . CRS ; } params . FROMCRS = 'EPSG:4326' ; const prmCache = { url : new Resource ( { url : src . getUrl ( ) , proxy : new MyProxy ( { proxy : this . getProxyFromConfig ( ) , maxResolution : ol_lyr . getMaxResolution ( ) , HsUtilsService : this . HsUtilsService , projection : this . getProjectionFromParams ( params ) , } ) , } ) , layers : src . getParams ( ) . LAYERS , dimensions : getDimensions ( ol_lyr ) , getFeatureInfoFormats : [ new GetFeatureInfoFormat ( 'text' , 'text/plain' ) ] , enablePickFeatures : true , parameters : params , tilingScheme : new WebMercatorTilingScheme ( ) , getFeatureInfoParameters : { VERSION : params . VERSION , CRS : 'EPSG:4326' , FROMCRS : 'EPSG:4326' , } , minimumTerrainLevel : params . minimumTerrainLevel || 12 , tileWidth : 1024 , tileHeight : 1024 , } ; const tmp = new ImageryLayer ( new WebMapServiceImageryProvider ( this . removeUnwantedParams ( prmCache ) ) , { alpha : ol_lyr . getOpacity ( ) || 0.7 , show : ol_lyr . getVisible ( ) , } ) ; this . paramCaches . push ( { imageryLayer : tmp , cache : prmCache } ) ; return tmp ; } removeUnwantedParams ( prmCache ) { if ( prmCache . parameters . dimensions ) { delete prmCache . parameters . dimensions ; } return prmCache ; } changeLayerParam ( layer , parameter , new_value ) { new_value = dayjs ( new_value ) . isValid ( ) ? dayjs ( new_value ) . toISOString ( ) : new_value ; const prmCache = this . findParamCache ( layer ) ; prmCache . parameters [ parameter ] = new_value ; this . layersToBeDeleted . push ( layer ) ; const tmp = new ImageryLayer ( new WebMapServiceImageryProvider ( prmCache ) , { alpha : layer . alpha , show : layer . show , } ) ; this . paramCaches . push ( { imageryLayer : tmp , cache : prmCache } ) ; this . linkOlLayerToCesiumLayer ( this . findOlLayer ( layer ) , tmp ) ; this . viewer . imageryLayers . add ( tmp ) ; } findParamCache ( layer ) { const found = this . paramCaches . filter ( ( m ) => m . imageryLayer == layer ) ; if ( found . length > 0 ) { return found [ 0 ] . cache ; } } removeLayersWithOldParams ( ) { while ( this . layersToBeDeleted . length > 0 ) { const cesiumLayer = this . layersToBeDeleted . pop ( ) ; this . viewer . imageryLayers . remove ( cesiumLayer ) ; const mappingsToRemove = this . ol2CsMappings . filter ( ( m ) => m . csObject == cesiumLayer ) ; for ( const mapping of mappingsToRemove ) { this . ol2CsMappings . splice ( this . ol2CsMappings . indexOf ( mapping ) ) ; } } } }