Skip to content

Commit

Permalink
[Maps] Auto generate legends and styles from mvt data (#94811)
Browse files Browse the repository at this point in the history
  • Loading branch information
thomasneirynck authored Jul 8, 2021
1 parent bf5ddcf commit 9134bc0
Show file tree
Hide file tree
Showing 35 changed files with 869 additions and 237 deletions.
8 changes: 4 additions & 4 deletions api_docs/maps.json
Original file line number Diff line number Diff line change
Expand Up @@ -3085,13 +3085,13 @@
},
{
"parentPluginId": "maps",
"id": "def-common.KBN_TOO_MANY_FEATURES_PROPERTY",
"id": "def-common.KBN_METADATA_FEATURE",
"type": "string",
"tags": [],
"label": "KBN_TOO_MANY_FEATURES_PROPERTY",
"label": "KBN_METADATA_FEATURE",
"description": [],
"signature": [
"\"__kbn_too_many_features__\""
"\"__kbn_metadata_feature__\""
],
"source": {
"path": "x-pack/plugins/maps/common/constants.ts",
Expand Down Expand Up @@ -3582,4 +3582,4 @@
}
]
}
}
}
5 changes: 4 additions & 1 deletion x-pack/plugins/maps/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ export const MVT_GETGRIDTILE_API_PATH = 'mvt/getGridTile';
export const MVT_SOURCE_LAYER_NAME = 'source_layer';
// Identifies vector tile "too many features" feature.
// "too many features" feature is a box showing area that contains too many features for single ES search response
export const KBN_TOO_MANY_FEATURES_PROPERTY = '__kbn_too_many_features__';
export const KBN_METADATA_FEATURE = '__kbn_metadata_feature__';
export const KBN_FEATURE_COUNT = '__kbn_feature_count__';
export const KBN_IS_TILE_COMPLETE = '__kbn_is_tile_complete__';
export const KBN_VECTOR_SHAPE_TYPE_COUNTS = '__kbn_vector_shape_type_counts__';
export const KBN_TOO_MANY_FEATURES_IMAGE_ID = '__kbn_too_many_features_image_id__';
// Identifies centroid feature.
// Centroids are a single point for representing lines, multiLines, polygons, and multiPolygons
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,22 @@
/* eslint-disable @typescript-eslint/consistent-type-definitions */

import { Query } from 'src/plugins/data/public';
import { Feature } from 'geojson';
import {
FieldMeta,
HeatmapStyleDescriptor,
StyleDescriptor,
VectorStyleDescriptor,
} from './style_property_descriptor_types';
import { DataRequestDescriptor } from './data_request_descriptor_types';
import { AbstractSourceDescriptor, TermJoinSourceDescriptor } from './source_descriptor_types';
import { VectorShapeTypeCounts } from '../get_geometry_counts';
import {
KBN_FEATURE_COUNT,
KBN_IS_TILE_COMPLETE,
KBN_METADATA_FEATURE,
KBN_VECTOR_SHAPE_TYPE_COUNTS,
} from '../constants';

