import type { Map as MbMap } from '@kbn/mapbox-gl' ; import { Query } from 'src/plugins/data/public' ; import _ from 'lodash' ; import React , { ReactElement } from 'react' ; import { EuiIcon } from '@elastic/eui' ; import uuid from 'uuid/v4' ; import { FeatureCollection } from 'geojson' ; import { DataRequest } from '../util/data_request' ; import { LAYER_TYPE , MAX_ZOOM , MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER , MIN_ZOOM , SOURCE_BOUNDS_DATA_REQUEST_ID , SOURCE_DATA_REQUEST_ID , } from '../../../common/constants' ; import { copyPersistentState } from '../../reducers/copy_persistent_state' ; import { Attribution , LayerDescriptor , MapExtent , StyleDescriptor , TileMetaFeature , Timeslice , StyleMetaDescriptor , } from '../../../common/descriptor_types' ; import { ImmutableSourceProperty , ISource , SourceEditorArgs } from '../sources/source' ; import { DataRequestContext } from '../../actions' ; import { IStyle } from '../styles/style' ; import { LICENSED_FEATURES } from '../../licensed_features' ; import { IESSource } from '../sources/es_source' ; export interface ILayer { getBounds ( dataRequestContext ) : Promise < MapExtent | null > ; getDataRequest ( id ) : DataRequest | undefined ; getDisplayName ( source ? ) : Promise < string > ; getId ( ) : string ; getSourceDataRequest ( ) : DataRequest | undefined ; getSource ( ) : ISource ; getSourceForEditing ( ) : ISource ; syncData ( syncContext ) : void ; supportsElasticsearchFilters ( ) : boolean ; supportsFitToBounds ( ) : Promise < boolean > ; getAttributions ( ) : Promise < Attribution [ ] > ; getLabel ( ) : string ; hasLegendDetails ( ) : Promise < boolean > ; renderLegendDetails ( ) : ReactElement < any > | null ; showAtZoomLevel ( zoom ) : boolean ; getMinZoom ( ) : number ; getMaxZoom ( ) : number ; getMinSourceZoom ( ) : number ; getAlpha ( ) : number ; getQuery ( ) : Query | null ; getStyle ( ) : IStyle ; getStyleForEditing ( ) : IStyle ; getCurrentStyle ( ) : IStyle ; getImmutableSourceProperties ( ) : Promise < ImmutableSourceProperty [ ] > ; renderSourceSettingsEditor ( sourceEditorArgs ) : ReactElement < any > | null ; isLayerLoading ( ) : boolean ; isLoadingBounds ( ) : boolean ; isFilteredByGlobalTime ( ) : Promise < boolean > ; hasErrors ( ) : boolean ; getErrors ( ) : string ; getMbLayerIds ( ) : string [ ] ; getMbSourceId ( ) : string ; ownsMbLayerId ( mbLayerId ) : boolean ; ownsMbSourceId ( mbSourceId ) : boolean ; syncLayerWithMB ( mbMap , timeslice ? ) : void ; getLayerTypeIconName ( ) : string ; isInitialDataLoadComplete ( ) : boolean ; getIndexPatternIds ( ) : string [ ] ; getQueryableIndexPatternIds ( ) : string [ ] ; getType ( ) : LAYER_TYPE ; isVisible ( ) : boolean ; cloneDescriptor ( ) : Promise < LayerDescriptor > ; renderStyleEditor ( onStyleDescriptorChange ) : ReactElement < any > | null ; getInFlightRequestTokens ( ) : symbol [ ] ; getPrevRequestToken ( dataId ) : symbol | undefined ; destroy : ( ) => void ; isPreviewLayer : ( ) => boolean ; areLabelsOnTop : ( ) => boolean ; supportsLabelsOnTop : ( ) => boolean ; isFittable ( ) : Promise < boolean > ; isIncludeInFitToBounds ( ) : boolean ; getLicensedFeatures ( ) : Promise < LICENSED_FEATURES [ ] > ; getLayerIcon ( isTocIcon ) : LayerIcon ; getDescriptor ( ) : LayerDescriptor ; getGeoFieldNames ( ) : string [ ] ; getStyleMetaDescriptorFromLocalFeatures ( ) : Promise < StyleMetaDescriptor | null > ; isBasemap ( order ) : boolean ; } export type LayerIcon = { icon : ReactElement ; tooltipContent ? : string | null ; areResultsTrimmed ? : boolean ; } ; export interface ILayerArguments { layerDescriptor : LayerDescriptor ; source : ISource ; } export class AbstractLayer implements ILayer { protected readonly _descriptor : LayerDescriptor ; protected readonly _source : ISource ; protected readonly _dataRequests : DataRequest [ ] ; static createDescriptor ( options ) { return { ... options , sourceDescriptor : options . sourceDescriptor ? options . sourceDescriptor : null , __dataRequests : _ . get ( options , '__dataRequests' , [ ] ) , id : _ . get ( options , 'id' , uuid ( ) ) , label : options . label && options . label . length > 0 ? options . label : null , minZoom : _ . get ( options , 'minZoom' , MIN_ZOOM ) , maxZoom : _ . get ( options , 'maxZoom' , MAX_ZOOM ) , alpha : _ . get ( options , 'alpha' , 0.75 ) , visible : _ . get ( options , 'visible' , true ) , style : _ . get ( options , 'style' , null ) , includeInFitToBounds : typeof options . includeInFitToBounds === 'boolean' ? options . includeInFitToBounds : true , } ; } destroy ( ) { if ( this . _source ) { this . _source . destroy ( ) ; } } constructor ( { layerDescriptor , source } ) { this . _descriptor = AbstractLayer . createDescriptor ( layerDescriptor ) ; this . _source = source ; if ( this . _descriptor . __dataRequests ) { this . _dataRequests = this . _descriptor . __dataRequests . map ( ( dataRequest ) => new DataRequest ( dataRequest ) ) ; } else { this . _dataRequests = [ ] ; } } static getBoundDataForSource ( mbMap , sourceId ) { const mbStyle = mbMap . getStyle ( ) ; return mbStyle . sources [ sourceId ] . data ; } getDescriptor ( ) { return this . _descriptor ; } async cloneDescriptor ( ) { const clonedDescriptor = copyPersistentState ( this . _descriptor ) ; clonedDescriptor . id = uuid ( ) ; const displayName = await this . getDisplayName ( ) ; clonedDescriptor . label = `Clone of ${ displayName }` ; clonedDescriptor . sourceDescriptor = this . getSource ( ) . cloneDescriptor ( ) ; return clonedDescriptor ; } makeMbLayerId ( layerNameSuffix ) { return `${ this . getId ( ) }${ MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER }${ layerNameSuffix }` ; } isPreviewLayer ( ) { return ! ! this . _descriptor . __isPreviewLayer ; } supportsElasticsearchFilters ( ) { return this . getSource ( ) . isESSource ( ) ; } async supportsFitToBounds ( ) { return await this . getSource ( ) . supportsFitToBounds ( ) ; } async isFittable ( ) { return ( await this . supportsFitToBounds ( ) ) && this . isVisible ( ) && this . isIncludeInFitToBounds ( ) ; } isIncludeInFitToBounds ( ) { return ! ! this . _descriptor . includeInFitToBounds ; } async isFilteredByGlobalTime ( ) { return false ; } async getDisplayName ( source ? ) { if ( this . _descriptor . label ) { return this . _descriptor . label ; } const sourceDisplayName = source ? await source . getDisplayName ( ) : await this . getSource ( ) . getDisplayName ( ) ; return sourceDisplayName || `Layer ${ this . _descriptor . id }` ; } async getAttributions ( ) { if ( this . hasErrors ( ) || ! this . isVisible ( ) ) { return [ ] ; } const attributionProvider = this . getSource ( ) . getAttributionProvider ( ) ; if ( attributionProvider ) { return attributionProvider ( ) ; } return this . _descriptor . attribution !== undefined ? [ this . _descriptor . attribution ] : [ ] ; } getStyleForEditing ( ) { throw new Error ( 'Should implement AbstractLayer#getStyleForEditing' ) ; } getStyle ( ) { throw new Error ( 'Should implement AbstractLayer#getStyle' ) ; } getCurrentStyle ( ) { throw new Error ( 'Should implement AbstractLayer#getCurrentStyle' ) ; } getLabel ( ) { return this . _descriptor . label ? this . _descriptor . label : '' ; } getLayerIcon ( isTocIcon ) { return { icon : < EuiIcon size = "m" type = { this . getLayerTypeIconName ( ) } / > , } ; } async hasLegendDetails ( ) { return false ; } renderLegendDetails ( ) { return null ; } getId ( ) { return this . _descriptor . id ; } getSource ( ) { return this . _source ; } getSourceForEditing ( ) { return this . _source ; } isVisible ( ) { return ! ! this . _descriptor . visible ; } showAtZoomLevel ( zoom ) { return zoom >= this . getMinZoom ( ) && zoom <= this . getMaxZoom ( ) ; } getMinZoom ( ) { return typeof this . _descriptor . minZoom === 'number' ? this . _descriptor . minZoom : MIN_ZOOM ; } getMaxZoom ( ) { return typeof this . _descriptor . maxZoom === 'number' ? this . _descriptor . maxZoom : MAX_ZOOM ; } getMinSourceZoom ( ) { return this . _source . getMinZoom ( ) ; } getMbSourceId ( ) { return this . getId ( ) ; } _requiresPrevSourceCleanup ( mbMap ) { return false ; } _removeStaleMbSourcesAndLayers ( mbMap ) { if ( this . _requiresPrevSourceCleanup ( mbMap ) ) { const mbStyle = mbMap . getStyle ( ) ; mbStyle . layers . forEach ( ( mbLayer ) => { if ( this . ownsMbLayerId ( mbLayer . id ) ) { mbMap . removeLayer ( mbLayer . id ) ; } } ) ; Object . keys ( mbStyle . sources ) . some ( ( mbSourceId ) => { if ( this . ownsMbSourceId ( mbSourceId ) ) { mbMap . removeSource ( mbSourceId ) ; } } ) ; } } getAlpha ( ) { return typeof this . _descriptor . alpha === 'number' ? this . _descriptor . alpha : 1 ; } getQuery ( ) { return this . _descriptor . query ? this . _descriptor . query : null ; } async getImmutableSourceProperties ( ) { const source = this . getSource ( ) ; return await source . getImmutableProperties ( ) ; } renderSourceSettingsEditor ( sourceEditorArgs ) { return this . getSourceForEditing ( ) . renderSourceSettingsEditor ( sourceEditorArgs ) ; } getPrevRequestToken ( dataId ) { const prevDataRequest = this . getDataRequest ( dataId ) ; if ( ! prevDataRequest ) { return ; } return prevDataRequest . getRequestToken ( ) ; } getInFlightRequestTokens ( ) { if ( ! this . _dataRequests ) { return [ ] ; } const requestTokens = this . _dataRequests . map ( ( dataRequest ) => dataRequest . getRequestToken ( ) ) ; return _ . compact ( requestTokens ) ; } getSourceDataRequest ( ) { return this . getDataRequest ( SOURCE_DATA_REQUEST_ID ) ; } getDataRequest ( id ) { return this . _dataRequests . find ( ( dataRequest ) => dataRequest . getDataId ( ) === id ) ; } isLayerLoading ( ) { const areTilesLoading = typeof this . _descriptor . __areTilesLoaded !== 'undefined' ? ! this . _descriptor . __areTilesLoaded : false ; return areTilesLoading || this . _dataRequests . some ( ( dataRequest ) => dataRequest . isLoading ( ) ) ; } isLoadingBounds ( ) { const boundsDataRequest = this . getDataRequest ( SOURCE_BOUNDS_DATA_REQUEST_ID ) ; return ! ! boundsDataRequest && boundsDataRequest . isLoading ( ) ; } hasErrors ( ) { return _ . get ( this . _descriptor , '__isInErrorState' , false ) ; } getErrors ( ) { return this . hasErrors ( ) && this . _descriptor . __errorMessage ? this . _descriptor . __errorMessage : '' ; } async syncData ( syncContext ) { } getMbLayerIds ( ) { throw new Error ( 'Should implement AbstractLayer#getMbLayerIds' ) ; } ownsMbLayerId ( layerId ) { throw new Error ( 'Should implement AbstractLayer#ownsMbLayerId' ) ; } ownsMbSourceId ( mbSourceId ) { throw new Error ( 'Should implement AbstractLayer#ownsMbSourceId' ) ; } syncLayerWithMB ( mbMap ) { throw new Error ( 'Should implement AbstractLayer#syncLayerWithMB' ) ; } getLayerTypeIconName ( ) { throw new Error ( 'should implement Layer#getLayerTypeIconName' ) ; } isInitialDataLoadComplete ( ) { const sourceDataRequest = this . getSourceDataRequest ( ) ; return sourceDataRequest ? sourceDataRequest . hasData ( ) : false ; } async getBounds ( dataRequestContext ) { return null ; } renderStyleEditor ( onStyleDescriptorChange ) { const style = this . getStyleForEditing ( ) ; if ( ! style ) { return null ; } return style . renderEditor ( onStyleDescriptorChange ) ; } getIndexPatternIds ( ) { return [ ] ; } getQueryableIndexPatternIds ( ) { return [ ] ; } syncVisibilityWithMb ( mbMap , mbLayerId ) { mbMap . setLayoutProperty ( mbLayerId , 'visibility' , this . isVisible ( ) ? 'visible' : 'none' ) ; } getType ( ) { return this . _descriptor . type as LAYER_TYPE ; } areLabelsOnTop ( ) { return false ; } supportsLabelsOnTop ( ) { return false ; } async getLicensedFeatures ( ) { return [ ] ; } getGeoFieldNames ( ) { const source = this . getSource ( ) ; return source . isESSource ( ) ? [ ( source as IESSource ) . getGeoFieldName ( ) ] : [ ] ; } async getStyleMetaDescriptorFromLocalFeatures ( ) { return null ; } isBasemap ( order ) { return false ; } _getMetaFromTiles ( ) { return this . _descriptor . __metaFromTiles || [ ] ; } }