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

[Maps] Add DynamicStyleProperty#getMbPropertyName and DynamicStyleProperty#getMbPropertyValue #77366

Merged
merged 15 commits into from
Sep 15, 2020
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
thomasneirynck marked this conversation as resolved.
Show resolved Hide resolved
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;
thomasneirynck marked this conversation as resolved.
Show resolved Hide resolved
}
}

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