[Maps] Add 3rd party vector tile support (#62084)

Adds support for adding an external vector tile service to Maps. This is experimental functionality. To enable, add `xpack.maps.enableVectorTiles: true` to the `kibana.yml`configuration file.
This commit is contained in:
Thomas Neirynck 2020-04-16 16:26:13 -04:00 committed by GitHub
parent 0a9e17b57f
commit ad41eea211
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 816 additions and 118 deletions

View file

@ -25,7 +25,7 @@ export * from './ui_settings';
export * from './field_icon';
export * from './table_list_view';
export * from './split_panel';
export { ValidatedDualRange } from './validated_range';
export { ValidatedDualRange, Value } from './validated_range';
export * from './notifications';
export { Markdown, MarkdownSimple } from './markdown';
export { reactToUiComponent, uiToReactComponent } from './adapters';

View file

@ -17,4 +17,4 @@
* under the License.
*/
export { ValidatedDualRange } from './validated_dual_range';
export { ValidatedDualRange, Value } from './validated_dual_range';

View file

@ -38,6 +38,7 @@ export function maps(kibana) {
return {
showMapVisualizationTypes: serverConfig.get('xpack.maps.showMapVisualizationTypes'),
showMapsInspectorAdapter: serverConfig.get('xpack.maps.showMapsInspectorAdapter'),
enableVectorTiles: serverConfig.get('xpack.maps.enableVectorTiles'),
preserveDrawingBuffer: serverConfig.get('xpack.maps.preserveDrawingBuffer'),
isEmsEnabled: mapConfig.includeElasticMapsService,
emsFontLibraryUrl: mapConfig.emsFontLibraryUrl,
@ -85,6 +86,7 @@ export function maps(kibana) {
showMapVisualizationTypes: Joi.boolean().default(false),
showMapsInspectorAdapter: Joi.boolean().default(false), // flag used in functional testing
preserveDrawingBuffer: Joi.boolean().default(false), // flag used in functional testing
enableVectorTiles: Joi.boolean().default(false), // flag used to enable/disable vector-tiles
}).default();
},

View file

@ -63,13 +63,13 @@ export class AddLayerPanel extends Component {
return;
}
const style =
const styleDescriptor =
this.state.layer && this.state.layer.getCurrentStyle()
? this.state.layer.getCurrentStyle().getDescriptor()
: null;
const layerInitProps = {
...options,
style: style,
style: styleDescriptor,
};
const newLayer = source.createDefaultLayer(layerInitProps, this.props.mapColors);
if (!this._isMounted) {

View file

@ -13,10 +13,13 @@ import {
updateLayerMinZoom,
updateLayerAlpha,
} from '../../../actions/map_actions';
import { MAX_ZOOM } from '../../../../../../../plugins/maps/common/constants';
function mapStateToProps(state = {}) {
const selectedLayer = getSelectedLayer(state);
return {
minVisibilityZoom: selectedLayer.getMinSourceZoom(),
maxVisibilityZoom: MAX_ZOOM,
alpha: selectedLayer.getAlpha(),
label: selectedLayer.getLabel(),
layerId: selectedLayer.getId(),

View file

@ -13,8 +13,6 @@ import { ValidatedRange } from '../../../../../../../plugins/maps/public/compone
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { ValidatedDualRange } from '../../../../../../../../src/plugins/kibana_react/public';
import { MAX_ZOOM, MIN_ZOOM } from '../../../../common/constants';
export function LayerSettings(props) {
const onLabelChange = event => {
const label = event.target.value;
@ -22,8 +20,8 @@ export function LayerSettings(props) {
};
const onZoomChange = ([min, max]) => {
props.updateMinZoom(props.layerId, Math.max(MIN_ZOOM, parseInt(min, 10)));
props.updateMaxZoom(props.layerId, Math.min(MAX_ZOOM, parseInt(max, 10)));
props.updateMinZoom(props.layerId, Math.max(props.minVisibilityZoom, parseInt(min, 10)));
props.updateMaxZoom(props.layerId, Math.min(props.maxVisibilityZoom, parseInt(max, 10)));
};
const onAlphaChange = alpha => {
@ -38,8 +36,8 @@ export function LayerSettings(props) {
defaultMessage: 'Visibility',
})}
formRowDisplay="columnCompressed"
min={MIN_ZOOM}
max={MAX_ZOOM}
min={props.minVisibilityZoom}
max={props.maxVisibilityZoom}
value={[props.minZoom, props.maxZoom]}
showInput="inputWithPopover"
showRange

View file

@ -14,7 +14,12 @@ import {
} from './utils';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { getGlyphUrl, isRetina } from '../../../../../../../plugins/maps/public/meta';
import { DECIMAL_DEGREES_PRECISION, ZOOM_PRECISION } from '../../../../common/constants';
import {
DECIMAL_DEGREES_PRECISION,
MAX_ZOOM,
MIN_ZOOM,
ZOOM_PRECISION,
} from '../../../../common/constants';
import mapboxgl from 'mapbox-gl/dist/mapbox-gl-csp';
import mbWorkerUrl from '!!file-loader!mapbox-gl/dist/mapbox-gl-csp-worker';
import mbRtlPlugin from '!!file-loader!@mapbox/mapbox-gl-rtl-text/mapbox-gl-rtl-text.min.js';
@ -132,6 +137,8 @@ export class MBMapContainer extends React.Component {
scrollZoom: this.props.scrollZoom,
preserveDrawingBuffer: getInjectedVarFunc()('preserveDrawingBuffer', false),
interactive: !this.props.disableInteractive,
minZoom: MIN_ZOOM,
maxZoom: MAX_ZOOM,
};
const initialView = _.get(this.props.goto, 'center');
if (initialView) {

View file

@ -4,13 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import '../../../../plugins/maps/public/layers/layer_wizard_registry';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import '../../../../plugins/maps/public/layers/sources/source_registry';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import '../../../../plugins/maps/public/layers/load_layer_wizards';
import { Plugin, CoreStart, CoreSetup } from 'src/core/public';
// @ts-ignore
import { Start as InspectorStartContract } from 'src/plugins/inspector/public';

View file

@ -19,6 +19,8 @@ import { BlendedVectorLayer } from '../../../../../plugins/maps/public/layers/bl
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { getTimeFilter } from '../../../../../plugins/maps/public/kibana_services';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { TiledVectorLayer } from '../../../../../plugins/maps/public/layers/tiled_vector_layer';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { getInspectorAdapters } from '../../../../../plugins/maps/public/reducers/non_serializable_instances';
import {
copyPersistentState,
@ -51,6 +53,8 @@ function createLayerInstance(layerDescriptor, inspectorAdapters) {
return new HeatmapLayer({ layerDescriptor, source });
case BlendedVectorLayer.type:
return new BlendedVectorLayer({ layerDescriptor, source });
case TiledVectorLayer.type:
return new TiledVectorLayer({ layerDescriptor, source });
default:
throw new Error(`Unrecognized layerType ${layerDescriptor.type}`);
}

View file

@ -5,6 +5,7 @@
*/
jest.mock('../../../../../plugins/maps/public/layers/vector_layer', () => {});
jest.mock('../../../../../plugins/maps/public/layers/tiled_vector_layer', () => {});
jest.mock('../../../../../plugins/maps/public/layers/blended_vector_layer', () => {});
jest.mock('../../../../../plugins/maps/public/layers/heatmap_layer', () => {});
jest.mock('../../../../../plugins/maps/public/layers/vector_tile_layer', () => {});

View file

@ -46,9 +46,10 @@ export function createMapPath(id: string) {
export enum LAYER_TYPE {
TILE = 'TILE',
VECTOR = 'VECTOR',
VECTOR_TILE = 'VECTOR_TILE',
VECTOR_TILE = 'VECTOR_TILE', // for static display of mvt vector tiles with a mapbox stylesheet. Does not support any ad-hoc configurations. Used for consuming EMS vector tiles.
HEATMAP = 'HEATMAP',
BLENDED_VECTOR = 'BLENDED_VECTOR',
TILED_VECTOR = 'TILED_VECTOR', // similar to a regular vector-layer, but it consumes the data as .mvt tilea iso GeoJson. It supports similar ad-hoc configurations like a regular vector layer (E.g. using IVectorStyle), although there is some loss of functionality e.g. does not support term joining
}
export enum SORT_ORDER {
@ -67,6 +68,7 @@ export enum SOURCE_TYPES {
KIBANA_TILEMAP = 'KIBANA_TILEMAP',
REGIONMAP_FILE = 'REGIONMAP_FILE',
GEOJSON_FILE = 'GEOJSON_FILE',
MVT_SINGLE_LAYER = 'MVT_SINGLE_LAYER',
}
export enum FIELD_ORIGIN {

View file

@ -31,12 +31,12 @@ type ESGeoGridSourceSyncMeta = {
requestType: RENDER_AS;
};
export type VectorSourceSyncMeta = ESSearchSourceSyncMeta | ESGeoGridSourceSyncMeta;
export type VectorSourceSyncMeta = ESSearchSourceSyncMeta | ESGeoGridSourceSyncMeta | null;
export type VectorSourceRequestMeta = MapFilters & {
applyGlobalQuery: boolean;
fieldNames: string[];
geogridPrecision: number;
geogridPrecision?: number;
sourceQuery: MapQuery;
sourceMeta: VectorSourceSyncMeta;
};

View file

@ -94,6 +94,18 @@ export type XYZTMSSourceDescriptor = AbstractSourceDescriptor &
urlTemplate: string;
};
export type TiledSingleLayerVectorSourceDescriptor = AbstractSourceDescriptor & {
urlTemplate: string;
layerName: string;
// These are the min/max zoom levels of the availability of the a particular layerName in the tileset at urlTemplate.
// These are _not_ the visible zoom-range of the data on a map.
// Tiled data can be displayed at higher levels of zoom than that they are stored in the tileset.
// e.g. EMS basemap data from level 14 is at most detailed resolution and can be displayed at higher levels
minSourceZoom: number;
maxSourceZoom: number;
};
export type JoinDescriptor = {
leftField: string;
right: ESTermSourceDescriptor;
@ -107,7 +119,9 @@ export type SourceDescriptor =
| ESTermSourceDescriptor
| ESSearchSourceDescriptor
| ESGeoGridSourceDescriptor
| EMSFileSourceDescriptor;
| EMSFileSourceDescriptor
| ESPewPewSourceDescriptor
| TiledSingleLayerVectorSourceDescriptor;
export type LayerDescriptor = {
__dataRequests?: DataRequestDescriptor[];

View file

@ -38,7 +38,9 @@ export const getFileUploadComponent = () => {
};
let getInjectedVar;
export const setInjectedVarFunc = getInjectedVarFunc => (getInjectedVar = getInjectedVarFunc);
export const setInjectedVarFunc = getInjectedVarFunc => {
getInjectedVar = getInjectedVarFunc;
};
export const getInjectedVarFunc = () => getInjectedVar;
let uiSettings;

View file

@ -3,7 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { LayerDescriptor, MapExtent, MapFilters } from '../../common/descriptor_types';
import { LayerDescriptor, MapExtent, MapFilters, MapQuery } from '../../common/descriptor_types';
import { ISource } from './sources/source';
import { DataRequest } from './util/data_request';
import { SyncContext } from '../actions/map_actions';
@ -17,6 +17,11 @@ export interface ILayer {
getSource(): ISource;
getSourceForEditing(): ISource;
syncData(syncContext: SyncContext): Promise<void>;
isVisible(): boolean;
showAtZoomLevel(zoomLevel: number): boolean;
getMinZoom(): number;
getMaxZoom(): number;
getMinSourceZoom(): number;
}
export interface ILayerArguments {
@ -35,4 +40,12 @@ export class AbstractLayer implements ILayer {
getSource(): ISource;
getSourceForEditing(): ISource;
syncData(syncContext: SyncContext): Promise<void>;
isVisible(): boolean;
showAtZoomLevel(zoomLevel: number): boolean;
getMinZoom(): number;
getMaxZoom(): number;
getMinSourceZoom(): number;
getQuery(): MapQuery;
_removeStaleMbSourcesAndLayers(mbMap: unknown): void;
_requiresPrevSourceCleanup(mbMap: unknown): boolean;
}

View file

@ -141,7 +141,8 @@ export class AbstractLayer {
defaultMessage: `Layer is hidden.`,
});
} else if (!this.showAtZoomLevel(zoomLevel)) {
const { minZoom, maxZoom } = this.getZoomConfig();
const minZoom = this.getMinZoom();
const maxZoom = this.getMaxZoom();
icon = <EuiIcon size="m" type="expand" />;
tooltipContent = i18n.translate('xpack.maps.layer.zoomFeedbackTooltip', {
defaultMessage: `Layer is visible between zoom levels {minZoom} and {maxZoom}.`,
@ -203,7 +204,7 @@ export class AbstractLayer {
}
showAtZoomLevel(zoom) {
return zoom >= this._descriptor.minZoom && zoom <= this._descriptor.maxZoom;
return zoom >= this.getMinZoom() && zoom <= this.getMaxZoom();
}
getMinZoom() {
@ -214,6 +215,30 @@ export class AbstractLayer {
return this._descriptor.maxZoom;
}
getMinSourceZoom() {
return this._source.getMinZoom();
}
_requiresPrevSourceCleanup() {
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 this._descriptor.alpha;
}
@ -222,13 +247,6 @@ export class AbstractLayer {
return this._descriptor.query;
}
getZoomConfig() {
return {
minZoom: this._descriptor.minZoom,
maxZoom: this._descriptor.maxZoom,
};
}
getCurrentStyle() {
return this._style;
}

View file

@ -1,30 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { registerLayerWizard } from './layer_wizard_registry';
import { uploadLayerWizardConfig } from './sources/client_file_source';
import { esDocumentsLayerWizardConfig } from './sources/es_search_source';
import { clustersLayerWizardConfig, heatmapLayerWizardConfig } from './sources/es_geo_grid_source';
import { point2PointLayerWizardConfig } from './sources/es_pew_pew_source/es_pew_pew_source';
import { emsBoundariesLayerWizardConfig } from './sources/ems_file_source';
import { emsBaseMapLayerWizardConfig } from './sources/ems_tms_source';
import { kibanaRegionMapLayerWizardConfig } from './sources/kibana_regionmap_source';
import { kibanaBasemapLayerWizardConfig } from './sources/kibana_tilemap_source';
import { tmsLayerWizardConfig } from './sources/xyz_tms_source';
import { wmsLayerWizardConfig } from './sources/wms_source';
// Registration order determines display order
registerLayerWizard(uploadLayerWizardConfig);
registerLayerWizard(esDocumentsLayerWizardConfig);
registerLayerWizard(clustersLayerWizardConfig);
registerLayerWizard(heatmapLayerWizardConfig);
registerLayerWizard(point2PointLayerWizardConfig);
registerLayerWizard(emsBoundariesLayerWizardConfig);
registerLayerWizard(emsBaseMapLayerWizardConfig);
registerLayerWizard(kibanaRegionMapLayerWizardConfig);
registerLayerWizard(kibanaBasemapLayerWizardConfig);
registerLayerWizard(tmsLayerWizardConfig);
registerLayerWizard(wmsLayerWizardConfig);

View file

@ -0,0 +1,66 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { registerLayerWizard } from './layer_wizard_registry';
// @ts-ignore
import { uploadLayerWizardConfig } from './sources/client_file_source';
// @ts-ignore
import { esDocumentsLayerWizardConfig } from './sources/es_search_source';
// @ts-ignore
import { clustersLayerWizardConfig, heatmapLayerWizardConfig } from './sources/es_geo_grid_source';
// @ts-ignore
import { point2PointLayerWizardConfig } from './sources/es_pew_pew_source/es_pew_pew_source';
// @ts-ignore
import { emsBoundariesLayerWizardConfig } from './sources/ems_file_source';
// @ts-ignore
import { emsBaseMapLayerWizardConfig } from './sources/ems_tms_source';
// @ts-ignore
import { kibanaRegionMapLayerWizardConfig } from './sources/kibana_regionmap_source';
// @ts-ignore
import { kibanaBasemapLayerWizardConfig } from './sources/kibana_tilemap_source';
import { tmsLayerWizardConfig } from './sources/xyz_tms_source';
// @ts-ignore
import { wmsLayerWizardConfig } from './sources/wms_source';
import { mvtVectorSourceWizardConfig } from './sources/mvt_single_layer_vector_source';
// @ts-ignore
import { getInjectedVarFunc } from '../kibana_services';
// Registration order determines display order
let registered = false;
export function registerLayerWizards() {
if (registered) {
return;
}
// @ts-ignore
registerLayerWizard(uploadLayerWizardConfig);
// @ts-ignore
registerLayerWizard(esDocumentsLayerWizardConfig);
// @ts-ignore
registerLayerWizard(clustersLayerWizardConfig);
// @ts-ignore
registerLayerWizard(heatmapLayerWizardConfig);
// @ts-ignore
registerLayerWizard(point2PointLayerWizardConfig);
// @ts-ignore
registerLayerWizard(emsBoundariesLayerWizardConfig);
// @ts-ignore
registerLayerWizard(emsBaseMapLayerWizardConfig);
// @ts-ignore
registerLayerWizard(kibanaRegionMapLayerWizardConfig);
// @ts-ignore
registerLayerWizard(kibanaBasemapLayerWizardConfig);
registerLayerWizard(tmsLayerWizardConfig);
// @ts-ignore
registerLayerWizard(wmsLayerWizardConfig);
const getInjectedVar = getInjectedVarFunc();
if (getInjectedVar && getInjectedVar('enableVectorTiles', false)) {
// eslint-disable-next-line no-console
console.warn('Vector tiles are an experimental feature and should not be used in production.');
registerLayerWizard(mvtVectorSourceWizardConfig);
}
registered = true;
}

View file

@ -23,6 +23,7 @@ export class ESGeoGridSource extends AbstractESAggSource {
constructor(sourceDescriptor: ESGeoGridSourceDescriptor, inspectorAdapters: unknown);
getFieldNames(): string[];
getGridResolution(): GRID_RESOLUTION;
getGeoGridPrecision(zoom: number): number;
}

View file

@ -9,4 +9,5 @@ import { ESSearchSourceDescriptor } from '../../../../common/descriptor_types';
export class ESSearchSource extends AbstractESSource {
constructor(sourceDescriptor: Partial<ESSearchSourceDescriptor>, inspectorAdapters: unknown);
getFieldNames(): string[];
}

View file

@ -4,8 +4,5 @@
* you may not use this file except in compliance with the Elastic License.
*/
export const VECTOR_SHAPE_TYPES = {
POINT: 'POINT',
LINE: 'LINE',
POLYGON: 'POLYGON',
};
export * from './mvt_single_layer_vector_source';
export * from './layer_wizard';

View file

@ -0,0 +1,42 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
import React from 'react';
import {
MVTSingleLayerVectorSourceEditor,
MVTSingleLayerVectorSourceConfig,
} from './mvt_single_layer_vector_source_editor';
import { MVTSingleLayerVectorSource, sourceTitle } from './mvt_single_layer_vector_source';
import { LayerWizard, RenderWizardArguments } from '../../layer_wizard_registry';
import { SOURCE_TYPES } from '../../../../common/constants';
export const mvtVectorSourceWizardConfig: LayerWizard = {
description: i18n.translate('xpack.maps.source.mvtVectorSourceWizard', {
defaultMessage: 'Vector source wizard',
}),
icon: 'grid',
renderWizard: ({ onPreviewSource, inspectorAdapters }: RenderWizardArguments) => {
const onSourceConfigChange = ({
urlTemplate,
layerName,
minSourceZoom,
maxSourceZoom,
}: MVTSingleLayerVectorSourceConfig) => {
const sourceDescriptor = MVTSingleLayerVectorSource.createDescriptor({
urlTemplate,
layerName,
minSourceZoom,
maxSourceZoom,
type: SOURCE_TYPES.MVT_SINGLE_LAYER,
});
const source = new MVTSingleLayerVectorSource(sourceDescriptor, inspectorAdapters);
onPreviewSource(source);
};
return <MVTSingleLayerVectorSourceEditor onSourceConfigChange={onSourceConfigChange} />;
},
title: sourceTitle,
};

View file

@ -0,0 +1,175 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
import uuid from 'uuid/v4';
import { AbstractSource, ImmutableSourceProperty } from '../source';
import { TiledVectorLayer } from '../../tiled_vector_layer';
import { GeoJsonWithMeta, ITiledSingleLayerVectorSource } from '../vector_source';
import { MAX_ZOOM, MIN_ZOOM, SOURCE_TYPES } from '../../../../common/constants';
import { VECTOR_SHAPE_TYPES } from '../vector_feature_types';
import { IField } from '../../fields/field';
import { registerSource } from '../source_registry';
import { getDataSourceLabel, getUrlLabel } from '../../../../common/i18n_getters';
import {
LayerDescriptor,
MapExtent,
TiledSingleLayerVectorSourceDescriptor,
VectorSourceRequestMeta,
VectorSourceSyncMeta,
} from '../../../../common/descriptor_types';
import { VectorLayerArguments } from '../../vector_layer';
export const sourceTitle = i18n.translate(
'xpack.maps.source.MVTSingleLayerVectorSource.sourceTitle',
{
defaultMessage: 'Vector Tile Layer',
}
);
export class MVTSingleLayerVectorSource extends AbstractSource
implements ITiledSingleLayerVectorSource {
static createDescriptor({
urlTemplate,
layerName,
minSourceZoom,
maxSourceZoom,
}: TiledSingleLayerVectorSourceDescriptor) {
return {
type: SOURCE_TYPES.MVT_SINGLE_LAYER,
id: uuid(),
urlTemplate,
layerName,
minSourceZoom: Math.max(MIN_ZOOM, minSourceZoom),
maxSourceZoom: Math.min(MAX_ZOOM, maxSourceZoom),
};
}
readonly _descriptor: TiledSingleLayerVectorSourceDescriptor;
constructor(
sourceDescriptor: TiledSingleLayerVectorSourceDescriptor,
inspectorAdapters?: object
) {
super(sourceDescriptor, inspectorAdapters);
this._descriptor = sourceDescriptor;
}
renderSourceSettingsEditor() {
return null;
}
getFieldNames(): string[] {
return [];
}
createDefaultLayer(options: LayerDescriptor): TiledVectorLayer {
const layerDescriptor = {
sourceDescriptor: this._descriptor,
...options,
};
const normalizedLayerDescriptor = TiledVectorLayer.createDescriptor(layerDescriptor, []);
const vectorLayerArguments: VectorLayerArguments = {
layerDescriptor: normalizedLayerDescriptor,
source: this,
};
return new TiledVectorLayer(vectorLayerArguments);
}
getGeoJsonWithMeta(
layerName: 'string',
searchFilters: unknown[],
registerCancelCallback: (callback: () => void) => void
): Promise<GeoJsonWithMeta> {
// todo: remove this method
// This is a consequence of ITiledSingleLayerVectorSource extending IVectorSource.
throw new Error('Does not implement getGeoJsonWithMeta');
}
async getFields(): Promise<IField[]> {
return [];
}
async getImmutableProperties(): Promise<ImmutableSourceProperty[]> {
return [
{ label: getDataSourceLabel(), value: sourceTitle },
{ label: getUrlLabel(), value: this._descriptor.urlTemplate },
{
label: i18n.translate('xpack.maps.source.MVTSingleLayerVectorSource.layerNameMessage', {
defaultMessage: 'Layer name',
}),
value: this._descriptor.layerName,
},
{
label: i18n.translate('xpack.maps.source.MVTSingleLayerVectorSource.minZoomMessage', {
defaultMessage: 'Min zoom',
}),
value: this._descriptor.minSourceZoom.toString(),
},
{
label: i18n.translate('xpack.maps.source.MVTSingleLayerVectorSource.maxZoomMessage', {
defaultMessage: 'Max zoom',
}),
value: this._descriptor.maxSourceZoom.toString(),
},
];
}
async getDisplayName(): Promise<string> {
return this._descriptor.layerName;
}
async getUrlTemplateWithMeta() {
return {
urlTemplate: this._descriptor.urlTemplate,
layerName: this._descriptor.layerName,
minSourceZoom: this._descriptor.minSourceZoom,
maxSourceZoom: this._descriptor.maxSourceZoom,
};
}
async getSupportedShapeTypes(): Promise<VECTOR_SHAPE_TYPES[]> {
return [VECTOR_SHAPE_TYPES.POINT, VECTOR_SHAPE_TYPES.LINE, VECTOR_SHAPE_TYPES.POLYGON];
}
canFormatFeatureProperties() {
return false;
}
getMinZoom() {
return this._descriptor.minSourceZoom;
}
getMaxZoom() {
return this._descriptor.maxSourceZoom;
}
getBoundsForFilters(searchFilters: VectorSourceRequestMeta): MapExtent {
return {
maxLat: 90,
maxLon: 180,
minLat: -90,
minLon: -180,
};
}
getFieldByName(fieldName: string): IField | null {
return null;
}
getSyncMeta(): VectorSourceSyncMeta {
return null;
}
getApplyGlobalQuery(): boolean {
return false;
}
}
registerSource({
ConstructorFunction: MVTSingleLayerVectorSource,
type: SOURCE_TYPES.MVT_SINGLE_LAYER,
});

View file

@ -0,0 +1,128 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
/* eslint-disable @typescript-eslint/consistent-type-definitions */
import React, { Fragment, Component, ChangeEvent } from 'react';
import _ from 'lodash';
import { EuiFieldText, EuiFormRow } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { MAX_ZOOM, MIN_ZOOM } from '../../../../common/constants';
import { ValidatedDualRange, Value } from '../../../../../../../src/plugins/kibana_react/public';
export type MVTSingleLayerVectorSourceConfig = {
urlTemplate: string;
layerName: string;
minSourceZoom: number;
maxSourceZoom: number;
};
export interface Props {
onSourceConfigChange: (sourceConfig: MVTSingleLayerVectorSourceConfig) => void;
}
interface State {
urlTemplate: string;
layerName: string;
minSourceZoom: number;
maxSourceZoom: number;
}
export class MVTSingleLayerVectorSourceEditor extends Component<Props, State> {
state = {
urlTemplate: '',
layerName: '',
minSourceZoom: MIN_ZOOM,
maxSourceZoom: MAX_ZOOM,
};
_sourceConfigChange = _.debounce(() => {
const canPreview =
this.state.urlTemplate.indexOf('{x}') >= 0 &&
this.state.urlTemplate.indexOf('{y}') >= 0 &&
this.state.urlTemplate.indexOf('{z}') >= 0;
if (canPreview && this.state.layerName) {
this.props.onSourceConfigChange({
urlTemplate: this.state.urlTemplate,
layerName: this.state.layerName,
minSourceZoom: this.state.minSourceZoom,
maxSourceZoom: this.state.maxSourceZoom,
});
}
}, 200);
_handleUrlTemplateChange = (e: ChangeEvent<HTMLInputElement>) => {
const url = e.target.value;
this.setState(
{
urlTemplate: url,
},
() => this._sourceConfigChange()
);
};
_handleLayerNameInputChange = (e: ChangeEvent<HTMLInputElement>) => {
const layerName = e.target.value;
this.setState(
{
layerName,
},
() => this._sourceConfigChange()
);
};
_handleZoomRangeChange = (e: Value) => {
const minSourceZoom = parseInt(e[0] as string, 10);
const maxSourceZoom = parseInt(e[1] as string, 10);
if (this.state.minSourceZoom !== minSourceZoom || this.state.maxSourceZoom !== maxSourceZoom) {
this.setState({ minSourceZoom, maxSourceZoom }, () => this._sourceConfigChange());
}
};
render() {
return (
<Fragment>
<EuiFormRow
label={i18n.translate('xpack.maps.source.MVTSingleLayerVectorSourceEditor.urlMessage', {
defaultMessage: 'Url',
})}
>
<EuiFieldText value={this.state.urlTemplate} onChange={this._handleUrlTemplateChange} />
</EuiFormRow>
<EuiFormRow
label={i18n.translate(
'xpack.maps.source.MVTSingleLayerVectorSourceEditor.layerNameMessage',
{
defaultMessage: 'Layer name',
}
)}
>
<EuiFieldText value={this.state.layerName} onChange={this._handleLayerNameInputChange} />
</EuiFormRow>
<ValidatedDualRange
label=""
formRowDisplay="columnCompressed"
min={MIN_ZOOM}
max={MAX_ZOOM}
value={[this.state.minSourceZoom, this.state.maxSourceZoom]}
showInput="inputWithPopover"
showRange
showLabels
onChange={this._handleZoomRangeChange}
allowEmptyRange={false}
compressed
prepend={i18n.translate(
'xpack.maps.source.MVTSingleLayerVectorSourceEditor.dataZoomRangeMessage',
{
defaultMessage: 'Zoom levels',
}
)}
/>
</Fragment>
);
}
}

View file

@ -19,7 +19,7 @@ export type Attribution = {
};
export interface ISource {
createDefaultLayer(): ILayer;
createDefaultLayer(options?: LayerDescriptor): ILayer;
destroy(): void;
getDisplayName(): Promise<string>;
getInspectorAdapters(): object;
@ -31,6 +31,8 @@ export interface ISource {
isTimeAware(): Promise<boolean>;
getImmutableProperties(): Promise<ImmutableSourceProperty[]>;
getAttributions(): Promise<Attribution[]>;
getMinZoom(): number;
getMaxZoom(): number;
}
export class AbstractSource implements ISource {
@ -49,4 +51,6 @@ export class AbstractSource implements ISource {
isTimeAware(): Promise<boolean>;
getImmutableProperties(): Promise<ImmutableSourceProperty[]>;
getAttributions(): Promise<Attribution[]>;
getMinZoom(): number;
getMaxZoom(): number;
}

View file

@ -6,6 +6,7 @@
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { copyPersistentState } from '../../reducers/util';
import { MIN_ZOOM, MAX_ZOOM } from '../../../common/constants';
export class AbstractSource {
static isIndexingSource = false;
@ -79,7 +80,7 @@ export class AbstractSource {
return false;
}
isQueryAware() {
async isTimeAware() {
return false;
}
@ -107,6 +108,14 @@ export class AbstractSource {
return [];
}
isFilterByMapBounds() {
return false;
}
isQueryAware() {
return false;
}
getGeoGridPrecision() {
return 0;
}
@ -140,4 +149,12 @@ export class AbstractSource {
async getValueSuggestions(/* field, query */) {
return [];
}
getMinZoom() {
return MIN_ZOOM;
}
getMaxZoom() {
return MAX_ZOOM;
}
}

View file

@ -10,7 +10,7 @@ import { ISource } from './source';
type SourceRegistryEntry = {
ConstructorFunction: new (
sourceDescriptor: any, // this is the source-descriptor that corresponds specifically to the particular ISource instance
inspectorAdapters: unknown
inspectorAdapters?: object
) => ISource;
type: string;
};

View file

@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export enum VECTOR_SHAPE_TYPES {
POINT = 'POINT',
LINE = 'LINE',
POLYGON = 'POLYGON',
}

View file

@ -14,6 +14,7 @@ import {
VectorSourceRequestMeta,
VectorSourceSyncMeta,
} from '../../../../common/descriptor_types';
import { VECTOR_SHAPE_TYPES } from '../vector_feature_types';
export type GeoJsonFetchMeta = ESSearchSourceResponseMeta;
@ -31,8 +32,10 @@ export interface IVectorSource extends ISource {
): Promise<GeoJsonWithMeta>;
getFields(): Promise<IField[]>;
getFieldByName(fieldName: string): IField;
getFieldByName(fieldName: string): IField | null;
getSyncMeta(): VectorSourceSyncMeta;
getFieldNames(): string[];
getApplyGlobalQuery(): boolean;
}
export class AbstractVectorSource extends AbstractSource implements IVectorSource {
@ -44,6 +47,21 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc
): Promise<GeoJsonWithMeta>;
getFields(): Promise<IField[]>;
getFieldByName(fieldName: string): IField;
getFieldByName(fieldName: string): IField | null;
getSyncMeta(): VectorSourceSyncMeta;
getSupportedShapeTypes(): Promise<VECTOR_SHAPE_TYPES[]>;
canFormatFeatureProperties(): boolean;
getApplyGlobalQuery(): boolean;
getFieldNames(): string[];
}
export interface ITiledSingleLayerVectorSource extends IVectorSource {
getUrlTemplateWithMeta(): Promise<{
layerName: string;
urlTemplate: string;
minSourceZoom: number;
maxSourceZoom: number;
}>;
getMinZoom(): number;
getMaxZoom(): number;
}

View file

@ -60,6 +60,10 @@ export class AbstractVectorSource extends AbstractSource {
throw new Error(`Should implemement ${this.constructor.type} ${this}`);
}
getFieldNames() {
return [];
}
/**
* Retrieves a field. This may be an existing instance.
* @param fieldName

View file

@ -37,7 +37,7 @@ export class VectorStyleEditor extends Component {
defaultDynamicProperties: getDefaultDynamicProperties(),
defaultStaticProperties: getDefaultStaticProperties(),
supportedFeatures: undefined,
selectedFeatureType: undefined,
selectedFeature: null,
};
componentWillUnmount() {
@ -91,18 +91,20 @@ export class VectorStyleEditor extends Component {
return;
}
let selectedFeature = VECTOR_SHAPE_TYPES.POLYGON;
if (this.props.isPointsOnly) {
selectedFeature = VECTOR_SHAPE_TYPES.POINT;
} else if (this.props.isLinesOnly) {
selectedFeature = VECTOR_SHAPE_TYPES.LINE;
if (!_.isEqual(supportedFeatures, this.state.supportedFeatures)) {
this.setState({ supportedFeatures });
}
if (
!_.isEqual(supportedFeatures, this.state.supportedFeatures) ||
selectedFeature !== this.state.selectedFeature
) {
this.setState({ supportedFeatures, selectedFeature });
if (this.state.selectedFeature === null) {
let selectedFeature = VECTOR_SHAPE_TYPES.POLYGON;
if (this.props.isPointsOnly) {
selectedFeature = VECTOR_SHAPE_TYPES.POINT;
} else if (this.props.isLinesOnly) {
selectedFeature = VECTOR_SHAPE_TYPES.LINE;
}
this.setState({
selectedFeature: selectedFeature,
});
}
}

View file

@ -7,17 +7,23 @@ import { IStyleProperty } from './properties/style_property';
import { IDynamicStyleProperty } from './properties/dynamic_style_property';
import { IVectorLayer } from '../../vector_layer';
import { IVectorSource } from '../../sources/vector_source';
import { VectorStyleDescriptor } from '../../../../common/descriptor_types';
import {
VectorStyleDescriptor,
VectorStylePropertiesDescriptor,
} from '../../../../common/descriptor_types';
export interface IVectorStyle {
getAllStyleProperties(): IStyleProperty[];
getDescriptor(): VectorStyleDescriptor;
getDynamicPropertiesArray(): IDynamicStyleProperty[];
getSourceFieldNames(): string[];
}
export class VectorStyle implements IVectorStyle {
static createDescriptor(properties: VectorStylePropertiesDescriptor): VectorStyleDescriptor;
static createDefaultStyleProperties(mapColors: string[]): VectorStylePropertiesDescriptor;
constructor(descriptor: VectorStyleDescriptor, source: IVectorSource, layer: IVectorLayer);
getSourceFieldNames(): string[];
getAllStyleProperties(): IStyleProperty[];
getDescriptor(): VectorStyleDescriptor;
getDynamicPropertiesArray(): IDynamicStyleProperty[];

View file

@ -0,0 +1,170 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { EuiIcon } from '@elastic/eui';
import { VectorStyle } from './styles/vector/vector_style';
import { SOURCE_DATA_ID_ORIGIN, LAYER_TYPE } from '../../common/constants';
import { VectorLayer, VectorLayerArguments } from './vector_layer';
import { canSkipSourceUpdate } from './util/can_skip_fetch';
import { ITiledSingleLayerVectorSource } from './sources/vector_source';
import { SyncContext } from '../actions/map_actions';
import { ISource } from './sources/source';
import { VectorLayerDescriptor, VectorSourceRequestMeta } from '../../common/descriptor_types';
import { MVTSingleLayerVectorSourceConfig } from './sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source_editor';
export class TiledVectorLayer extends VectorLayer {
static type = LAYER_TYPE.TILED_VECTOR;
static createDescriptor(
descriptor: VectorLayerDescriptor,
mapColors: string[]
): VectorLayerDescriptor {
const layerDescriptor = super.createDescriptor(descriptor, mapColors);
layerDescriptor.type = TiledVectorLayer.type;
if (!layerDescriptor.style) {
const styleProperties = VectorStyle.createDefaultStyleProperties(mapColors);
layerDescriptor.style = VectorStyle.createDescriptor(styleProperties);
}
return layerDescriptor;
}
readonly _source: ITiledSingleLayerVectorSource; // downcast to the more specific type
constructor({ layerDescriptor, source }: VectorLayerArguments) {
super({ layerDescriptor, source });
this._source = source as ITiledSingleLayerVectorSource;
}
getCustomIconAndTooltipContent() {
return {
icon: <EuiIcon size="m" type={this.getLayerTypeIconName()} />,
};
}
async _syncMVTUrlTemplate({ startLoading, stopLoading, onLoadError, dataFilters }: SyncContext) {
const requestToken: symbol = Symbol(`layer-${this.getId()}-${SOURCE_DATA_ID_ORIGIN}`);
const searchFilters: VectorSourceRequestMeta = this._getSearchFilters(
dataFilters,
this.getSource(),
this._style
);
const prevDataRequest = this.getSourceDataRequest();
const canSkip = await canSkipSourceUpdate({
source: this._source as ISource,
prevDataRequest,
nextMeta: searchFilters,
});
if (canSkip) {
return null;
}
startLoading(SOURCE_DATA_ID_ORIGIN, requestToken, searchFilters);
try {
const templateWithMeta = await this._source.getUrlTemplateWithMeta();
stopLoading(SOURCE_DATA_ID_ORIGIN, requestToken, templateWithMeta, {});
} catch (error) {
onLoadError(SOURCE_DATA_ID_ORIGIN, requestToken, error.message);
}
}
async syncData(syncContext: SyncContext) {
if (!this.isVisible() || !this.showAtZoomLevel(syncContext.dataFilters.zoom)) {
return;
}
await this._syncSourceStyleMeta(syncContext, this._source, this._style);
await this._syncSourceFormatters(syncContext, this._source, this._style);
await this._syncMVTUrlTemplate(syncContext);
}
_syncSourceBindingWithMb(mbMap: unknown) {
// @ts-ignore
const mbSource = mbMap.getSource(this.getId());
if (!mbSource) {
const sourceDataRequest = this.getSourceDataRequest();
if (!sourceDataRequest) {
// this is possible if the layer was invisible at startup.
// the actions will not perform any data=syncing as an optimization when a layer is invisible
// when turning the layer back into visible, it's possible the url has not been resovled yet.
return;
}
const sourceMeta: MVTSingleLayerVectorSourceConfig | null = sourceDataRequest.getData() as MVTSingleLayerVectorSourceConfig;
if (!sourceMeta) {
return;
}
const sourceId = this.getId();
// @ts-ignore
mbMap.addSource(sourceId, {
type: 'vector',
tiles: [sourceMeta.urlTemplate],
minzoom: sourceMeta.minSourceZoom,
maxzoom: sourceMeta.maxSourceZoom,
});
}
}
_syncStylePropertiesWithMb(mbMap: unknown) {
// @ts-ignore
const mbSource = mbMap.getSource(this.getId());
if (!mbSource) {
return;
}
const sourceDataRequest = this.getSourceDataRequest();
if (!sourceDataRequest) {
return;
}
const sourceMeta: MVTSingleLayerVectorSourceConfig = sourceDataRequest.getData() as MVTSingleLayerVectorSourceConfig;
this._setMbPointsProperties(mbMap, sourceMeta.layerName);
this._setMbLinePolygonProperties(mbMap, sourceMeta.layerName);
}
_requiresPrevSourceCleanup(mbMap: unknown): boolean {
// @ts-ignore
const mbTileSource = mbMap.getSource(this.getId());
if (!mbTileSource) {
return false;
}
const dataRequest = this.getSourceDataRequest();
if (!dataRequest) {
return false;
}
const tiledSourceMeta: MVTSingleLayerVectorSourceConfig | null = dataRequest.getData() as MVTSingleLayerVectorSourceConfig;
if (
mbTileSource.tiles[0] === tiledSourceMeta.urlTemplate &&
mbTileSource.minzoom === tiledSourceMeta.minSourceZoom &&
mbTileSource.maxzoom === tiledSourceMeta.maxSourceZoom
) {
// TileURL and zoom-range captures all the state. If this does not change, no updates are required.
return false;
}
return true;
}
syncLayerWithMB(mbMap: unknown) {
this._removeStaleMbSourcesAndLayers(mbMap);
this._syncSourceBindingWithMb(mbMap);
this._syncStylePropertiesWithMb(mbMap);
}
getJoins() {
return [];
}
getMinZoom() {
// higher resolution vector tiles cannot be displayed at lower-res
return Math.max(this._source.getMinZoom(), super.getMinZoom());
}
}

View file

@ -9,6 +9,7 @@ import { AbstractLayer } from './layer';
import { IVectorSource } from './sources/vector_source';
import {
MapFilters,
LayerDescriptor,
VectorLayerDescriptor,
VectorSourceRequestMeta,
} from '../../common/descriptor_types';
@ -20,7 +21,7 @@ import { SyncContext } from '../actions/map_actions';
type VectorLayerArguments = {
source: IVectorSource;
joins: IJoin[];
joins?: IJoin[];
layerDescriptor: VectorLayerDescriptor;
};
@ -28,11 +29,12 @@ export interface IVectorLayer extends ILayer {
getFields(): Promise<IField[]>;
getStyleEditorFields(): Promise<IField[]>;
getValidJoins(): IJoin[];
getSource(): IVectorSource;
}
export class VectorLayer extends AbstractLayer implements IVectorLayer {
static createDescriptor(
options: Partial<VectorLayerDescriptor>,
options: Partial<LayerDescriptor>,
mapColors?: string[]
): VectorLayerDescriptor;
@ -40,14 +42,30 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer {
protected readonly _style: IVectorStyle;
constructor(options: VectorLayerArguments);
getLayerTypeIconName(): string;
getFields(): Promise<IField[]>;
getStyleEditorFields(): Promise<IField[]>;
getValidJoins(): IJoin[];
_syncSourceStyleMeta(
syncContext: SyncContext,
source: IVectorSource,
style: IVectorStyle
): Promise<void>;
_syncSourceFormatters(
syncContext: SyncContext,
source: IVectorSource,
style: IVectorStyle
): Promise<void>;
syncLayerWithMB(mbMap: unknown): void;
_getSearchFilters(
dataFilters: MapFilters,
source: IVectorSource,
style: IVectorStyle
): VectorSourceRequestMeta;
_syncData(syncContext: SyncContext, source: IVectorSource, style: IVectorStyle): Promise<void>;
ownsMbSourceId(sourceId: string): boolean;
ownsMbLayerId(sourceId: string): boolean;
_setMbPointsProperties(mbMap: unknown, mvtSourceLayer?: string): void;
_setMbLinePolygonProperties(mbMap: unknown, mvtSourceLayer?: string): void;
getSource(): IVectorSource;
}

View file

@ -641,7 +641,7 @@ export class VectorLayer extends AbstractLayer {
}
}
_setMbPointsProperties(mbMap) {
_setMbPointsProperties(mbMap, mvtSourceLayer) {
const pointLayerId = this._getMbPointLayerId();
const symbolLayerId = this._getMbSymbolLayerId();
const pointLayer = mbMap.getLayer(pointLayerId);
@ -658,7 +658,7 @@ export class VectorLayer extends AbstractLayer {
if (symbolLayer) {
mbMap.setLayoutProperty(symbolLayerId, 'visibility', 'none');
}
this._setMbCircleProperties(mbMap);
this._setMbCircleProperties(mbMap, mvtSourceLayer);
} else {
markerLayerId = symbolLayerId;
textLayerId = symbolLayerId;
@ -666,7 +666,7 @@ export class VectorLayer extends AbstractLayer {
mbMap.setLayoutProperty(pointLayerId, 'visibility', 'none');
mbMap.setLayoutProperty(this._getMbTextLayerId(), 'visibility', 'none');
}
this._setMbSymbolProperties(mbMap);
this._setMbSymbolProperties(mbMap, mvtSourceLayer);
}
this.syncVisibilityWithMb(mbMap, markerLayerId);
@ -677,27 +677,36 @@ export class VectorLayer extends AbstractLayer {
}
}
_setMbCircleProperties(mbMap) {
_setMbCircleProperties(mbMap, mvtSourceLayer) {
const sourceId = this.getId();
const pointLayerId = this._getMbPointLayerId();
const pointLayer = mbMap.getLayer(pointLayerId);
if (!pointLayer) {
mbMap.addLayer({
const mbLayer = {
id: pointLayerId,
type: 'circle',
source: sourceId,
paint: {},
});
};
if (mvtSourceLayer) {
mbLayer['source-layer'] = mvtSourceLayer;
}
mbMap.addLayer(mbLayer);
}
const textLayerId = this._getMbTextLayerId();
const textLayer = mbMap.getLayer(textLayerId);
if (!textLayer) {
mbMap.addLayer({
const mbLayer = {
id: textLayerId,
type: 'symbol',
source: sourceId,
});
};
if (mvtSourceLayer) {
mbLayer['source-layer'] = mvtSourceLayer;
}
mbMap.addLayer(mbLayer);
}
const filterExpr = getPointFilterExpression(this._hasJoins());
@ -719,17 +728,21 @@ export class VectorLayer extends AbstractLayer {
});
}
_setMbSymbolProperties(mbMap) {
_setMbSymbolProperties(mbMap, mvtSourceLayer) {
const sourceId = this.getId();
const symbolLayerId = this._getMbSymbolLayerId();
const symbolLayer = mbMap.getLayer(symbolLayerId);
if (!symbolLayer) {
mbMap.addLayer({
const mbLayer = {
id: symbolLayerId,
type: 'symbol',
source: sourceId,
});
};
if (mvtSourceLayer) {
mbLayer['source-layer'] = mvtSourceLayer;
}
mbMap.addLayer(mbLayer);
}
const filterExpr = getPointFilterExpression(this._hasJoins());
@ -750,26 +763,34 @@ export class VectorLayer extends AbstractLayer {
});
}
_setMbLinePolygonProperties(mbMap) {
_setMbLinePolygonProperties(mbMap, mvtSourceLayer) {
const sourceId = this.getId();
const fillLayerId = this._getMbPolygonLayerId();
const lineLayerId = this._getMbLineLayerId();
const hasJoins = this._hasJoins();
if (!mbMap.getLayer(fillLayerId)) {
mbMap.addLayer({
const mbLayer = {
id: fillLayerId,
type: 'fill',
source: sourceId,
paint: {},
});
};
if (mvtSourceLayer) {
mbLayer['source-layer'] = mvtSourceLayer;
}
mbMap.addLayer(mbLayer);
}
if (!mbMap.getLayer(lineLayerId)) {
mbMap.addLayer({
const mbLayer = {
id: lineLayerId,
type: 'line',
source: sourceId,
paint: {},
});
};
if (mvtSourceLayer) {
mbLayer['source-layer'] = mvtSourceLayer;
}
mbMap.addLayer(mbLayer);
}
this.getCurrentStyle().setMBPaintProperties({
alpha: this.getAlpha(),

View file

@ -161,19 +161,7 @@ export class VectorTileLayer extends TileLayer {
return;
}
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);
}
});
}
this._removeStaleMbSourcesAndLayers(mbMap);
let initialBootstrapCompleted = false;
const sourceIds = Object.keys(vectorStyle.sources);

View file

@ -33,6 +33,7 @@ import {
setVisualizations,
// @ts-ignore
} from './kibana_services';
import { registerLayerWizards } from './layers/load_layer_wizards';
export interface MapsPluginSetupDependencies {
inspector: InspectorSetupContract;
@ -72,6 +73,7 @@ export const bindStartCoreAndPlugins = (core: CoreStart, plugins: any) => {
setUiActions(plugins.uiActions);
setNavigation(plugins.navigation);
setCoreI18n(core.i18n);
registerLayerWizards();
};
/**