mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[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:
parent
0a9e17b57f
commit
ad41eea211
37 changed files with 816 additions and 118 deletions
|
@ -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';
|
||||
|
|
|
@ -17,4 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export { ValidatedDualRange } from './validated_dual_range';
|
||||
export { ValidatedDualRange, Value } from './validated_dual_range';
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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}`);
|
||||
}
|
||||
|
|
|
@ -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', () => {});
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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[];
|
||||
|
|
|
@ -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;
|
||||
|
|
15
x-pack/plugins/maps/public/layers/layer.d.ts
vendored
15
x-pack/plugins/maps/public/layers/layer.d.ts
vendored
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
66
x-pack/plugins/maps/public/layers/load_layer_wizards.ts
Normal file
66
x-pack/plugins/maps/public/layers/load_layer_wizards.ts
Normal 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;
|
||||
}
|
|
@ -23,6 +23,7 @@ export class ESGeoGridSource extends AbstractESAggSource {
|
|||
|
||||
constructor(sourceDescriptor: ESGeoGridSourceDescriptor, inspectorAdapters: unknown);
|
||||
|
||||
getFieldNames(): string[];
|
||||
getGridResolution(): GRID_RESOLUTION;
|
||||
getGeoGridPrecision(zoom: number): number;
|
||||
}
|
||||
|
|
|
@ -9,4 +9,5 @@ import { ESSearchSourceDescriptor } from '../../../../common/descriptor_types';
|
|||
|
||||
export class ESSearchSource extends AbstractESSource {
|
||||
constructor(sourceDescriptor: Partial<ESSearchSourceDescriptor>, inspectorAdapters: unknown);
|
||||
getFieldNames(): string[];
|
||||
}
|
||||
|
|
|
@ -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';
|
|
@ -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,
|
||||
};
|
|
@ -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,
|
||||
});
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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[];
|
||||
|
|
170
x-pack/plugins/maps/public/layers/tiled_vector_layer.tsx
Normal file
170
x-pack/plugins/maps/public/layers/tiled_vector_layer.tsx
Normal 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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue