import type { Map as MbMap , AnyLayer as MbLayer , GeoJSONSource as MbGeoJSONSource , VectorSource as MbVectorSource , } from '@kbn/mapbox-gl' ; import { Feature } from 'geojson' ; import { i18n } from '@kbn/i18n' ; import { VectorStyle } from '../../../styles/vector/vector_style' ; import { LAYER_TYPE , SOURCE_TYPES } from '../../../../../common/constants' ; import { NO_RESULTS_ICON_AND_TOOLTIPCONTENT , AbstractVectorLayer , VectorLayerArguments , } from '../vector_layer' ; import { IMvtVectorSource } from '../../../sources/vector_source' ; import { DataRequestContext } from '../../../../actions' ; import { StyleMetaDescriptor , TileMetaFeature , VectorLayerDescriptor , } from '../../../../../common/descriptor_types' ; import { ESSearchSource } from '../../../sources/es_search_source' ; import { LayerIcon } from '../../layer' ; import { MvtSourceData , syncMvtSourceData } from './mvt_source_data' ; export const ES_MVT_META_LAYER_NAME = 'meta' ; const ES_MVT_HITS_TOTAL_RELATION = 'hits.total.relation' ; const ES_MVT_HITS_TOTAL_VALUE = 'hits.total.value' ; const MAX_RESULT_WINDOW_DATA_REQUEST_ID = 'maxResultWindow' ; export class MvtVectorLayer extends AbstractVectorLayer { static createDescriptor ( descriptor , mapColors ? ) { const layerDescriptor = super . createDescriptor ( descriptor , mapColors ) ; layerDescriptor . type = LAYER_TYPE . MVT_VECTOR ; if ( ! layerDescriptor . style ) { const styleProperties = VectorStyle . createDefaultStyleProperties ( mapColors ? mapColors : [ ] ) ; layerDescriptor . style = VectorStyle . createDescriptor ( styleProperties ) ; } return layerDescriptor ; } readonly _source : IMvtVectorSource ; constructor ( { layerDescriptor , source } ) { super ( { layerDescriptor , source } ) ; this . _source = source as IMvtVectorSource ; } getFeatureId ( feature ) { if ( ! this . getSource ( ) . isESSource ( ) ) { return feature . id ; } return this . getSource ( ) . getType ( ) === SOURCE_TYPES . ES_SEARCH ? feature . properties ?. _id : feature . properties ?. _key ; } getLayerIcon ( isTocIcon ) { if ( ! this . getSource ( ) . isESSource ( ) ) { return { icon : this . getCurrentStyle ( ) . getIcon ( false ) , tooltipContent : null , areResultsTrimmed : false , } ; } const tileMetaFeatures = this . _getMetaFromTiles ( ) ; if ( ! tileMetaFeatures . length ) { return NO_RESULTS_ICON_AND_TOOLTIPCONTENT ; } if ( this . getSource ( ) . getType ( ) !== SOURCE_TYPES . ES_SEARCH ) { return { icon : this . getCurrentStyle ( ) . getIcon ( false ) , tooltipContent : null , areResultsTrimmed : false , } ; } const maxResultWindow = this . _getMaxResultWindow ( ) ; if ( maxResultWindow === undefined ) { return { icon : this . getCurrentStyle ( ) . getIcon ( false ) , tooltipContent : null , areResultsTrimmed : false , } ; } let totalFeaturesCount = 0 ; let tilesWithFeatures = 0 ; tileMetaFeatures . forEach ( ( tileMeta ) => { const count = tileMeta && tileMeta . properties ? tileMeta . properties [ ES_MVT_HITS_TOTAL_VALUE ] : 0 ; if ( count > 0 ) { totalFeaturesCount += count ; tilesWithFeatures ++ ; } } ) ; if ( totalFeaturesCount === 0 ) { return NO_RESULTS_ICON_AND_TOOLTIPCONTENT ; } const areResultsTrimmed = tileMetaFeatures . some ( ( tileMeta ) => { if ( tileMeta ?. properties ?. [ ES_MVT_HITS_TOTAL_RELATION ] === 'gte' ) { return tileMeta ?. properties ?. [ ES_MVT_HITS_TOTAL_VALUE ] >= maxResultWindow + 1 ; } else { return false ; } } ) ; const canMultiCountShapes = ! this . getStyle ( ) . getIsPointsOnly ( ) && totalFeaturesCount > 1 && tilesWithFeatures > 1 ; const countPrefix = canMultiCountShapes ? '~' : '' ; const countMsg = areResultsTrimmed ? i18n . translate ( 'xpack.maps.tiles.resultsTrimmedMsg' , { defaultMessage : `Results limited to {countPrefix}{count} documents.` , values : { count : totalFeaturesCount . toLocaleString ( ) , countPrefix , } , } ) : i18n . translate ( 'xpack.maps.tiles.resultsCompleteMsg' , { defaultMessage : `Found {countPrefix}{count} documents.` , values : { count : totalFeaturesCount . toLocaleString ( ) , countPrefix , } , } ) ; const tooltipContent = canMultiCountShapes ? countMsg + i18n . translate ( 'xpack.maps.tiles.shapeCountMsg' , { defaultMessage : ' This count is approximate.' , } ) : countMsg ; return { icon : this . getCurrentStyle ( ) . getIcon ( isTocIcon && areResultsTrimmed ) , tooltipContent , areResultsTrimmed , } ; } _getMaxResultWindow ( ) { const dataRequest = this . getDataRequest ( MAX_RESULT_WINDOW_DATA_REQUEST_ID ) ; if ( ! dataRequest ) { return ; } const data = dataRequest . getData ( ) as { maxResultWindow : number ; } | undefined ; return data ? data . maxResultWindow : undefined ; } async _syncMaxResultWindow ( { startLoading , stopLoading } ) { const prevDataRequest = this . getDataRequest ( MAX_RESULT_WINDOW_DATA_REQUEST_ID ) ; if ( prevDataRequest ) { return ; } const requestToken = Symbol ( `${ this . getId ( ) }-${ MAX_RESULT_WINDOW_DATA_REQUEST_ID }` ) ; startLoading ( MAX_RESULT_WINDOW_DATA_REQUEST_ID , requestToken ) ; const maxResultWindow = await ( this . getSource ( ) as ESSearchSource ) . getMaxResultWindow ( ) ; stopLoading ( MAX_RESULT_WINDOW_DATA_REQUEST_ID , requestToken , { maxResultWindow } ) ; } async syncData ( syncContext ) { if ( this . getSource ( ) . getType ( ) === SOURCE_TYPES . ES_SEARCH ) { await this . _syncMaxResultWindow ( syncContext ) ; } await this . _syncSourceStyleMeta ( syncContext , this . getSource ( ) , this . getCurrentStyle ( ) ) ; await this . _syncSourceFormatters ( syncContext , this . getSource ( ) , this . getCurrentStyle ( ) ) ; await syncMvtSourceData ( { layerId : this . getId ( ) , prevDataRequest : this . getSourceDataRequest ( ) , requestMeta : await this . _getVectorSourceRequestMeta ( syncContext . isForceRefresh , syncContext . dataFilters , this . getSource ( ) , this . getCurrentStyle ( ) ) , source : this . getSource ( ) as IMvtVectorSource , syncContext , } ) ; } _syncSourceBindingWithMb ( mbMap ) { const mbSource = mbMap . getSource ( this . getMbSourceId ( ) ) ; if ( mbSource ) { return ; } const sourceDataRequest = this . getSourceDataRequest ( ) ; if ( ! sourceDataRequest ) { return ; } const sourceData = sourceDataRequest . getData ( ) as MvtSourceData | undefined ; if ( ! sourceData ) { return ; } const mbSourceId = this . getMbSourceId ( ) ; mbMap . addSource ( mbSourceId , { type : 'vector' , tiles : [ sourceData . tileUrl ] , minzoom : sourceData . tileMinZoom , maxzoom : sourceData . tileMaxZoom , } ) ; } getMbLayerIds ( ) { return [ ... super . getMbLayerIds ( ) , this . _getMbTooManyFeaturesLayerId ( ) ] ; } ownsMbSourceId ( mbSourceId ) { return this . getMbSourceId ( ) === mbSourceId ; } _getMbTooManyFeaturesLayerId ( ) { return this . makeMbLayerId ( 'toomanyfeatures' ) ; } _syncStylePropertiesWithMb ( mbMap ) { const mbSource = mbMap . getSource ( this . getMbSourceId ( ) ) ; if ( ! mbSource ) { return ; } const sourceDataRequest = this . getSourceDataRequest ( ) ; if ( ! sourceDataRequest ) { return ; } const sourceData = sourceDataRequest . getData ( ) as MvtSourceData | undefined ; if ( ! sourceData || sourceData . tileSourceLayer === '' ) { return ; } this . _setMbLabelProperties ( mbMap , sourceData . tileSourceLayer ) ; this . _setMbPointsProperties ( mbMap , sourceData . tileSourceLayer ) ; this . _setMbLinePolygonProperties ( mbMap , sourceData . tileSourceLayer ) ; this . _syncTooManyFeaturesProperties ( mbMap ) ; } _syncTooManyFeaturesProperties ( mbMap ) { if ( this . getSource ( ) . getType ( ) !== SOURCE_TYPES . ES_SEARCH ) { return ; } const maxResultWindow = this . _getMaxResultWindow ( ) ; if ( maxResultWindow === undefined ) { return ; } const tooManyFeaturesLayerId = this . _getMbTooManyFeaturesLayerId ( ) ; if ( ! mbMap . getLayer ( tooManyFeaturesLayerId ) ) { const mbTooManyFeaturesLayer = { id : tooManyFeaturesLayerId , type : 'line' , source : this . getId ( ) , paint : { } , } ; mbTooManyFeaturesLayer [ 'source-layer' ] = ES_MVT_META_LAYER_NAME ; mbMap . addLayer ( mbTooManyFeaturesLayer ) ; mbMap . setFilter ( tooManyFeaturesLayerId , [ 'all' , [ '==' , [ 'get' , ES_MVT_HITS_TOTAL_RELATION ] , 'gte' ] , [ '>=' , [ 'get' , ES_MVT_HITS_TOTAL_VALUE ] , maxResultWindow + 1 ] , ] ) ; mbMap . setPaintProperty ( tooManyFeaturesLayerId , 'line-color' , this . getCurrentStyle ( ) . getPrimaryColor ( ) ) ; mbMap . setPaintProperty ( tooManyFeaturesLayerId , 'line-width' , 3 ) ; mbMap . setPaintProperty ( tooManyFeaturesLayerId , 'line-dasharray' , [ 2 , 1 ] ) ; mbMap . setPaintProperty ( tooManyFeaturesLayerId , 'line-opacity' , this . getAlpha ( ) ) ; } this . syncVisibilityWithMb ( mbMap , tooManyFeaturesLayerId ) ; mbMap . setLayerZoomRange ( tooManyFeaturesLayerId , this . getMinZoom ( ) , this . getMaxZoom ( ) ) ; } _requiresPrevSourceCleanup ( mbMap ) { const mbSource = mbMap . getSource ( this . getMbSourceId ( ) ) as MbVectorSource | MbGeoJSONSource ; if ( ! mbSource ) { return false ; } if ( ! ( 'tiles' in mbSource ) ) { return true ; } const mbTileSource = mbSource as MbVectorSource ; const sourceDataRequest = this . getSourceDataRequest ( ) ; if ( ! sourceDataRequest ) { return false ; } const sourceData = sourceDataRequest . getData ( ) as MvtSourceData | undefined ; if ( ! sourceData ) { return false ; } const isSourceDifferent = mbTileSource . tiles ?. [ 0 ] !== sourceData . tileUrl || mbTileSource . minzoom !== sourceData . tileMinZoom || mbTileSource . maxzoom !== sourceData . tileMaxZoom ; if ( isSourceDifferent ) { return true ; } const layerIds = this . getMbLayerIds ( ) ; for ( let i = 0 ; i < layerIds . length ; i ++ ) { const mbLayer = mbMap . getLayer ( layerIds [ i ] ) ; if ( mbLayer && mbLayer . sourceLayer !== sourceData . tileSourceLayer && mbLayer . sourceLayer !== ES_MVT_META_LAYER_NAME ) { return true ; } } return false ; } syncLayerWithMB ( mbMap ) { this . _removeStaleMbSourcesAndLayers ( mbMap ) ; this . _syncSourceBindingWithMb ( mbMap ) ; this . _syncStylePropertiesWithMb ( mbMap ) ; } getJoins ( ) { return [ ] ; } getMinZoom ( ) { return Math . max ( this . _source . getMinZoom ( ) , super . getMinZoom ( ) ) ; } getFeatureById ( id ) { return null ; } async getStyleMetaDescriptorFromLocalFeatures ( ) { return await this . getCurrentStyle ( ) . pluckStyleMetaFromTileMeta ( this . _getMetaFromTiles ( ) ) ; } }