Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] [Maps] Add styling and tooltip support to mapbox mvt vector tile sources (#64488) #70592

Merged
merged 1 commit into from
Jul 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import React, { Component } from 'react';
import React, { Component, ReactNode } from 'react';
import { EuiFormRow, EuiDualRange } from '@elastic/eui';
import { EuiFormRowDisplayKeys } from '@elastic/eui/src/components/form/form_row/form_row';
import { EuiDualRangeProps } from '@elastic/eui/src/components/form/range/dual_range';
Expand All @@ -32,7 +32,7 @@ export type ValueMember = EuiDualRangeProps['value'][0];
interface Props extends Omit<EuiDualRangeProps, 'value' | 'onChange' | 'min' | 'max'> {
value?: Value;
allowEmptyRange?: boolean;
label?: string;
label?: string | ReactNode;
formRowDisplay?: EuiFormRowDisplayKeys;
onChange?: (val: [string, string]) => void;
min?: number;
Expand Down
5 changes: 5 additions & 0 deletions x-pack/plugins/maps/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,11 @@ export enum SCALING_TYPES {

export const RGBA_0000 = 'rgba(0,0,0,0)';

export enum MVT_FIELD_TYPE {
STRING = 'String',
NUMBER = 'Number',
}

export const SPATIAL_FILTERS_LAYER_ID = 'SPATIAL_FILTERS_LAYER_ID';

export enum INITIAL_LOCATION {
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/maps/common/descriptor_types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
*/

export * from './data_request_descriptor_types';
export * from './descriptor_types';
export * from './sources';
export * from './map_descriptor';
export * from './style_property_descriptor_types';
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/
/* eslint-disable @typescript-eslint/consistent-type-definitions */

import { GeoJsonProperties } from 'geojson';
import { Query } from '../../../../../src/plugins/data/common';
import { DRAW_TYPE, ES_GEO_FIELD_TYPE, ES_SPATIAL_RELATIONS } from '../constants';

Expand Down Expand Up @@ -39,8 +40,9 @@ export type Goto = {
};

export type TooltipFeature = {
id: number;
id?: number | string;
layerId: string;
mbProperties: GeoJsonProperties;
};

export type TooltipState = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@

import { FeatureCollection } from 'geojson';
import { Query } from 'src/plugins/data/public';
import { AGG_TYPE, GRID_RESOLUTION, RENDER_AS, SORT_ORDER, SCALING_TYPES } from '../constants';
import {
AGG_TYPE,
GRID_RESOLUTION,
RENDER_AS,
SORT_ORDER,
SCALING_TYPES,
MVT_FIELD_TYPE,
} from '../constants';
import { StyleDescriptor, VectorStyleDescriptor } from './style_property_descriptor_types';
import { DataRequestDescriptor } from './data_request_descriptor_types';

Expand Down Expand Up @@ -96,18 +103,34 @@ export type XYZTMSSourceDescriptor = AbstractSourceDescriptor &
urlTemplate: string;
};

export type TiledSingleLayerVectorSourceDescriptor = AbstractSourceDescriptor & {
export type MVTFieldDescriptor = {
name: string;
type: MVT_FIELD_TYPE;
};

export type TiledSingleLayerVectorSourceSettings = {
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
// These are important so mapbox does not issue invalid requests based on the zoom level.

// Tiled layer data cannot be displayed at lower levels of zoom than that they are stored in the tileset.
// e.g. building footprints at level 14 cannot be displayed at level 0.
minSourceZoom: number;
// Tiled layer 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
maxSourceZoom: number;

fields: MVTFieldDescriptor[];
};

export type TiledSingleLayerVectorSourceDescriptor = AbstractSourceDescriptor &
TiledSingleLayerVectorSourceSettings & {
tooltipProperties: string[];
};

export type GeojsonFileSourceDescriptor = {
__featureCollection: FeatureCollection;
name: string;
Expand Down
4 changes: 4 additions & 0 deletions x-pack/plugins/maps/public/classes/fields/es_agg_field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ export class ESAggField implements IESAggField {
async getCategoricalFieldMetaRequest(size: number): Promise<unknown> {
return this._esDocField ? this._esDocField.getCategoricalFieldMetaRequest(size) : null;
}

supportsAutoDomain(): boolean {
return true;
}
}

export function esAggFieldsFactory(
Expand Down
10 changes: 10 additions & 0 deletions x-pack/plugins/maps/public/classes/fields/field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export interface IField {
isValid(): boolean;
getOrdinalFieldMetaRequest(): Promise<unknown>;
getCategoricalFieldMetaRequest(size: number): Promise<unknown>;

// Determines whether Maps-app can automatically determine the domain of the field-values
// if this is not the case (e.g. for .mvt tiled data),
// then styling properties that require the domain to be known cannot use this property.
supportsAutoDomain(): boolean;

supportsFieldMeta(): boolean;
}

Expand Down Expand Up @@ -80,4 +86,8 @@ export class AbstractField implements IField {
async getCategoricalFieldMetaRequest(size: number): Promise<unknown> {
return null;
}

supportsAutoDomain(): boolean {
return true;
}
}
59 changes: 59 additions & 0 deletions x-pack/plugins/maps/public/classes/fields/mvt_field.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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 { AbstractField, IField } from './field';
import { FIELD_ORIGIN, MVT_FIELD_TYPE } from '../../../common/constants';
import { ITiledSingleLayerVectorSource, IVectorSource } from '../sources/vector_source';
import { MVTFieldDescriptor } from '../../../common/descriptor_types';

export class MVTField extends AbstractField implements IField {
private readonly _source: ITiledSingleLayerVectorSource;
private readonly _type: MVT_FIELD_TYPE;
constructor({
fieldName,
type,
source,
origin,
}: {
fieldName: string;
source: ITiledSingleLayerVectorSource;
origin: FIELD_ORIGIN;
type: MVT_FIELD_TYPE;
}) {
super({ fieldName, origin });
this._source = source;
this._type = type;
}

getMVTFieldDescriptor(): MVTFieldDescriptor {
return {
type: this._type,
name: this.getName(),
};
}

getSource(): IVectorSource {
return this._source;
}

async getDataType(): Promise<string> {
if (this._type === MVT_FIELD_TYPE.STRING) {
return 'string';
} else if (this._type === MVT_FIELD_TYPE.NUMBER) {
return 'number';
} else {
throw new Error(`Unrecognized MVT field-type ${this._type}`);
}
}

async getLabel(): Promise<string> {
return this.getName();
}

supportsAutoDomain() {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ export class TopTermPercentageField implements IESAggField {
return 0;
}

supportsAutoDomain(): boolean {
return true;
}

supportsFieldMeta(): boolean {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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 sinon from 'sinon';
import { DataRequestContext } from '../../../actions';
import { DataMeta, MapFilters } from '../../../../common/descriptor_types';

export class MockSyncContext implements DataRequestContext {
dataFilters: MapFilters;
isRequestStillActive: (dataId: string, requestToken: symbol) => boolean;
onLoadError: (dataId: string, requestToken: symbol, errorMessage: string) => void;
registerCancelCallback: (requestToken: symbol, callback: () => void) => void;
startLoading: (dataId: string, requestToken: symbol, meta: DataMeta) => void;
stopLoading: (dataId: string, requestToken: symbol, data: object, meta: DataMeta) => void;
updateSourceData: (newData: unknown) => void;

constructor({ dataFilters }: { dataFilters: Partial<MapFilters> }) {
const mapFilters: MapFilters = {
filters: [],
timeFilters: {
from: 'now',
to: '15m',
mode: 'relative',
},
zoom: 0,
...dataFilters,
};

this.dataFilters = mapFilters;
this.isRequestStillActive = sinon.spy();
this.onLoadError = sinon.spy();
this.registerCancelCallback = sinon.spy();
this.startLoading = sinon.spy();
this.stopLoading = sinon.spy();
this.updateSourceData = sinon.spy();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { getFileUploadComponent } from '../../../kibana_services';
import { GeojsonFileSource } from '../../sources/geojson_file_source';
import { VectorLayer } from '../../layers/vector_layer/vector_layer';
// @ts-ignore
// @ts-expect-error
import { createDefaultLayerDescriptor } from '../../sources/es_search_source';
import { RenderWizardArguments } from '../../layers/layer_wizard_registry';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export class HeatmapLayer extends VectorLayer {
resolution: this.getSource().getGridResolution(),
});
mbMap.setPaintProperty(heatmapLayerId, 'heatmap-opacity', this.getAlpha());
mbMap.setLayerZoomRange(heatmapLayerId, this._descriptor.minZoom, this._descriptor.maxZoom);
mbMap.setLayerZoomRange(heatmapLayerId, this.getMinZoom(), this.getMaxZoom());
}

getLayerTypeIconName() {
Expand Down
17 changes: 9 additions & 8 deletions x-pack/plugins/maps/public/classes/layers/layer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -325,27 +325,28 @@ export class AbstractLayer implements ILayer {
return this._source.getMinZoom();
}

_getMbSourceId() {
return this.getId();
}

_requiresPrevSourceCleanup(mbMap: unknown) {
return false;
}

_removeStaleMbSourcesAndLayers(mbMap: unknown) {
if (this._requiresPrevSourceCleanup(mbMap)) {
// @ts-ignore
// @ts-expect-error
const mbStyle = mbMap.getStyle();
// @ts-ignore
// @ts-expect-error
mbStyle.layers.forEach((mbLayer) => {
// @ts-ignore
if (this.ownsMbLayerId(mbLayer.id)) {
// @ts-ignore
// @ts-expect-error
mbMap.removeLayer(mbLayer.id);
}
});
// @ts-ignore
Object.keys(mbStyle.sources).some((mbSourceId) => {
// @ts-ignore
if (this.ownsMbSourceId(mbSourceId)) {
// @ts-ignore
// @ts-expect-error
mbMap.removeSource(mbSourceId);
}
});
Expand Down Expand Up @@ -429,7 +430,7 @@ export class AbstractLayer implements ILayer {
throw new Error('Should implement AbstractLayer#ownsMbLayerId');
}

ownsMbSourceId(sourceId: string): boolean {
ownsMbSourceId(mbSourceId: string): boolean {
throw new Error('Should implement AbstractLayer#ownsMbSourceId');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ export class TileLayer extends AbstractLayer {
return;
}

const sourceId = this.getId();
mbMap.addSource(sourceId, {
const mbSourceId = this._getMbSourceId();
mbMap.addSource(mbSourceId, {
type: 'raster',
tiles: [tmsSourceData.url],
tileSize: 256,
Expand All @@ -85,7 +85,7 @@ export class TileLayer extends AbstractLayer {
mbMap.addLayer({
id: mbLayerId,
type: 'raster',
source: sourceId,
source: mbSourceId,
minzoom: this._descriptor.minZoom,
maxzoom: this._descriptor.maxZoom,
});
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading