import { MapEnv , StyleSetEvaluator , Value , ValueMap } from "@here/harp-datasource-protocol/index-decoder" ; import { TileKey } from "@here/harp-geoutils" ; import { assert , ILogger , LoggerManager } from "@here/harp-utils" ; import * as Long from "long" ; import { ShapeUtils , Vector2 , Vector3 } from "three" ; import { DataAdapter } from "../../DataAdapter" ; import { DecodeInfo } from "../../DecodeInfo" ; import { IGeometryProcessor , ILineGeometry , IPolygonGeometry } from "../../IGeometryProcessor" ; import { ComposedDataFilter , OmvFeatureFilter , OmvFeatureModifier , OmvGenericFeatureFilter , OmvGenericFeatureModifier } from "../../OmvDataFilter" ; import { FeatureModifierId , OmvDecoderOptions , OmvFeatureFilterDescription , OmvGeometryType } from "../../OmvDecoderDefs" ; import { OmvPoliticalViewFeatureModifier } from "../../OmvPoliticalViewFeatureModifier" ; import { isArrayBufferLike } from "../../OmvUtils" ; import { StyleSetDataFilter } from "../../StyleSetDataFilter" ; import { FeatureAttributes , GeometryCommands , isClosePathCommand , isLineToCommand , isMoveToCommand , OmvVisitor , visitOmv } from "./OmvData" ; import { com } from "./proto/vector_tile" ; const propertyCategories = [ "stringValue" , "floatValue" , "doubleValue" , "intValue" , "uintValue" , "sintValue" , "boolValue" ] ; const logger = LoggerManager . instance . create ( "OmvDataAdapter" , { enabled : false } ) ; function simplifiedValue ( value ) { const hasOwnProperty = Object . prototype . hasOwnProperty ; for ( const category of propertyCategories ) { if ( hasOwnProperty . call ( value , category ) ) { const v = value [ category as keyof com . mapbox . pb . Tile . IValue ] ; if ( v === undefined ) { throw new Error ( "unpexted undefined value" ) ; } return Long . isLong ( v ) ? ( v as any ) . toNumber ( ) : v ; } } throw new Error ( "not happening" ) ; } function decodeFeatureId ( feature , properties , logger ? ) { if ( properties . id !== undefined && properties . id !== null ) { return properties . id as number | string ; } if ( feature . hasOwnProperty ( "id" ) ) { const id = feature . id ; if ( typeof id === "number" ) { return id ; } else if ( id ) { if ( logger !== undefined && id . greaterThan ( Number . MAX_SAFE_INTEGER ) ) { logger . error ( "Invalid ID: Larger than largest available Number in feature: " , feature ) ; } return id . toNumber ( ) ; } } return undefined ; } function readAttributes ( layer , feature ) { const attrs = new FeatureAttributes ( ) ; const attributes = { } ; attrs . accept ( layer , feature , { visitAttribute : ( name , value ) => { attributes [ name ] = simplifiedValue ( value ) ; return true ; } } ) ; return attributes ; } export function asGeometryType ( feature ) { if ( feature === undefined ) { return OmvGeometryType . UNKNOWN ; } switch ( feature . type ) { case com . mapbox . pb . Tile . GeomType . UNKNOWN : return OmvGeometryType . UNKNOWN ; case com . mapbox . pb . Tile . GeomType . POINT : return OmvGeometryType . POINT ; case com . mapbox . pb . Tile . GeomType . LINESTRING : return OmvGeometryType . LINESTRING ; case com . mapbox . pb . Tile . GeomType . POLYGON : return OmvGeometryType . POLYGON ; default : return OmvGeometryType . UNKNOWN ; } } function checkWinding ( multipolygon ) { const firstPolygon = multipolygon [ 0 ] ; if ( firstPolygon === undefined || firstPolygon . rings . length === 0 ) { return ; } const isOuterRingClockWise = ShapeUtils . area ( firstPolygon . rings [ 0 ] ) > 0 ; if ( ! isOuterRingClockWise ) { for ( const polygon of multipolygon ) { for ( const ring of polygon . rings ) { ring . reverse ( ) ; } } } } function roundUpCoordinates ( coordinates , layerExtents ) { coordinates . forEach ( p => { if ( p . x === layerExtents - 1 ) { p . x = layerExtents ; } } ) ; } function roundUpPolygonCoordinates ( geometry , layerExtents ) { geometry . forEach ( polygon => polygon . rings . forEach ( r => roundUpCoordinates ( r , layerExtents ) ) ) ; } function roundUpLineCoordinates ( geometry , layerExtents ) { geometry . forEach ( line => roundUpCoordinates ( line . positions , layerExtents ) ) ; } function createFeatureModifier ( filterDescription , featureModifierId ? ) { switch ( featureModifierId ) { case FeatureModifierId . default : return new OmvGenericFeatureModifier ( filterDescription ) ; default : assert ( ! "Unrecognized feature modifier id, using default!" ) ; return new OmvGenericFeatureModifier ( filterDescription ) ; } } /**
 * The class `OmvDataAdapter` converts OMV protobuf geo data
 * to geometries for the given `IGeometryProcessor`.
 */ export class OmvDataAdapter implements DataAdapter , OmvVisitor { private readonly m_geometryCommands = new GeometryCommands ( ) ; private m_tileKey ! : TileKey ; private m_layer ! : com . mapbox . pb . Tile . ILayer ; private m_dataFilter ? : OmvFeatureFilter ; private m_featureModifiers ? : OmvFeatureModifier [ ] ; private m_processor ! : IGeometryProcessor ; private m_roundUpCoordinatesIfNeeded : boolean = false ; /**
     * The [[OmvFeatureFilter]] used to filter features.
     */ get dataFilter ( ) : OmvFeatureFilter | undefined { return this . m_dataFilter ; } param options param styleSetEvaluator configure ( options , styleSetEvaluator ) { if ( options . filterDescription !== undefined ) { if ( options . filterDescription !== null ) { const filterDescription = options . filterDescription ; const featureModifiersIds = options . featureModifiers ; this . m_dataFilter = new OmvGenericFeatureFilter ( filterDescription ) ; const featureModifiers = [ ] ; if ( featureModifiersIds !== undefined ) { featureModifiersIds . forEach ( fmId => { featureModifiers . push ( createFeatureModifier ( filterDescription , fmId ) ) ; } ) ; } else { featureModifiers . push ( createFeatureModifier ( filterDescription , FeatureModifierId . default ) ) ; } this . m_featureModifiers = featureModifiers ; } else { this . m_dataFilter = undefined ; this . m_featureModifiers = undefined ; } const styleSetDataFilter = new StyleSetDataFilter ( styleSetEvaluator ) ; this . m_dataFilter = this . m_dataFilter ? new ComposedDataFilter ( [ styleSetDataFilter , this . m_dataFilter ] ) : styleSetDataFilter ; } if ( options . politicalView !== undefined ) { const politicalView = options . politicalView ; let featureModifiers = this . m_featureModifiers ; if ( featureModifiers ) { featureModifiers = featureModifiers . filter ( fm => ! ( fm instanceof OmvPoliticalViewFeatureModifier ) ) ; } if ( politicalView . length !== 0 ) { assert ( politicalView . length === 2 , "The political view must be specified as two letters ISO 3166-1 standard!" ) ; const povFeatureModifier = new OmvPoliticalViewFeatureModifier ( politicalView ) ; if ( featureModifiers ) { featureModifiers . push ( povFeatureModifier ) ; } else { featureModifiers = [ povFeatureModifier ] ; } } this . m_featureModifiers = featureModifiers && featureModifiers . length > 0 ? featureModifiers : undefined ; } this . m_roundUpCoordinatesIfNeeded = options . roundUpCoordinatesIfNeeded ?? false ; } @override
      canProcess ( data ) { return isArrayBufferLike ( data ) ; } @override
      process ( data , decodeInfo , geometryProcessor ) { const { tileKey } = decodeInfo ; const payload = new Uint8Array ( data ) ; const proto = com . mapbox . pb . Tile . decode ( payload ) ; this . m_tileKey = tileKey ; this . m_processor = geometryProcessor ; visitOmv ( proto , this ) ; } param layer visitLayer ( layer ) { this . m_layer = layer ; const storageLevel = this . m_tileKey . level ; const layerName = layer . name ; if ( this . m_dataFilter !== undefined && ! this . m_dataFilter . wantsLayer ( layerName , storageLevel ) ) { return false ; } return true ; } param feature visitPointFeature ( feature ) { if ( feature . geometry === undefined ) { return ; } const modifierFunc = this . m_featureModifiers ?. [ 0 ] . doProcessPointFeature ; const properties = this . filterAndModifyFeature ( feature , this . m_dataFilter ?. wantsPointFeature , modifierFunc ) ; if ( ! properties ) { return ; } const layerName = this . m_layer . name ; const layerExtents = this . m_layer . extent ?? 4096 ; const geometry = [ ] ; this . m_geometryCommands . accept ( feature . geometry , { type : "Point" , visitCommand : command => { if ( isMoveToCommand ( command ) ) { geometry . push ( new Vector3 ( command . position . x , command . position . y , 0 ) ) ; } } } ) ; if ( geometry . length === 0 ) { return ; } this . m_processor . processPointFeature ( layerName , layerExtents , geometry , properties , decodeFeatureId ( feature , properties , logger ) ) ; } param feature visitLineFeature ( feature ) { if ( feature . geometry === undefined ) { return ; } const modifierFunc = this . m_featureModifiers ?. [ 0 ] . doProcessLineFeature ; const properties = this . filterAndModifyFeature ( feature , this . m_dataFilter ?. wantsLineFeature , modifierFunc ) ; if ( ! properties ) { return ; } const layerName = this . m_layer . name ; const layerExtents = this . m_layer . extent ?? 4096 ; const geometry = [ ] ; let positions ; this . m_geometryCommands . accept ( feature . geometry , { type : "Line" , visitCommand : command => { if ( isMoveToCommand ( command ) ) { positions = [ command . position ] ; geometry . push ( { positions } ) ; } else if ( isLineToCommand ( command ) ) { positions . push ( command . position ) ; } } } ) ; if ( geometry . length === 0 ) { return ; } if ( this . mustRoundUpCoordinates ) { roundUpLineCoordinates ( geometry , layerExtents ) ; } this . m_processor . processLineFeature ( layerName , layerExtents , geometry , properties , decodeFeatureId ( feature , properties , logger ) ) ; } param feature visitPolygonFeature ( feature ) { if ( feature . geometry === undefined ) { return ; } const modifierFunc = this . m_featureModifiers ?. [ 0 ] . doProcessPolygonFeature ; const properties = this . filterAndModifyFeature ( feature , this . m_dataFilter ?. wantsPolygonFeature , modifierFunc ) ; if ( ! properties ) { return ; } const layerName = this . m_layer . name ; const layerExtents = this . m_layer . extent ?? 4096 ; const geometry = [ ] ; let currentPolygon ; let currentRing ; let exteriorWinding ; this . m_geometryCommands . accept ( feature . geometry , { type : "Polygon" , visitCommand : command => { if ( isMoveToCommand ( command ) ) { currentRing = [ command . position ] ; } else if ( isLineToCommand ( command ) ) { currentRing . push ( command . position ) ; } else if ( isClosePathCommand ( command ) ) { if ( currentRing !== undefined && currentRing . length > 0 ) { const currentRingWinding = Math . sign ( ShapeUtils . area ( currentRing ) ) ; if ( exteriorWinding === undefined ) { exteriorWinding = currentRingWinding ; } if ( currentRingWinding === exteriorWinding ) { currentPolygon = { rings : [ ] } ; geometry . push ( currentPolygon ) ; } currentRing . push ( currentRing [ 0 ] . clone ( ) ) ; currentPolygon ?. rings . push ( currentRing ) ; } } } } ) ; if ( geometry . length === 0 ) { return ; } if ( this . mustRoundUpCoordinates ) { roundUpPolygonCoordinates ( geometry , layerExtents ) ; } checkWinding ( geometry ) ; this . m_processor . processPolygonFeature ( layerName , layerExtents , geometry , properties , decodeFeatureId ( feature , properties , logger ) ) ; } param feature param filterFunc param modifierFunc returns private filterAndModifyFeature ( feature , filterFunc ? , modifierFunc ? ) { const storageLevel = this . m_tileKey . level ; const layerName = this . m_layer . name ; const geometryType = asGeometryType ( feature ) ; if ( this . m_dataFilter && filterFunc ! . apply ( this . m_dataFilter , [ layerName , geometryType , storageLevel ] ) === false ) { return undefined ; } const properties = readAttributes ( this . m_layer , feature ) ; const env = new MapEnv ( properties ) ; if ( this . m_featureModifiers ?. find ( fm => { assert ( modifierFunc !== undefined ) ; return ! modifierFunc ! . apply ( fm , [ layerName , env , this . m_tileKey . level ] ) ; } ) !== undefined ) { return undefined ; } return properties ; } private get mustRoundUpCoordinates ( ) : boolean { return ( this . m_roundUpCoordinatesIfNeeded && this . m_tileKey . level < 5 && this . m_tileKey . column === this . m_tileKey . columnCount ( ) - 1 ) ; } }