[Maps] Move field-meta implementation to style property (#52828)

* improve clarity

* rename for clarity

* feedback

* add comment

* remove debug statements
This commit is contained in:
Thomas Neirynck 2019-12-12 18:19:06 -05:00 committed by Nathan Reese
parent 4765ed0fa1
commit fe44595523
6 changed files with 83 additions and 72 deletions

View file

@ -8,7 +8,6 @@ import _ from 'lodash';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { rangeShape } from '../style_option_shapes';
import { getVectorStyleLabel } from '../get_vector_style_label';
import { StyleLegendRow } from '../../../components/style_legend_row';
@ -25,7 +24,7 @@ export class StylePropertyLegendRow extends Component {
}
render() {
const { range, style } = this.props;
const { meta: range, style } = this.props;
const min = this._formatValue(_.get(range, 'min', EMPTY_VALUE));
const minLabel = this.props.style.isFieldMetaEnabled() && range && range.isMinOutsideStdRange ? `< ${min}` : min;
@ -48,6 +47,6 @@ export class StylePropertyLegendRow extends Component {
StylePropertyLegendRow.propTypes = {
label: PropTypes.string,
fieldFormatter: PropTypes.func,
range: rangeShape,
meta: PropTypes.object,
style: PropTypes.object
};

View file

@ -35,7 +35,7 @@ export class VectorStyleLegend extends Component {
const rowDescriptors = rows.map(row => {
return {
label: row.label,
range: row.range,
range: row.meta,
styleOptions: row.style.getOptions(),
};
});

View file

@ -35,8 +35,3 @@ export const dynamicSizeShape = PropTypes.shape({
maxSize: PropTypes.number.isRequired,
field: fieldShape,
});
export const rangeShape = PropTypes.shape({
min: PropTypes.number.isRequired,
max: PropTypes.number.isRequired,
});

View file

@ -60,4 +60,51 @@ export class DynamicStyleProperty extends AbstractStyleProperty {
getFieldMetaOptions() {
return _.get(this.getOptions(), 'fieldMetaOptions', {});
}
pluckStyleMetaFromFeatures(features) {
const name = this.getField().getName();
let min = Infinity;
let max = -Infinity;
for (let i = 0; i < features.length; i++) {
const feature = features[i];
const newValue = parseFloat(feature.properties[name]);
if (!isNaN(newValue)) {
min = Math.min(min, newValue);
max = Math.max(max, newValue);
}
}
return (min === Infinity || max === -Infinity) ? null : ({
min: min,
max: max,
delta: max - min
});
}
pluckStyleMetaFromFieldMetaData(fieldMetaData) {
const realFieldName = this._field.getESDocFieldName ? this._field.getESDocFieldName() : this._field.getName();
const stats = fieldMetaData[realFieldName];
if (!stats) {
return null;
}
const sigma = _.get(this.getFieldMetaOptions(), 'sigma', DEFAULT_SIGMA);
const stdLowerBounds = stats.avg - (stats.std_deviation * sigma);
const stdUpperBounds = stats.avg + (stats.std_deviation * sigma);
const min = Math.max(stats.min, stdLowerBounds);
const max = Math.min(stats.max, stdUpperBounds);
return {
min,
max,
delta: max - min,
isMinOutsideStdRange: stats.min < stdLowerBounds,
isMaxOutsideStdRange: stats.max > stdUpperBounds,
};
}
}

View file

@ -180,24 +180,17 @@ export class VectorStyle extends AbstractStyle {
}
async pluckStyleMetaFromSourceDataRequest(sourceDataRequest) {
const features = _.get(sourceDataRequest.getData(), 'features', []);
if (features.length === 0) {
return {};
}
const scaledFields = this.getDynamicPropertiesArray()
.map(styleProperty => {
return {
name: styleProperty.getField().getName(),
min: Infinity,
max: -Infinity
};
});
const dynamicProperties = this.getDynamicPropertiesArray();
const supportedFeatures = await this._source.getSupportedShapeTypes();
const isSingleFeatureType = supportedFeatures.length === 1;
if (scaledFields.length === 0 && isSingleFeatureType) {
if (dynamicProperties.length === 0 && isSingleFeatureType) {
// no meta data to pull from source data request.
return {};
}
@ -216,15 +209,6 @@ export class VectorStyle extends AbstractStyle {
if (!hasPolygons && POLYGONS.includes(feature.geometry.type)) {
hasPolygons = true;
}
for (let j = 0; j < scaledFields.length; j++) {
const scaledField = scaledFields[j];
const newValue = parseFloat(feature.properties[scaledField.name]);
if (!isNaN(newValue)) {
scaledField.min = Math.min(scaledField.min, newValue);
scaledField.max = Math.max(scaledField.max, newValue);
}
}
}
const featuresMeta = {
@ -235,13 +219,11 @@ export class VectorStyle extends AbstractStyle {
}
};
scaledFields.forEach(({ min, max, name }) => {
if (min !== Infinity && max !== -Infinity) {
featuresMeta[name] = {
min,
max,
delta: max - min,
};
dynamicProperties.forEach(dynamicProperty => {
const styleMeta = dynamicProperty.pluckStyleMetaFromFeatures(features);
if (styleMeta) {
const name = dynamicProperty.getField().getName();
featuresMeta[name] = styleMeta;
}
});
@ -290,13 +272,15 @@ export class VectorStyle extends AbstractStyle {
return this._isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.POLYGON);
}
_getFieldRange = (fieldName) => {
const fieldRangeFromLocalFeatures = _.get(this._descriptor, ['__styleMeta', fieldName]);
_getFieldMeta = (fieldName) => {
const fieldMetaFromLocalFeatures = _.get(this._descriptor, ['__styleMeta', fieldName]);
const dynamicProps = this.getDynamicPropertiesArray();
const dynamicProp = dynamicProps.find(dynamicProp => { return fieldName === dynamicProp.getField().getName(); });
if (!dynamicProp || !dynamicProp.isFieldMetaEnabled()) {
return fieldRangeFromLocalFeatures;
return fieldMetaFromLocalFeatures;
}
let dataRequestId;
@ -313,34 +297,19 @@ export class VectorStyle extends AbstractStyle {
}
if (!dataRequestId) {
return fieldRangeFromLocalFeatures;
return fieldMetaFromLocalFeatures;
}
const styleMetaDataRequest = this._layer._findDataRequestForSource(dataRequestId);
if (!styleMetaDataRequest || !styleMetaDataRequest.hasData()) {
return fieldRangeFromLocalFeatures;
return fieldMetaFromLocalFeatures;
}
const data = styleMetaDataRequest.getData();
const field = dynamicProp.getField();
const realFieldName = field.getESDocFieldName ? field.getESDocFieldName() : field.getName();
const stats = data[realFieldName];
if (!stats) {
return fieldRangeFromLocalFeatures;
}
const fieldMeta = dynamicProp.pluckStyleMetaFromFieldMetaData(data);
return fieldMeta ? fieldMeta : fieldMetaFromLocalFeatures;
const sigma = _.get(dynamicProp.getFieldMetaOptions(), 'sigma', 3);
const stdLowerBounds = stats.avg - (stats.std_deviation * sigma);
const stdUpperBounds = stats.avg + (stats.std_deviation * sigma);
const min = Math.max(stats.min, stdLowerBounds);
const max = Math.min(stats.max, stdUpperBounds);
return {
min,
max,
delta: max - min,
isMinOutsideStdRange: stats.min < stdLowerBounds,
isMaxOutsideStdRange: stats.max > stdUpperBounds,
};
}
_getStyleMeta = () => {
@ -392,7 +361,7 @@ export class VectorStyle extends AbstractStyle {
return {
label: await style.getField().getLabel(),
fieldFormatter: await this._source.getFieldFormatter(style.getField().getName()),
range: this._getFieldRange(style.getField().getName()),
meta: this._getFieldMeta(style.getField().getName()),
style,
};
});
@ -402,7 +371,7 @@ export class VectorStyle extends AbstractStyle {
return <VectorStyleLegend loadRows={loadRows} />;
}
_getStyleFields() {
_getFeatureStyleParams() {
return this.getDynamicPropertiesArray()
.map(styleProperty => {
@ -424,7 +393,7 @@ export class VectorStyle extends AbstractStyle {
supportsFeatureState,
isScaled,
name: field.getName(),
range: this._getFieldRange(field.getName()),
meta: this._getFieldMeta(field.getName()),
computedName: getComputedFieldName(styleProperty.getStyleName(), field.getName()),
};
});
@ -443,14 +412,14 @@ export class VectorStyle extends AbstractStyle {
}
}
setFeatureState(featureCollection, mbMap, sourceId) {
setFeatureStateAndStyleProps(featureCollection, mbMap, mbSourceId) {
if (!featureCollection) {
return;
}
const styleFields = this._getStyleFields();
if (styleFields.length === 0) {
const featureStateParams = this._getFeatureStyleParams();
if (featureStateParams.length === 0) {
return;
}
@ -464,8 +433,8 @@ export class VectorStyle extends AbstractStyle {
for (let i = 0; i < featureCollection.features.length; i++) {
const feature = featureCollection.features[i];
for (let j = 0; j < styleFields.length; j++) {
const { supportsFeatureState, isScaled, name, range, computedName } = styleFields[j];
for (let j = 0; j < featureStateParams.length; j++) {
const { supportsFeatureState, isScaled, name, meta: range, computedName } = featureStateParams[j];
const value = parseFloat(feature.properties[name]);
let styleValue;
if (isScaled) {
@ -484,15 +453,16 @@ export class VectorStyle extends AbstractStyle {
feature.properties[computedName] = styleValue;
}
}
tmpFeatureIdentifier.source = sourceId;
tmpFeatureIdentifier.source = mbSourceId;
tmpFeatureIdentifier.id = feature.id;
mbMap.setFeatureState(tmpFeatureIdentifier, tmpFeatureState);
}
const hasGeoJsonProperties = styleFields.some(({ supportsFeatureState }) => {
return !supportsFeatureState;
});
return hasGeoJsonProperties;
//returns boolean indicating if styles do not support feature-state and some values are stored in geojson properties
//this return-value is used in an optimization for style-updates with mapbox-gl.
//`true` indicates the entire data needs to reset on the source (otherwise the style-rules will not be reapplied)
//`false` indicates the data does not need to be reset on the store, because styles are re-evaluated if they use featureState
return featureStateParams.some(({ supportsFeatureState }) => !supportsFeatureState);
}
arePointsSymbolizedAsCircles() {

View file

@ -509,7 +509,7 @@ export class VectorLayer extends AbstractLayer {
// "feature-state" data expressions are not supported with layout properties.
// To work around this limitation,
// scaled layout properties (like icon-size) must fall back to geojson property values :(
const hasGeoJsonProperties = this._style.setFeatureState(featureCollection, mbMap, this.getId());
const hasGeoJsonProperties = this._style.setFeatureStateAndStyleProps(featureCollection, mbMap, this.getId());
if (featureCollection !== featureCollectionOnMap || hasGeoJsonProperties) {
mbGeoJSONSource.setData(featureCollection);
}