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] Auto generate legends and styles from mvt data (#94811) #105203

Merged
merged 1 commit into from
Jul 12, 2021
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
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