export type Attribution = {
label: string;
Expand All @@ -26,13 +35,24 @@ export type JoinDescriptor = {
right: TermJoinSourceDescriptor;
};

export type TileMetaFeature = Feature & {
properties: {
[KBN_METADATA_FEATURE]: true;
[KBN_IS_TILE_COMPLETE]: boolean;
[KBN_FEATURE_COUNT]: number;
[KBN_VECTOR_SHAPE_TYPE_COUNTS]: VectorShapeTypeCounts;
fieldMeta?: FieldMeta;
};
};

export type LayerDescriptor = {
__dataRequests?: DataRequestDescriptor[];
__isInErrorState?: boolean;
__isPreviewLayer?: boolean;
__errorMessage?: string;
__trackedLayerDescriptor?: LayerDescriptor;
__areTilesLoaded?: boolean;
__metaFromTiles?: TileMetaFeature[];
alpha?: number;
attribution?: Attribution;
id: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,14 +225,16 @@ export type GeometryTypes = {
isPolygonsOnly: boolean;
};

export type FieldMeta = {
[key: string]: {
range?: RangeFieldMeta;
categories?: CategoryFieldMeta;
};
};

export type StyleMetaDescriptor = {
geometryTypes?: GeometryTypes;
fieldMeta: {
[key: string]: {
range?: RangeFieldMeta;
categories?: CategoryFieldMeta;
};
};
fieldMeta: FieldMeta;
};

export type VectorStyleDescriptor = StyleDescriptor & {
Expand Down
4 changes: 2 additions & 2 deletions x-pack/plugins/maps/common/get_centroid_features.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ test('should not create centroid feature for point and multipoint', () => {
expect(centroidFeatures.length).toBe(0);
});

test('should not create centroid for too many features polygon', () => {
test('should not create centroid for the metadata polygon', () => {
const polygonFeature: Feature = {
type: 'Feature',
geometry: {
Expand All @@ -60,7 +60,7 @@ test('should not create centroid for too many features polygon', () => {
],
},
properties: {
__kbn_too_many_features__: true,
__kbn_metadata_feature__: true,
prop0: 'value0',
prop1: 0.0,
},
Expand Down
8 changes: 2 additions & 6 deletions x-pack/plugins/maps/common/get_centroid_features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,15 @@ import turfArea from '@turf/area';
import turfCenterOfMass from '@turf/center-of-mass';
import turfLength from '@turf/length';
import { lineString, polygon } from '@turf/helpers';
import {
GEO_JSON_TYPE,
KBN_IS_CENTROID_FEATURE,
KBN_TOO_MANY_FEATURES_PROPERTY,
} from './constants';
import { GEO_JSON_TYPE, KBN_IS_CENTROID_FEATURE, KBN_METADATA_FEATURE } from './constants';

export function getCentroidFeatures(featureCollection: FeatureCollection): Feature[] {
const centroids = [];
for (let i = 0; i < featureCollection.features.length; i++) {
const feature = featureCollection.features[i];

// do not add centroid for kibana added features
if (feature.properties?.[KBN_TOO_MANY_FEATURES_PROPERTY]) {
if (feature.properties?.[KBN_METADATA_FEATURE]) {
continue;
}

Expand Down
45 changes: 45 additions & 0 deletions x-pack/plugins/maps/common/get_geometry_counts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { Feature } from 'geojson';
import { GEO_JSON_TYPE, VECTOR_SHAPE_TYPE } from './constants';

export interface VectorShapeTypeCounts {
[VECTOR_SHAPE_TYPE.POINT]: number;
[VECTOR_SHAPE_TYPE.LINE]: number;
[VECTOR_SHAPE_TYPE.POLYGON]: number;
}

export function countVectorShapeTypes(features: Feature[]): VectorShapeTypeCounts {
const vectorShapeTypeCounts: VectorShapeTypeCounts = {
[VECTOR_SHAPE_TYPE.POINT]: 0,
[VECTOR_SHAPE_TYPE.LINE]: 0,
[VECTOR_SHAPE_TYPE.POLYGON]: 0,
};

for (let i = 0; i < features.length; i++) {
const feature: Feature = features[i];
if (
feature.geometry.type === GEO_JSON_TYPE.POINT ||
feature.geometry.type === GEO_JSON_TYPE.MULTI_POINT
) {
vectorShapeTypeCounts[VECTOR_SHAPE_TYPE.POINT] += 1;
} else if (
feature.geometry.type === GEO_JSON_TYPE.LINE_STRING ||
feature.geometry.type === GEO_JSON_TYPE.MULTI_LINE_STRING
) {
vectorShapeTypeCounts[VECTOR_SHAPE_TYPE.LINE] += 1;
} else if (
feature.geometry.type === GEO_JSON_TYPE.POLYGON ||
feature.geometry.type === GEO_JSON_TYPE.MULTI_POLYGON
) {
vectorShapeTypeCounts[VECTOR_SHAPE_TYPE.POLYGON] += 1;
}
}

return vectorShapeTypeCounts;
}
46 changes: 46 additions & 0 deletions x-pack/plugins/maps/common/pluck_category_field_meta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { Feature } from 'geojson';
import { CategoryFieldMeta } from './descriptor_types';

export function pluckCategoryFieldMeta(
features: Feature[],
name: string,
size: number
): CategoryFieldMeta | null {
const counts = new Map();
for (let i = 0; i < features.length; i++) {
const feature = features[i];
const term = feature.properties ? feature.properties[name] : undefined;
// properties object may be sparse, so need to check if the field is effectively present
if (typeof term !== undefined) {
if (counts.has(term)) {
counts.set(term, counts.get(term) + 1);
} else {
counts.set(term, 1);
}
}
}

return trimCategories(counts, size);
}

export function trimCategories(counts: Map<string, number>, size: number): CategoryFieldMeta {
const ordered = [];
for (const [key, value] of counts) {
ordered.push({ key, count: value });
}

ordered.sort((a, b) => {
return b.count - a.count;
});
const truncated = ordered.slice(0, size);
return {
categories: truncated,
} as CategoryFieldMeta;
}
34 changes: 34 additions & 0 deletions x-pack/plugins/maps/common/pluck_range_field_meta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { Feature } from 'geojson';
import { RangeFieldMeta } from './descriptor_types';

export function pluckRangeFieldMeta(
features: Feature[],
name: string,
parseValue: (rawValue: unknown) => number
): RangeFieldMeta | null {
let min = Infinity;
let max = -Infinity;
for (let i = 0; i < features.length; i++) {
const feature = features[i];
const newValue = feature.properties ? parseValue(feature.properties[name]) : NaN;
if (!isNaN(newValue)) {
min = Math.min(min, newValue);
max = Math.max(max, newValue);
}
}

return min === Infinity || max === -Infinity
? null
: ({
min,
max,
delta: max - min,
} as RangeFieldMeta);
}
13 changes: 5 additions & 8 deletions x-pack/plugins/maps/public/actions/data_request_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { FeatureCollection } from 'geojson';
import { MapStoreState } from '../reducers/store';
import {
KBN_IS_CENTROID_FEATURE,
LAYER_STYLE_TYPE,
LAYER_TYPE,
SOURCE_DATA_REQUEST_ID,
} from '../../common/constants';
Expand Down Expand Up @@ -49,7 +48,6 @@ import { IVectorLayer } from '../classes/layers/vector_layer';
import { DataMeta, MapExtent, MapFilters } from '../../common/descriptor_types';
import { DataRequestAbortError } from '../classes/util/data_request';
import { scaleBounds, turfBboxToBounds } from '../../common/elasticsearch_util';
import { IVectorStyle } from '../classes/styles/vector/vector_style';

const FIT_TO_BOUNDS_SCALE_FACTOR = 0.1;

Expand Down Expand Up @@ -95,14 +93,12 @@ export function updateStyleMeta(layerId: string | null) {
if (!layer) {
return;
}
const sourceDataRequest = layer.getSourceDataRequest();
const style = layer.getCurrentStyle();
if (!style || !sourceDataRequest || style.getType() !== LAYER_STYLE_TYPE.VECTOR) {

const styleMeta = await layer.getStyleMetaDescriptorFromLocalFeatures();
if (!styleMeta) {
return;
}
const styleMeta = await (style as IVectorStyle).pluckStyleMetaFromSourceDataRequest(
sourceDataRequest
);

dispatch({
type: SET_LAYER_STYLE_META,
layerId,
Expand Down Expand Up @@ -249,6 +245,7 @@ function endDataLoad(
dispatch(unregisterCancelCallback(requestToken));
const dataRequest = getDataRequestDescriptor(getState(), layerId, dataId);
if (dataRequest && dataRequest.dataRequestToken !== requestToken) {
// todo - investigate - this may arise with failing style meta request and should not throw in that case
throw new DataRequestAbortError();
}

Expand Down
21 changes: 21 additions & 0 deletions x-pack/plugins/maps/public/actions/layer_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
JoinDescriptor,
LayerDescriptor,
StyleDescriptor,
TileMetaFeature,
} from '../../common/descriptor_types';
import { ILayer } from '../classes/layers/layer';
import { IVectorLayer } from '../classes/layers/vector_layer';
Expand Down Expand Up @@ -591,3 +592,23 @@ export function setAreTilesLoaded(layerId: string, areTilesLoaded: boolean) {
newValue: areTilesLoaded,
};
}

export function updateMetaFromTiles(layerId: string, mbMetaFeatures: TileMetaFeature[]) {
return async (
dispatch: ThunkDispatch<MapStoreState, void, AnyAction>,
getState: () => MapStoreState
) => {
const layer = getLayerById(layerId, getState());
if (!layer) {
return;
}

dispatch({
type: UPDATE_LAYER_PROP,
id: layerId,
propName: '__metaFromTiles',
newValue: mbMetaFeatures,
});
await dispatch(updateStyleMeta(layerId));
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export class CountAggField implements IESAggField {
}

supportsAutoDomain(): boolean {
return this._canReadFromGeoJson ? true : this.supportsFieldMeta();
return true;
}

canReadFromGeoJson(): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,9 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer {

private readonly _isClustered: boolean;
private readonly _clusterSource: ESGeoGridSource;
private readonly _clusterStyle: IVectorStyle;
private readonly _clusterStyle: VectorStyle;
private readonly _documentSource: ESSearchSource;
private readonly _documentStyle: IVectorStyle;
private readonly _documentStyle: VectorStyle;

constructor(options: BlendedVectorLayerArguments) {
super({
Expand All @@ -195,7 +195,7 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer {
});

this._documentSource = this._source as ESSearchSource; // VectorLayer constructor sets _source as document source
this._documentStyle = this._style as IVectorStyle; // VectorLayer constructor sets _style as document source
this._documentStyle = this._style; // VectorLayer constructor sets _style as document source

this._clusterSource = getClusterSource(this._documentSource, this._documentStyle);
const clusterStyleDescriptor = getClusterStyleDescriptor(
Expand Down Expand Up @@ -279,7 +279,7 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer {
return this._documentSource;
}

getCurrentStyle(): IVectorStyle {
getCurrentStyle(): VectorStyle {
return this._isClustered ? this._clusterStyle : this._documentStyle;
}

Expand Down
Loading

0 comments on commit 9134bc0

Please sign in to comment.