import { DomSanitizer } from '@angular/platform-browser' ; import { Injectable } from '@angular/core' ; import Feature from 'ol/Feature' ; import OpenLayersParser from 'geostyler-openlayers-parser' ; import QGISStyleParser from 'geostyler-qgis-parser' ; import SLDParser from 'geostyler-sld-parser' ; import VectorLayer from 'ol/layer/Vector' ; import VectorSource from 'ol/source/Vector' ; import { Cluster } from 'ol/source' ; import { FillSymbolizer , Filter , Style as GeoStylerStyle , Rule , } from 'geostyler-style' ; import { Geometry } from 'ol/geom' ; import { Icon , Style } from 'ol/style' ; import { StyleFunction , StyleLike } from 'ol/style/Style' ; import { Subject } from 'rxjs' ; import { createDefaultStyle } from 'ol/style/Style' ; import { HsEventBusService } from '../core/event-bus.service' ; import { HsLayerDescriptor } from '../layermanager/layer-descriptor.interface' ; import { HsLayerUtilsService } from '../utils/layer-utils.service' ; import { HsLogService } from '../../common/log/log.service' ; import { HsMapService } from '../map/map.service' ; import { HsQueryVectorService } from '../query/query-vector.service' ; import { HsSaveMapService } from '../save-map/save-map.service' ; import { HsUtilsService } from '../utils/utils.service' ; import { defaultStyle } from './styles' ; import { getCluster , getQml , getSld , getTitle , setSld , } from '../../common/layer-extensions' ; import { getHighlighted } from '../../common/feature-extensions' ; import { parseStyle } from './backwards-compatibility' ; @ Injectable ( { providedIn : 'root' , } ) export class HsStylerService { layer : VectorLayer < VectorSource < Geometry > > = null ; onSet : Subject < VectorLayer < VectorSource < Geometry > > > = new Subject ( ) ; layerTitle : string ; styleObject : GeoStylerStyle ; sldParser = ( SLDParser as any ) . default ? new ( SLDParser as any ) . default ( ) : new SLDParser ( ) ; qmlParser = ( QGISStyleParser as any ) . default ? new ( QGISStyleParser as any ) . default ( ) : new QGISStyleParser ( ) ; sld : string ; pin_white_blue = new Style ( { image : new Icon ( { src : this . hsUtilsService . getAssetsPath ( ) + 'img/pin_white_blue32.png' , crossOrigin : 'anonymous' , anchor : [ 0.5 , 1 ] , } ) , } ) ; constructor ( public hsQueryVectorService , public hsUtilsService , private hsLayerUtilsService , private hsEventBusService , private hsLogService , public sanitizer , private hsMapService , private hsSaveMapService ) { this . hsMapService . loaded ( ) . then ( ( ) => this . init ( ) ) ; } async init ( ) { for ( const layer of this . hsMapService . getLayersArray ( ) . filter ( ( layer ) => this . hsLayerUtilsService . isLayerVectorLayer ( layer ) ) ) { this . initLayerStyle ( layer as VectorLayer < VectorSource < Geometry > > ) ; } this . hsEventBusService . layerAdditions . subscribe ( ( layerDescriptor ) => { if ( this . hsLayerUtilsService . isLayerVectorLayer ( layerDescriptor . layer ) ) { this . initLayerStyle ( layerDescriptor . layer as VectorLayer < VectorSource < Geometry > > ) ; } } ) ; } pin_white_blue_highlight = ( feature , resolution ) : Array < Style > => { return [ new Style ( { image : new Icon ( { src : getHighlighted ( feature ) ? this . hsUtilsService . getAssetsPath ( ) + 'img/pin_white_red32.png' : this . hsUtilsService . getAssetsPath ( ) + 'img/pin_white_blue32.png' , crossOrigin : 'anonymous' , anchor : [ 0.5 , 1 ] , } ) , } ) , ] ; } ; isVectorLayer ( layer ) { if ( this . hsUtilsService . instOf ( layer , VectorLayer ) ) { return true ; } else { return false ; } } param layer param isClustered returns getLayerSource ( layer , isClustered ) { if ( ! layer ) { return ; } let src ; if ( isClustered ) { src = ( layer . getSource ( ) as Cluster ) . getSource ( ) ; } else { src = layer . getSource ( ) ; } return src ; } param layer async styleClusteredLayer ( layer ) { await this . fill ( layer ) ; if ( ! this . styleObject . rules . find ( ( r ) => { try { return r . filter [ 1 ] [ 1 ] == 'features' ; } catch ( ex ) { return false ; } } ) ) { const singleFeatureFilter = [ '||' , [ '==' , 'features' , 'undefined' ] , [ '==' , 'features' , '[object Object]' ] , ] ; for ( const rule of this . styleObject . rules ) { rule . filter = rule . filter ?. length > 0 ? [ '&&' , [ ... singleFeatureFilter ] , rule . filter ] : [ ... singleFeatureFilter ] ; } await this . addRule ( 'Cluster' ) ; } let style = layer . getStyle ( ) ; if ( this . hsUtilsService . instOf ( this . layer . getSource ( ) , Cluster ) && this . hsUtilsService . isFunction ( style ) ) { style = this . wrapStyleForClusters ( style as StyleFunction ) ; layer . setStyle ( style ) ; } } param layer async initLayerStyle ( layer ) { if ( ! this . isVectorLayer ( layer ) ) { return ; } let sld = getSld ( layer ) ; const qml = getQml ( layer ) ; let style = layer . getStyle ( ) ; if ( ( ! style || style == createDefaultStyle ) && ! sld && ! qml ) { sld = defaultStyle ; setSld ( layer , defaultStyle ) ; } if ( ( sld || qml ) && ( ! style || style == createDefaultStyle ) ) { style = ( await this . parseStyle ( sld ?? qml ) ) . style ; if ( style ) { layer . setStyle ( style ) ; } if ( getCluster ( layer ) ) { await this . styleClusteredLayer ( layer ) ; } } else if ( style && ! sld && ! qml && ! this . hsUtilsService . isFunction ( style ) && ! Array . isArray ( style ) ) { const customJson = this . hsSaveMapService . serializeStyle ( style as Style ) ; const sld = ( await this . parseStyle ( customJson ) ) . sld ; if ( sld ) { setSld ( layer , sld ) ; } } this . sld = sld ; } param style returns async parseStyle ( style ) { if ( typeof style == 'string' && ( style as string ) . includes ( 'StyledLayerDescriptor' ) ) { return { sld : style , style : await this . sldToOlStyle ( style ) } ; } if ( typeof style == 'string' && ( style as string ) . includes ( '<qgis' ) ) { return { qml : style , style : await this . qmlToOlStyle ( style ) } ; } else if ( typeof style == 'object' ) { return parseStyle ( style ) ; } } param layer async fill ( layer ) { try { if ( ! layer ) { return ; } this . layer = layer ; this . layerTitle = getTitle ( layer ) ; const sld = getSld ( layer ) ; const qml = getQml ( layer ) ; if ( sld != undefined ) { this . styleObject = await this . sldToJson ( sld ) ; } else if ( qml != undefined ) { this . styleObject = await this . qmlToJson ( qml ) ; } else { this . styleObject = { name : 'untitled style' , rules : [ ] } ; } this . geostylerWorkaround ( ) ; } catch ( ex ) { this . hsLogService . error ( ex . message ) ; } } /**
     * Tweak geostyler object attributes to mitigate
     * some discrepancies between opacity and fillOpacity usage
     */ geostylerWorkaround ( ) { if ( this . styleObject . rules ) { for ( const rule of this . styleObject . rules ) { if ( rule . symbolizers ) { for ( const symbol of rule . symbolizers . filter ( ( symb ) => symb . kind == 'Fill' ) as FillSymbolizer [ ] ) { symbol . opacity = symbol . fillOpacity ; } } } } } /**
     * Convert SLD to OL style object
     */ async sldToOlStyle ( sld ) { try { const sldObject = await this . sldToJson ( sld ) ; return await this . geoStylerStyleToOlStyle ( sldObject ) ; } catch ( ex ) { this . hsLogService . error ( ex ) ; } } /**
     * Convert QML to OL style object
     */ async qmlToOlStyle ( qml ) { try { const styleObject = await this . qmlToJson ( qml ) ; return await this . geoStylerStyleToOlStyle ( styleObject ) ; } catch ( ex ) { this . hsLogService . error ( ex ) ; } } public async geoStylerStyleToOlStyle ( sldObject ) { const olConverter = ( OpenLayersParser as any ) . default ? new ( OpenLayersParser as any ) . default ( ) : new OpenLayersParser ( ) ; const { output : style } = await olConverter . writeStyle ( sldObject ) ; return style ; } param sld returns private async sldToJson ( sld ) { const { output : sldObject } = await this . sldParser . readStyle ( sld ) ; return sldObject ; } param qml returns private async qmlToJson ( qml ) { const result = await this . qmlParser . readStyle ( qml ) ; if ( result . output ) { return result . output ; } else { this . hsLogService . error ( result . errors ) ; } } private async jsonToSld ( styleObject ) { const { output : sld } = await this . sldParser . writeStyle ( styleObject ) ; return sld ; } async addRule ( kind ) { switch ( kind ) { case 'Cluster' : this . styleObject . rules . push ( { name : 'Cluster rule' , filter : [ '&&' , [ '!=' , 'features' , 'undefined' ] , [ '!=' , 'features' , '[object Object]' ] , ] , symbolizers : [ { kind : 'Mark' , color : '#FFFFFF' , strokeOpacity : 0.41 , strokeColor : '#0099ff' , strokeWidth : 2 , wellKnownName : 'circle' , radius : 10 , } , { kind : 'Text' , label : '{{features}}' , haloColor : '#fff' , color : '#000' , offset : [ 0 , 0 ] , } , ] , } ) ; break ; case 'Simple' : default : this . styleObject . rules . push ( { name : 'Untitled rule' , symbolizers : [ ] , } ) ; } await this . save ( ) ; } async removeRule ( rule ) { this . styleObject . rules . splice ( this . styleObject . rules . indexOf ( rule ) , 1 ) ; await this . save ( ) ; } encodeTob64 ( str ) { return btoa ( encodeURIComponent ( str ) . replace ( /%([0-9A-F]{2})/g , ( match , p1 ) => { return String . fromCharCode ( parseInt ( p1 , 16 ) ) ; } ) ) ; } decodeToUnicode ( str ) { return decodeURIComponent ( Array . prototype . map . call ( atob ( str ) , ( c ) => { return '%' + ( '00' + c . charCodeAt ( 0 ) . toString ( 16 ) ) . slice ( - 2 ) ; } ) . join ( '' ) ) ; } async save ( ) { try { let style = await this . geoStylerStyleToOlStyle ( this . styleObject ) ; if ( this . styleObject . rules . length == 0 ) { this . hsLogService . warn ( 'Missing style rules for layer' , this . layer ) ; style = createDefaultStyle ; } if ( this . hsUtilsService . instOf ( this . layer . getSource ( ) , Cluster ) && this . hsUtilsService . isFunction ( style ) ) { style = this . wrapStyleForClusters ( style as StyleFunction ) ; } this . layer . setStyle ( style ) ; const sld = await this . jsonToSld ( this . styleObject ) ; setSld ( this . layer , sld ) ; this . sld = sld ; this . onSet . next ( this . layer ) ; } catch ( ex ) { this . hsLogService . error ( ex ) ; } } param style returns wrapStyleForClusters ( style ) { return ( feature , resolution ) => { const tmp = style ( feature , resolution ) ; if ( ! tmp ) { return ; } if ( Array . isArray ( tmp ) ) { for ( const evaluatedStyle of tmp as Style [ ] ) { if ( evaluatedStyle . getText && evaluatedStyle . getText ( ) ?. getText ( ) ?. includes ( '[object Object]' ) ) { const featureListSerialized = evaluatedStyle . getText ( ) . getText ( ) ; const fCount = featureListSerialized . split ( ',' ) . length . toString ( ) ; evaluatedStyle . getText ( ) . setText ( fCount ) ; } } } return tmp ; } ; } async reset ( ) { setSld ( this . layer , undefined ) ; this . layer . setStyle ( createDefaultStyle ) ; await this . initLayerStyle ( this . layer ) ; await this . fill ( this . layer ) ; await this . save ( ) ; } async loadSld ( sld ) { try { await this . sldParser . readStyle ( sld ) ; setSld ( this . layer , sld ) ; await this . fill ( this . layer ) ; await this . save ( ) ; } catch ( err ) { console . warn ( 'SLD could not be parsed' ) ; } } }