From 8b001dc913b2f5ac415f442c441e224f4ad6b316 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Tue, 15 Sep 2020 18:26:05 -0400 Subject: [PATCH] [Maps] Add DynamicStyleProperty#getMbPropertyName and DynamicStyleProperty#getMbPropertyValue (#77366) --- x-pack/plugins/maps/common/constants.ts | 4 ++ .../maps/public/classes/sources/source.ts | 4 +- .../dynamic_color_property.test.tsx | 6 +- .../properties/dynamic_icon_property.test.tsx | 4 +- .../dynamic_orientation_property.ts | 22 +++++--- .../properties/dynamic_size_property.test.tsx | 4 +- .../properties/dynamic_size_property.tsx | 4 +- .../properties/dynamic_style_property.tsx | 55 ++++++++++++++++++- .../properties/dynamic_text_property.ts | 18 +++--- .../vector/properties/style_property.ts | 15 +++-- .../classes/styles/vector/vector_style.tsx | 29 ++++------ 11 files changed, 110 insertions(+), 55 deletions(-) diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index 3589ec41e4db3..d72d04d2a1843 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -263,3 +263,7 @@ export enum MB_LOOKUP_FUNCTION { GET = 'get', FEATURE_STATE = 'feature-state', } + +export type RawValue = string | number | boolean | undefined | null; + +export type FieldFormatter = (value: RawValue) => string | number; diff --git a/x-pack/plugins/maps/public/classes/sources/source.ts b/x-pack/plugins/maps/public/classes/sources/source.ts index 4a050cc3d7d19..7e7a7bd8f049d 100644 --- a/x-pack/plugins/maps/public/classes/sources/source.ts +++ b/x-pack/plugins/maps/public/classes/sources/source.ts @@ -12,7 +12,7 @@ import { Adapters } from 'src/plugins/inspector/public'; import { copyPersistentState } from '../../reducers/util'; import { IField } from '../fields/field'; -import { MAX_ZOOM, MIN_ZOOM } from '../../../common/constants'; +import { FieldFormatter, MAX_ZOOM, MIN_ZOOM } from '../../../common/constants'; import { AbstractSourceDescriptor } from '../../../common/descriptor_types'; import { OnSourceChangeArgs } from '../../connected_components/layer_panel/view'; @@ -37,8 +37,6 @@ export type PreIndexedShape = { path: string; }; -export type FieldFormatter = (value: string | number | null | undefined | boolean) => string; - export interface ISource { destroy(): void; getDisplayName(): Promise; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx index de8f3b5c09175..c9188a0a19b0d 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.tsx @@ -15,7 +15,7 @@ import { shallow } from 'enzyme'; import { Feature, Point } from 'geojson'; import { DynamicColorProperty } from './dynamic_color_property'; -import { COLOR_MAP_TYPE, VECTOR_STYLES } from '../../../../../common/constants'; +import { COLOR_MAP_TYPE, RawValue, VECTOR_STYLES } from '../../../../../common/constants'; import { mockField, MockLayer, MockStyle } from './__tests__/test_util'; import { ColorDynamicOptions } from '../../../../../common/descriptor_types'; import { IVectorLayer } from '../../../layers/vector_layer/vector_layer'; @@ -28,7 +28,7 @@ const makeProperty = (options: ColorDynamicOptions, style?: MockStyle, field?: I field ? field : mockField, (new MockLayer(style ? style : new MockStyle()) as unknown) as IVectorLayer, () => { - return (value: string | number | undefined) => value + '_format'; + return (value: RawValue) => value + '_format'; } ); }; @@ -273,7 +273,7 @@ describe('supportsFieldMeta', () => { null, (new MockLayer(new MockStyle()) as unknown) as IVectorLayer, () => { - return (value: string | number | undefined) => value + '_format'; + return (value: RawValue) => value + '_format'; } ); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx index 06987ab8bcc48..2f9e4709c1c0b 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.test.tsx @@ -13,7 +13,7 @@ jest.mock('../components/vector_style_editor', () => ({ })); import React from 'react'; -import { VECTOR_STYLES } from '../../../../../common/constants'; +import { RawValue, VECTOR_STYLES } from '../../../../../common/constants'; // @ts-ignore import { DynamicIconProperty } from './dynamic_icon_property'; import { mockField, MockLayer } from './__tests__/test_util'; @@ -33,7 +33,7 @@ const makeProperty = (options: Partial, field: IField = mock field, mockVectorLayer, () => { - return (value: string | number | undefined) => value + '_format'; + return (value: RawValue) => value + '_format'; } ); }; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.ts b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.ts index dd976027a50f2..192fa1f4db6e0 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_orientation_property.ts @@ -5,25 +5,29 @@ */ import { Map as MbMap } from 'mapbox-gl'; -import { DynamicStyleProperty } from './dynamic_style_property'; -import { getComputedFieldName } from '../style_util'; -import { VECTOR_STYLES } from '../../../../../common/constants'; +import { DynamicStyleProperty, getNumericalMbFeatureStateValue } from './dynamic_style_property'; import { OrientationDynamicOptions } from '../../../../../common/descriptor_types'; +import { RawValue } from '../../../../../common/constants'; export class DynamicOrientationProperty extends DynamicStyleProperty { syncIconRotationWithMb(symbolLayerId: string, mbMap: MbMap) { if (this._field && this._field.isValid()) { - const targetName = this._field.supportsAutoDomain() - ? getComputedFieldName(VECTOR_STYLES.ICON_ORIENTATION, this.getFieldName()) - : this._field.getName(); - // Using property state instead of feature-state because layout properties do not support feature-state - mbMap.setLayoutProperty(symbolLayerId, 'icon-rotate', ['coalesce', ['get', targetName], 0]); + const targetName = this.getMbPropertyName(); + mbMap.setLayoutProperty(symbolLayerId, 'icon-rotate', [ + 'coalesce', + [this.getMbLookupFunction(), targetName], + 0, + ]); } else { mbMap.setLayoutProperty(symbolLayerId, 'icon-rotate', 0); } } - supportsMbFeatureState() { + supportsMbFeatureState(): boolean { return false; } + + getMbPropertyValue(rawValue: RawValue): RawValue { + return getNumericalMbFeatureStateValue(rawValue); + } } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx index c5298067f6cbe..b4244cf7829c4 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx @@ -15,7 +15,7 @@ import { shallow } from 'enzyme'; // @ts-ignore import { DynamicSizeProperty } from './dynamic_size_property'; -import { VECTOR_STYLES } from '../../../../../common/constants'; +import { RawValue, VECTOR_STYLES } from '../../../../../common/constants'; import { IField } from '../../../fields/field'; import { Map as MbMap } from 'mapbox-gl'; import { SizeDynamicOptions } from '../../../../../common/descriptor_types'; @@ -48,7 +48,7 @@ const makeProperty = ( field, (new MockLayer(mockStyle) as unknown) as IVectorLayer, () => { - return (value: string | number | undefined) => value + '_format'; + return (value: RawValue) => value + '_format'; }, false ); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx index 35c830f3cb5e3..4e75a61539ad9 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx @@ -7,7 +7,7 @@ import _ from 'lodash'; import React from 'react'; import { Map as MbMap } from 'mapbox-gl'; -import { DynamicStyleProperty, FieldFormatter } from './dynamic_style_property'; +import { DynamicStyleProperty } from './dynamic_style_property'; import { OrdinalLegend } from '../components/legend/ordinal_legend'; import { makeMbClampedNumberExpression } from '../style_util'; import { @@ -16,7 +16,7 @@ import { SMALL_MAKI_ICON_SIZE, // @ts-expect-error } from '../symbol_utils'; -import { MB_LOOKUP_FUNCTION, VECTOR_STYLES } from '../../../../../common/constants'; +import { FieldFormatter, MB_LOOKUP_FUNCTION, VECTOR_STYLES } from '../../../../../common/constants'; import { SizeDynamicOptions } from '../../../../../common/descriptor_types'; import { IField } from '../../../fields/field'; import { IVectorLayer } from '../../../layers/vector_layer/vector_layer'; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx index f6ab052497723..2bc819daeea90 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx @@ -15,6 +15,8 @@ import { SOURCE_META_DATA_REQUEST_ID, STYLE_TYPE, VECTOR_STYLES, + RawValue, + FieldFormatter, } from '../../../../../common/constants'; import { OrdinalFieldMetaPopover } from '../components/field_meta/ordinal_field_meta_popover'; import { CategoricalFieldMetaPopover } from '../components/field_meta/categorical_field_meta_popover'; @@ -28,6 +30,7 @@ import { IField } from '../../../fields/field'; import { IVectorLayer } from '../../../layers/vector_layer/vector_layer'; import { IJoin } from '../../../joins/join'; import { IVectorStyle } from '../vector_style'; +import { getComputedFieldName } from '../style_util'; export interface IDynamicStyleProperty extends IStyleProperty { getFieldMetaOptions(): FieldMetaOptions; @@ -46,9 +49,16 @@ export interface IDynamicStyleProperty extends IStyleProperty { pluckOrdinalStyleMetaFromFeatures(features: Feature[]): RangeFieldMeta | null; pluckCategoricalStyleMetaFromFeatures(features: Feature[]): CategoryFieldMeta | null; getValueSuggestions(query: string): Promise; -} -export type FieldFormatter = (value: string | number | undefined) => string | number; + // Returns the name that should be used for accessing the data from the mb-style rule + // Depending on + // - whether the field is used for labeling, icon-orientation, or other properties (color, size, ...), `feature-state` and or `get` is used + // - whether the field was run through a field-formatter, a new dynamic field is created with the formatted-value + // The combination of both will inform what field-name (e.g. the "raw" field name from the properties, the "computed field-name" for an on-the-fly created property (e.g. for feature-state or field-formatting). + // todo: There is an existing limitation to .mvt backed sources, where the field-formatters are not applied. Here, the raw-data needs to be accessed. + getMbPropertyName(): string; + getMbPropertyValue(value: RawValue): RawValue; +} export class DynamicStyleProperty extends AbstractStyleProperty @@ -313,7 +323,7 @@ export class DynamicStyleProperty }; } - formatField(value: string | number | undefined): string | number { + formatField(value: RawValue): string | number { if (this.getField()) { const fieldName = this.getFieldName(); const fieldFormatter = this._getFieldFormatter(fieldName); @@ -345,4 +355,43 @@ export class DynamicStyleProperty /> ); } + + getMbPropertyName() { + if (!this._field) { + return ''; + } + + let targetName; + if (this.supportsMbFeatureState()) { + // Base case for any properties that can support feature-state (e.g. color, size, ...) + // They just re-use the original property-name + targetName = this._field.getName(); + } else { + if (this._field.canReadFromGeoJson() && this._field.supportsAutoDomain()) { + // Geojson-sources can support rewrite + // e.g. field-formatters will create duplicate field + targetName = getComputedFieldName(this.getStyleName(), this._field.getName()); + } else { + // Non-geojson sources (e.g. 3rd party mvt or ES-source as mvt) + targetName = this._field.getName(); + } + } + return targetName; + } + + getMbPropertyValue(rawValue: RawValue): RawValue { + // Maps only uses feature-state for numerical values. + // `supportsMbFeatureState` will only return true when the mb-style rule does a feature-state lookup on a numerical value + // Calling `isOrdinal` would be equivalent. + return this.supportsMbFeatureState() ? getNumericalMbFeatureStateValue(rawValue) : rawValue; + } +} + +export function getNumericalMbFeatureStateValue(value: RawValue) { + if (typeof value !== 'string') { + return value; + } + + const valueAsFloat = parseFloat(value); + return isNaN(valueAsFloat) ? null : valueAsFloat; } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.ts b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.ts index d55a6e1cfb444..ec79d71eb7587 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_text_property.ts @@ -6,18 +6,18 @@ import { Map as MbMap } from 'mapbox-gl'; import { DynamicStyleProperty } from './dynamic_style_property'; -import { getComputedFieldName } from '../style_util'; import { LabelDynamicOptions } from '../../../../../common/descriptor_types'; +import { RawValue } from '../../../../../common/constants'; export class DynamicTextProperty extends DynamicStyleProperty { syncTextFieldWithMb(mbLayerId: string, mbMap: MbMap) { if (this._field && this._field.isValid()) { - // Fields that support auto-domain are normalized with a field-formatter and stored into a computed-field - // Otherwise, the raw value is just carried over and no computed field is created. - const targetName = this._field.supportsAutoDomain() - ? getComputedFieldName(this._styleName, this.getFieldName()) - : this._field.getName(); - mbMap.setLayoutProperty(mbLayerId, 'text-field', ['coalesce', ['get', targetName], '']); + const targetName = this.getMbPropertyName(); + mbMap.setLayoutProperty(mbLayerId, 'text-field', [ + 'coalesce', + [this.getMbLookupFunction(), targetName], + '', + ]); } else { mbMap.setLayoutProperty(mbLayerId, 'text-field', null); } @@ -34,4 +34,8 @@ export class DynamicTextProperty extends DynamicStyleProperty { isDynamic(): boolean; isComplete(): boolean; - formatField(value: string | number | undefined): string | number; + formatField(value: RawValue): string | number; getStyleName(): VECTOR_STYLES; getOptions(): T; renderLegendDetailRow(legendProps: LegendProps): ReactElement | null; @@ -53,9 +53,14 @@ export class AbstractStyleProperty implements IStyleProperty { return true; } - formatField(value: string | number | undefined): string | number { - // eslint-disable-next-line eqeqeq - return value == undefined ? '' : value; + formatField(value: RawValue): string | number { + if (typeof value === 'undefined' || value === null) { + return ''; + } else if (typeof value === 'boolean') { + return value.toString(); + } else { + return value; + } } getStyleName(): VECTOR_STYLES { diff --git a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx index 956522524a2eb..1244c53afe9a6 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx @@ -29,7 +29,7 @@ import { import { StyleMeta } from './style_meta'; import { VectorIcon } from './components/legend/vector_icon'; import { VectorStyleLegend } from './components/legend/vector_style_legend'; -import { getComputedFieldName, isOnlySingleFeatureType } from './style_util'; +import { isOnlySingleFeatureType } from './style_util'; import { StaticStyleProperty } from './properties/static_style_property'; import { DynamicStyleProperty } from './properties/dynamic_style_property'; import { DynamicSizeProperty } from './properties/dynamic_size_property'; @@ -82,11 +82,6 @@ const POINTS = [GEO_JSON_TYPE.POINT, GEO_JSON_TYPE.MULTI_POINT]; const LINES = [GEO_JSON_TYPE.LINE_STRING, GEO_JSON_TYPE.MULTI_LINE_STRING]; const POLYGONS = [GEO_JSON_TYPE.POLYGON, GEO_JSON_TYPE.MULTI_POLYGON]; -function getNumericalMbFeatureStateValue(value: string) { - const valueAsFloat = parseFloat(value); - return isNaN(valueAsFloat) ? null : valueAsFloat; -} - export interface IVectorStyle extends IStyle { getAllStyleProperties(): Array>; getDynamicPropertiesArray(): Array>; @@ -618,21 +613,17 @@ export class VectorStyle implements IVectorStyle { for (let j = 0; j < dynamicStyleProps.length; j++) { const dynamicStyleProp = dynamicStyleProps[j]; - const name = dynamicStyleProp.getFieldName(); - const computedName = getComputedFieldName(dynamicStyleProp.getStyleName(), name); - const rawValue = feature.properties ? feature.properties[name] : undefined; + const targetMbName = dynamicStyleProp.getMbPropertyName(); + const rawValue = feature.properties + ? feature.properties[dynamicStyleProp.getFieldName()] + : undefined; + const targetMbValue = dynamicStyleProp.getMbPropertyValue(rawValue); if (dynamicStyleProp.supportsMbFeatureState()) { - tmpFeatureState[name] = getNumericalMbFeatureStateValue(rawValue); // the same value will be potentially overridden multiple times, if the name remains identical + tmpFeatureState[targetMbName] = targetMbValue; // the same value will be potentially overridden multiple times, if the name remains identical } else { - // in practice, a new system property will only be created for: - // - label text: this requires the value to be formatted first. - // - icon orientation: this is a lay-out property which do not support feature-state (but we're still coercing to a number) - - const formattedValue = dynamicStyleProp.isOrdinal() - ? getNumericalMbFeatureStateValue(rawValue) - : dynamicStyleProp.formatField(rawValue); - - if (feature.properties) feature.properties[computedName] = formattedValue; + if (feature.properties) { + feature.properties[targetMbName] = targetMbValue; + } } } tmpFeatureIdentifier.source = mbSourceId;