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
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,32 @@
*/

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,
RawValue,
} from './dynamic_style_property';
import { OrientationDynamicOptions } from '../../../../../common/descriptor_types';

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 @@ -28,6 +28,9 @@ 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 type RawValue = string | number | undefined | null;

export interface IDynamicStyleProperty<T> extends IStyleProperty<T> {
getFieldMetaOptions(): FieldMetaOptions;
Expand All @@ -46,6 +49,15 @@ export interface IDynamicStyleProperty<T> extends IStyleProperty<T> {
pluckOrdinalStyleMetaFromFeatures(features: Feature[]): RangeFieldMeta | null;
pluckCategoricalStyleMetaFromFeatures(features: Feature[]): CategoryFieldMeta | null;
getValueSuggestions(query: string): Promise<string[]>;

// 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 type FieldFormatter = (value: string | number | undefined) => string | number;
Expand Down Expand Up @@ -313,11 +325,11 @@ export class DynamicStyleProperty<T>
};
}

formatField(value: string | number | undefined): string | number {
formatField(value: string | number | undefined | null): string | number {
thomasneirynck marked this conversation as resolved.
Show resolved Hide resolved
if (this.getField()) {
const fieldName = this.getFieldName();
const fieldFormatter = this._getFieldFormatter(fieldName);
return fieldFormatter ? fieldFormatter(value) : super.formatField(value);
return fieldFormatter && value !== null ? fieldFormatter(value) : super.formatField(value);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there a special check for just null? Is this still needed?

} else {
return super.formatField(value);
}
Expand Down Expand Up @@ -345,4 +357,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: string | number | null | undefined) {
thomasneirynck marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -5,19 +5,18 @@
*/

import { Map as MbMap } from 'mapbox-gl';
import { DynamicStyleProperty } from './dynamic_style_property';
import { getComputedFieldName } from '../style_util';
import { DynamicStyleProperty, RawValue } from './dynamic_style_property';
import { LabelDynamicOptions } from '../../../../../common/descriptor_types';

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 +33,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 @@ -20,7 +20,7 @@ export type LegendProps = {
export interface IStyleProperty<T> {
isDynamic(): boolean;
isComplete(): boolean;
formatField(value: string | number | undefined): string | number;
formatField(value: string | number | undefined | null): string | number;
getStyleName(): VECTOR_STYLES;
getOptions(): T;
renderLegendDetailRow(legendProps: LegendProps): ReactElement<any> | null;
Expand Down Expand Up @@ -53,9 +53,8 @@ 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: string | number | undefined | null): string | number {
return typeof value === 'undefined' || value === null ? '' : 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