Skip to content

Commit

Permalink
[Maps] Add DynamicStyleProperty#getMbPropertyName and DynamicStylePro…
Browse files Browse the repository at this point in the history
…perty#getMbPropertyValue (elastic#77366)
  • Loading branch information
thomasneirynck committed Sep 15, 2020
1 parent d326247 commit f0783c3
Show file tree
Hide file tree
Showing 11 changed files with 110 additions and 55 deletions.
4 changes: 4 additions & 0 deletions x-pack/plugins/maps/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
4 changes: 1 addition & 3 deletions x-pack/plugins/maps/public/classes/sources/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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<string>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
}
);
};
Expand Down Expand Up @@ -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';
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -33,7 +33,7 @@ const makeProperty = (options: Partial<IconDynamicOptions>, field: IField = mock
field,
mockVectorLayer,
() => {
return (value: string | number | undefined) => value + '_format';
return (value: RawValue) => value + '_format';
}
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<OrientationDynamicOptions> {
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<T> extends IStyleProperty<T> {
getFieldMetaOptions(): FieldMetaOptions;
Expand All @@ -46,9 +49,16 @@ export interface IDynamicStyleProperty<T> extends IStyleProperty<T> {
pluckOrdinalStyleMetaFromFeatures(features: Feature[]): RangeFieldMeta | null;
pluckCategoricalStyleMetaFromFeatures(features: Feature[]): CategoryFieldMeta | null;
getValueSuggestions(query: string): Promise<string[]>;
}

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<T>
extends AbstractStyleProperty<T>
Expand Down Expand Up @@ -313,7 +323,7 @@ export class DynamicStyleProperty<T>
};
}

formatField(value: string | number | undefined): string | number {
formatField(value: RawValue): string | number {
if (this.getField()) {
const fieldName = this.getFieldName();
const fieldFormatter = this._getFieldFormatter(fieldName);
Expand Down Expand Up @@ -345,4 +355,43 @@ export class DynamicStyleProperty<T>
/>
);
}

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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<LabelDynamicOptions> {
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);
}
Expand All @@ -34,4 +34,8 @@ export class DynamicTextProperty extends DynamicStyleProperty<LabelDynamicOption
supportsMbFeatureState() {
return false;
}

getMbPropertyValue(rawValue: RawValue): RawValue {
return this.formatField(rawValue);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ReactElement } from 'react';
// @ts-ignore
import { getVectorStyleLabel } from '../components/get_vector_style_label';
import { FieldMetaOptions } from '../../../../../common/descriptor_types';
import { VECTOR_STYLES } from '../../../../../common/constants';
import { RawValue, VECTOR_STYLES } from '../../../../../common/constants';

export type LegendProps = {
isPointsOnly: boolean;
Expand All @@ -20,7 +20,7 @@ export type LegendProps = {
export interface IStyleProperty<T> {
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<any> | null;
Expand Down Expand Up @@ -53,9 +53,14 @@ export class AbstractStyleProperty<T> implements IStyleProperty<T> {
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 {
Expand Down
29 changes: 10 additions & 19 deletions x-pack/plugins/maps/public/classes/styles/vector/vector_style.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<IStyleProperty<StylePropertyOptions>>;
getDynamicPropertiesArray(): Array<IDynamicStyleProperty<DynamicStylePropertyOptions>>;
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit f0783c3

Please sign in to comment.