[Maps] Only show vector style properties used by source in editor and show line icon in legend (#36280)

* supported features POC

* render point, line and polygon properties

* cleanup from merge with master

* line icon

* display button group for feature type styling

* fix vector_style tests

* remove unused function

* rename getSupportedFeatures to getSupportedShapeTypes and VECTOR_FEATURE_TYPE to VECTOR_SHAPE_TYPES

* review feedback

* display feature styles when user selects feature type

* create constants for geo_json types

* change line icon to line instead of rect

* add help function to avoid duplicate logic for isPointsOnly, isLineOnly, and isPolygonOnly

* add unit tests for _checkIfOnlyFeatureType
This commit is contained in:
Nathan Reese 2019-05-18 12:19:32 -06:00 committed by GitHub
parent 2347106fe6
commit 8202a1da61
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 529 additions and 116 deletions

View file

@ -33,3 +33,13 @@ export const ES_GEO_FIELD_TYPE = {
GEO_POINT: 'geo_point',
GEO_SHAPE: 'geo_shape'
};
export const GEO_JSON_TYPE = {
POINT: 'Point',
MULTI_POINT: 'MultiPoint',
LINE_STRING: 'LineString',
MULTI_LINE_STRING: 'MultiLineString',
POLYGON: 'Polygon',
MULTI_POLYGON: 'MultiPolygon',
GEOMETRY_COLLECTION: 'GeometryCollection',
};

View file

@ -658,7 +658,7 @@ export function updateLayerStyle(layerId, styleDescriptor) {
}
export function updateStyleMeta(layerId) {
return (dispatch, getState) => {
return async (dispatch, getState) => {
const layer = getLayerById(layerId, getState());
if (!layer) {
return;
@ -668,10 +668,11 @@ export function updateStyleMeta(layerId) {
if (!style || !sourceDataRequest) {
return;
}
const styleMeta = await style.pluckStyleMetaFromSourceDataRequest(sourceDataRequest);
dispatch({
type: SET_LAYER_STYLE_META,
layerId,
styleMeta: style.pluckStyleMetaFromSourceDataRequest(sourceDataRequest),
styleMeta,
});
};
}

View file

@ -8,7 +8,7 @@ import _ from 'lodash';
import { i18n } from '@kbn/i18n';
import { parse } from 'wellknown';
import { decodeGeoHash } from 'ui/utils/decode_geo_hash';
import { DECIMAL_DEGREES_PRECISION, ES_GEO_FIELD_TYPE } from '../common/constants';
import { DECIMAL_DEGREES_PRECISION, ES_GEO_FIELD_TYPE, GEO_JSON_TYPE } from '../common/constants';
/**
* Converts Elasticsearch search results into GeoJson FeatureCollection
@ -60,7 +60,7 @@ export function hitsToGeoJson(hits, flattenHit, geoFieldName, geoFieldType) {
function pointGeometryFactory(lat, lon) {
return {
type: 'Point',
type: GEO_JSON_TYPE.POINT,
coordinates: [lon, lat]
};
}
@ -131,25 +131,25 @@ export function convertESShapeToGeojsonGeometry(value) {
// The below is the correction in-place.
switch (value.type) {
case 'point':
geoJson.type = 'Point';
geoJson.type = GEO_JSON_TYPE.POINT;
break;
case 'linestring':
geoJson.type = 'LineString';
geoJson.type = GEO_JSON_TYPE.LINE_STRING;
break;
case 'polygon':
geoJson.type = 'Polygon';
geoJson.type = GEO_JSON_TYPE.POLYGON;
break;
case 'multipoint':
geoJson.type = 'MultiPoint';
geoJson.type = GEO_JSON_TYPE.MULTI_POINT;
break;
case 'multilinestring':
geoJson.type = 'MultiLineString';
geoJson.type = GEO_JSON_TYPE.MULTI_LINE_STRING;
break;
case 'multipolygon':
geoJson.type = 'MultiPolygon';
geoJson.type = GEO_JSON_TYPE.MULTI_POLYGON;
break;
case 'geometrycollection':
geoJson.type = 'GeometryCollection';
geoJson.type = GEO_JSON_TYPE.GEOMETRY_COLLECTION;
break;
case 'envelope':
case 'circle':

View file

@ -19,7 +19,7 @@ import { copyPersistentState, TRACKED_LAYER_DESCRIPTOR } from '../store/util';
function createLayerInstance(layerDescriptor, inspectorAdapters) {
const source = createSourceInstance(layerDescriptor.sourceDescriptor, inspectorAdapters);
const style = createStyleInstance(layerDescriptor.style);
const style = createStyleInstance(layerDescriptor.style, source);
switch (layerDescriptor.type) {
case TileLayer.type:
return new TileLayer({ layerDescriptor, source, style });
@ -43,7 +43,7 @@ function createSourceInstance(sourceDescriptor, inspectorAdapters) {
}
function createStyleInstance(styleDescriptor) {
function createStyleInstance(styleDescriptor, source) {
if (!styleDescriptor || !styleDescriptor.type) {
return null;
@ -51,7 +51,7 @@ function createStyleInstance(styleDescriptor) {
switch (styleDescriptor.type) {
case VectorStyle.type:
return new VectorStyle(styleDescriptor);
return new VectorStyle(styleDescriptor, source);
case TileStyle.type:
return new TileStyle(styleDescriptor);
case HeatmapStyle.type:

View file

@ -54,3 +54,9 @@ export const FillableRectangle = ({ style }) => (
<rect width="15" height="15" x=".5" y=".5" style={style} rx="4"/>
</svg>
);
export const ColorableLine = ({ style }) => (
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<line x1="0" y1="6" x2="16" y2="6" style={style} />
</svg>
);

View file

@ -5,6 +5,7 @@
*/
import { AbstractVectorSource } from '../vector_source';
import { VECTOR_SHAPE_TYPES } from '../vector_feature_types';
import React from 'react';
import { GIS_API_PATH, EMS_FILE } from '../../../../../common/constants';
import { getEmsVectorFilesMeta } from '../../../../meta';
@ -116,4 +117,8 @@ export class EMSFileSource extends AbstractVectorSource {
return true;
}
async getSupportedShapeTypes() {
return [VECTOR_SHAPE_TYPES.POLYGON];
}
}

View file

@ -7,6 +7,7 @@
import React from 'react';
import uuid from 'uuid/v4';
import { VECTOR_SHAPE_TYPES } from '../vector_feature_types';
import { AbstractESSource } from '../es_source';
import { HeatmapLayer } from '../../heatmap_layer';
import { VectorLayer } from '../../vector_layer';
@ -302,11 +303,11 @@ export class ESGeoGridSource extends AbstractESSource {
}
const layerDescriptor = this._createDefaultLayerDescriptor(options);
const style = new VectorStyle(layerDescriptor.style);
const style = new VectorStyle(layerDescriptor.style, this);
return new VectorLayer({
layerDescriptor: layerDescriptor,
source: this,
style: style
style
});
}
@ -317,4 +318,12 @@ export class ESGeoGridSource extends AbstractESSource {
async filterAndFormatPropertiesToHtml(properties) {
return await this.filterAndFormatPropertiesToHtmlForMetricFields(properties);
}
async getSupportedShapeTypes() {
if (this._descriptor.requestType === RENDER_AS.GRID) {
return [VECTOR_SHAPE_TYPES.POLYGON];
}
return [VECTOR_SHAPE_TYPES.POINT];
}
}

View file

@ -8,11 +8,12 @@ import _ from 'lodash';
import React from 'react';
import uuid from 'uuid/v4';
import { VECTOR_SHAPE_TYPES } from '../vector_feature_types';
import { AbstractESSource } from '../es_source';
import { hitsToGeoJson } from '../../../../elasticsearch_geo_utils';
import { CreateSourceEditor } from './create_source_editor';
import { UpdateSourceEditor } from './update_source_editor';
import { ES_SEARCH } from '../../../../../common/constants';
import { ES_SEARCH, ES_GEO_FIELD_TYPE } from '../../../../../common/constants';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../../common/i18n_getters';
import { ESTooltipProperty } from '../../tooltips/es_tooltip_property';
@ -197,6 +198,26 @@ export class ESSearchSource extends AbstractESSource {
});
}
async getSupportedShapeTypes() {
let geoFieldType;
try {
const geoField = this._getGeoField();
geoFieldType = geoField.type;
} catch(error) {
// ignore exeception
}
if (geoFieldType === ES_GEO_FIELD_TYPE.GEO_POINT) {
return [VECTOR_SHAPE_TYPES.POINT];
}
return [
VECTOR_SHAPE_TYPES.POINT,
VECTOR_SHAPE_TYPES.LINE,
VECTOR_SHAPE_TYPES.POLYGON
];
}
getSourceTooltipContent(sourceDataRequest) {
const featureCollection = sourceDataRequest ? sourceDataRequest.getData() : null;
const meta = sourceDataRequest ? sourceDataRequest.getMeta() : {};

View file

@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export const VECTOR_SHAPE_TYPES = {
POINT: 'POINT',
LINE: 'LINE',
POLYGON: 'POLYGON'
};

View file

@ -12,6 +12,7 @@ import { AbstractSource } from './source';
import * as topojson from 'topojson-client';
import _ from 'lodash';
import { i18n } from '@kbn/i18n';
import { VECTOR_SHAPE_TYPES } from './vector_feature_types';
export class AbstractVectorSource extends AbstractSource {
@ -58,11 +59,11 @@ export class AbstractVectorSource extends AbstractSource {
createDefaultLayer(options, mapColors) {
const layerDescriptor = this._createDefaultLayerDescriptor(options, mapColors);
const style = new VectorStyle(layerDescriptor.style);
const style = new VectorStyle(layerDescriptor.style, this);
return new VectorLayer({
layerDescriptor: layerDescriptor,
source: this,
style: style
style
});
}
@ -115,6 +116,14 @@ export class AbstractVectorSource extends AbstractSource {
return true;
}
async getSupportedShapeTypes() {
return [
VECTOR_SHAPE_TYPES.POINT,
VECTOR_SHAPE_TYPES.LINE,
VECTOR_SHAPE_TYPES.POLYGON
];
}
getSourceTooltipContent(/* sourceDataRequest */) {
return null;
}

View file

@ -12,7 +12,7 @@ export class AbstractStyle {
};
}
pluckStyleMetaFromSourceDataRequest(/* sourceDataRequest */) {
async pluckStyleMetaFromSourceDataRequest(/* sourceDataRequest */) {
return {};
}

View file

@ -4,24 +4,66 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { dynamicColorShape, staticColorShape } from '../style_option_shapes';
import { FillableCircle, FillableRectangle } from '../../../../../icons/additional_layer_icons';
import { ColorableLine, FillableCircle, FillableRectangle } from '../../../../../icons/additional_layer_icons';
import { VectorStyle } from '../../../vector_style';
import { getColorRampCenterColor } from '../../../../../utils/color_utils';
export function VectorIcon({ fillColor, lineColor, isPointsOnly }) {
const style = {
stroke: extractColorFromStyleProperty(lineColor, 'none'),
strokeWidth: '1px',
fill: extractColorFromStyleProperty(fillColor, 'grey'),
};
export class VectorIcon extends Component {
return isPointsOnly
? <FillableCircle style={style}/>
: <FillableRectangle style={style}/>;
state = {
isInitialized: false
}
componentDidMount() {
this._isMounted = true;
this._init();
}
componentWillUnmount() {
this._isMounted = false;
}
async _init() {
const isPointsOnly = await this.props.loadIsPointsOnly();
const isLinesOnly = await this.props.loadIsLinesOnly();
if (this._isMounted) {
this.setState({
isInitialized: true,
isPointsOnly,
isLinesOnly,
});
}
}
render() {
if (!this.state.isInitialized) {
return null;
}
if (this.state.isLinesOnly) {
const style = {
stroke: extractColorFromStyleProperty(this.props.lineColor, 'grey'),
strokeWidth: '4px',
};
return (
<ColorableLine style={style}/>
);
}
const style = {
stroke: extractColorFromStyleProperty(this.props.lineColor, 'none'),
strokeWidth: '1px',
fill: extractColorFromStyleProperty(this.props.fillColor, 'grey'),
};
return this.state.isPointsOnly
? <FillableCircle style={style}/>
: <FillableRectangle style={style}/>;
}
}
function extractColorFromStyleProperty(colorStyleProperty, defaultColor) {
@ -48,5 +90,6 @@ const colorStylePropertyShape = PropTypes.shape({
VectorIcon.propTypes = {
fillColor: colorStylePropertyShape,
lineColor: colorStylePropertyShape,
isPointsOnly: PropTypes.bool,
loadIsPointsOnly: PropTypes.func.isRequired,
loadIsLinesOnly: PropTypes.func.isRequired,
};

View file

@ -10,14 +10,18 @@ import React, { Component, Fragment } from 'react';
import { VectorStyleColorEditor } from './color/vector_style_color_editor';
import { VectorStyleSizeEditor } from './size/vector_style_size_editor';
import { getDefaultDynamicProperties, getDefaultStaticProperties } from '../../vector_style_defaults';
import { VECTOR_SHAPE_TYPES } from '../../../sources/vector_feature_types';
import { i18n } from '@kbn/i18n';
import { EuiSpacer } from '@elastic/eui';
import { EuiSpacer, EuiButtonGroup } from '@elastic/eui';
export class VectorStyleEditor extends Component {
state = {
ordinalFields: [],
defaultDynamicProperties: getDefaultDynamicProperties(),
defaultStaticProperties: getDefaultStaticProperties()
defaultStaticProperties: getDefaultStaticProperties(),
supportedFeatures: undefined,
selectedFeatureType: undefined,
}
componentWillUnmount() {
@ -27,10 +31,12 @@ export class VectorStyleEditor extends Component {
componentDidMount() {
this._isMounted = true;
this._loadOrdinalFields();
this._loadSupportedFeatures();
}
componentDidUpdate() {
this._loadOrdinalFields();
this._loadSupportedFeatures();
}
async _loadOrdinalFields() {
@ -43,52 +49,200 @@ export class VectorStyleEditor extends Component {
}
}
render() {
async _loadSupportedFeatures() {
const supportedFeatures = await this.props.layer.getSource().getSupportedShapeTypes();
const isPointsOnly = await this.props.loadIsPointsOnly();
const isLinesOnly = await this.props.loadIsLinesOnly();
if (!this._isMounted) {
return;
}
if (_.isEqual(supportedFeatures, this.state.supportedFeatures)
&& isPointsOnly === this.state.isPointsOnly
&& isLinesOnly === this.state.isLinesOnly) {
return;
}
let selectedFeature = VECTOR_SHAPE_TYPES.POLYGON;
if (isPointsOnly) {
selectedFeature = VECTOR_SHAPE_TYPES.POINT;
} else if (isLinesOnly) {
selectedFeature = VECTOR_SHAPE_TYPES.LINE;
}
if (!_.isEqual(supportedFeatures, this.state.supportedFeatures) ||
selectedFeature !== this.state.selectedFeature) {
this.setState({
supportedFeatures,
selectedFeature,
isPointsOnly,
isLinesOnly,
});
}
}
_renderFillColor() {
return (
<VectorStyleColorEditor
styleProperty="fillColor"
handlePropertyChange={this.props.handlePropertyChange}
styleDescriptor={this.props.styleProperties.fillColor}
ordinalFields={this.state.ordinalFields}
defaultStaticStyleOptions={this.state.defaultStaticProperties.fillColor.options}
defaultDynamicStyleOptions={this.state.defaultDynamicProperties.fillColor.options}
/>
);
}
_renderLineColor() {
return (
<VectorStyleColorEditor
styleProperty="lineColor"
handlePropertyChange={this.props.handlePropertyChange}
styleDescriptor={this.props.styleProperties.lineColor}
ordinalFields={this.state.ordinalFields}
defaultStaticStyleOptions={this.state.defaultStaticProperties.lineColor.options}
defaultDynamicStyleOptions={this.state.defaultDynamicProperties.lineColor.options}
/>
);
}
_renderLineWidth() {
return (
<VectorStyleSizeEditor
styleProperty="lineWidth"
handlePropertyChange={this.props.handlePropertyChange}
styleDescriptor={this.props.styleProperties.lineWidth}
ordinalFields={this.state.ordinalFields}
defaultStaticStyleOptions={this.state.defaultStaticProperties.lineWidth.options}
defaultDynamicStyleOptions={this.state.defaultDynamicProperties.lineWidth.options}
/>
);
}
_renderSymbolSize() {
return (
<VectorStyleSizeEditor
styleProperty="iconSize"
handlePropertyChange={this.props.handlePropertyChange}
styleDescriptor={this.props.styleProperties.iconSize}
ordinalFields={this.state.ordinalFields}
defaultStaticStyleOptions={this.state.defaultStaticProperties.iconSize.options}
defaultDynamicStyleOptions={this.state.defaultDynamicProperties.iconSize.options}
/>
);
}
_renderPointProperties() {
return (
<Fragment>
{this._renderFillColor()}
<EuiSpacer size="m" />
<VectorStyleColorEditor
styleProperty="fillColor"
handlePropertyChange={this.props.handlePropertyChange}
styleDescriptor={this.props.styleProperties.fillColor}
ordinalFields={this.state.ordinalFields}
defaultStaticStyleOptions={this.state.defaultStaticProperties.fillColor.options}
defaultDynamicStyleOptions={this.state.defaultDynamicProperties.fillColor.options}
{this._renderLineColor()}
<EuiSpacer size="m" />
{this._renderLineWidth()}
<EuiSpacer size="m" />
{this._renderSymbolSize()}
</Fragment>
);
}
_renderLineProperties() {
return (
<Fragment>
{this._renderLineColor()}
<EuiSpacer size="m" />
{this._renderLineWidth()}
</Fragment>
);
}
_renderPolygonProperties() {
return (
<Fragment>
{this._renderFillColor()}
<EuiSpacer size="m" />
{this._renderLineColor()}
<EuiSpacer size="m" />
{this._renderLineWidth()}
</Fragment>
);
}
_handleSelectedFeatureChange = selectedFeature => {
this.setState({ selectedFeature });
}
render() {
const {
supportedFeatures,
selectedFeature,
} = this.state;
if (!supportedFeatures) {
return null;
}
if (supportedFeatures.length === 1) {
switch (supportedFeatures[0]) {
case VECTOR_SHAPE_TYPES.POINT:
return this._renderPointProperties();
case VECTOR_SHAPE_TYPES.LINE:
return this._renderLineProperties();
case VECTOR_SHAPE_TYPES.POLYGON:
return this._renderPolygonProperties();
}
}
const featureButtons = [
{
id: VECTOR_SHAPE_TYPES.POINT,
label: i18n.translate('xpack.maps.vectorStyleEditor.pointLabel', {
defaultMessage: 'Points'
})
},
{
id: VECTOR_SHAPE_TYPES.LINE,
label: i18n.translate('xpack.maps.vectorStyleEditor.lineLabel', {
defaultMessage: 'Lines'
})
},
{
id: VECTOR_SHAPE_TYPES.POLYGON,
label: i18n.translate('xpack.maps.vectorStyleEditor.polygonLabel', {
defaultMessage: 'Polygons'
})
}
];
let styleProperties = this._renderPolygonProperties();
if (selectedFeature === VECTOR_SHAPE_TYPES.LINE) {
styleProperties = this._renderLineProperties();
} else if (selectedFeature === VECTOR_SHAPE_TYPES.POINT) {
styleProperties = this._renderPointProperties();
}
return (
<Fragment>
<EuiButtonGroup
legend={i18n.translate('xpack.maps.vectorStyleEditor.featureTypeButtonGroupLegend', {
defaultMessage: 'vector feature button group'
})}
options={featureButtons}
idSelected={selectedFeature}
onChange={this._handleSelectedFeatureChange}
/>
<EuiSpacer size="m" />
<VectorStyleColorEditor
styleProperty="lineColor"
handlePropertyChange={this.props.handlePropertyChange}
styleDescriptor={this.props.styleProperties.lineColor}
ordinalFields={this.state.ordinalFields}
defaultStaticStyleOptions={this.state.defaultStaticProperties.lineColor.options}
defaultDynamicStyleOptions={this.state.defaultDynamicProperties.lineColor.options}
/>
<EuiSpacer size="m" />
<VectorStyleSizeEditor
styleProperty="lineWidth"
handlePropertyChange={this.props.handlePropertyChange}
styleDescriptor={this.props.styleProperties.lineWidth}
ordinalFields={this.state.ordinalFields}
defaultStaticStyleOptions={this.state.defaultStaticProperties.lineWidth.options}
defaultDynamicStyleOptions={this.state.defaultDynamicProperties.lineWidth.options}
/>
<EuiSpacer size="m" />
<VectorStyleSizeEditor
styleProperty="iconSize"
handlePropertyChange={this.props.handlePropertyChange}
styleDescriptor={this.props.styleProperties.iconSize}
ordinalFields={this.state.ordinalFields}
defaultStaticStyleOptions={this.state.defaultStaticProperties.iconSize.options}
defaultDynamicStyleOptions={this.state.defaultDynamicProperties.iconSize.options}
/>
{styleProperties}
</Fragment>
);
}

View file

@ -11,9 +11,10 @@ import { getHexColorRangeStrings } from '../../utils/color_utils';
import { VectorStyleEditor } from './components/vector/vector_style_editor';
import { getDefaultStaticProperties } from './vector_style_defaults';
import { AbstractStyle } from './abstract_style';
import { SOURCE_DATA_ID_ORIGIN } from '../../../../common/constants';
import { SOURCE_DATA_ID_ORIGIN, GEO_JSON_TYPE } from '../../../../common/constants';
import { VectorIcon } from './components/vector/legend/vector_icon';
import { VectorStyleLegend } from './components/vector/legend/vector_style_legend';
import { VECTOR_SHAPE_TYPES } from '../sources/vector_feature_types';
export class VectorStyle extends AbstractStyle {
@ -24,8 +25,9 @@ export class VectorStyle extends AbstractStyle {
return `__kbn__scaled(${fieldName})`;
}
constructor(descriptor = {}) {
constructor(descriptor = {}, source) {
super();
this._source = source;
this._descriptor = {
...descriptor,
...VectorStyle.createDescriptor(descriptor.properties),
@ -64,6 +66,8 @@ export class VectorStyle extends AbstractStyle {
handlePropertyChange={handlePropertyChange}
styleProperties={styleProperties}
layer={layer}
loadIsPointsOnly={this._getIsPointsOnly}
loadIsLinesOnly={this._getIsLinesOnly}
/>
);
}
@ -125,7 +129,7 @@ export class VectorStyle extends AbstractStyle {
};
}
pluckStyleMetaFromSourceDataRequest(sourceDataRequest) {
async pluckStyleMetaFromSourceDataRequest(sourceDataRequest) {
const features = _.get(sourceDataRequest.getData(), 'features', []);
if (features.length === 0) {
return {};
@ -140,12 +144,29 @@ export class VectorStyle extends AbstractStyle {
};
});
let isPointsOnly = true;
const supportedFeatures = await this._source.getSupportedShapeTypes();
const isSingleFeatureType = supportedFeatures.length === 1;
if (scaledFields.length === 0 && isSingleFeatureType) {
// no meta data to pull from source data request.
return {};
}
let hasPoints = false;
let hasLines = false;
let hasPolygons = false;
for (let i = 0; i < features.length; i++) {
const feature = features[i];
if (isPointsOnly && feature.geometry.type !== 'Point') {
isPointsOnly = false;
if (!hasPoints && [GEO_JSON_TYPE.POINT, GEO_JSON_TYPE.MULTI_POINT].includes(feature.geometry.type)) {
hasPoints = true;
}
if (!hasLines && [GEO_JSON_TYPE.LINE_STRING, GEO_JSON_TYPE.MULTI_LINE_STRING].includes(feature.geometry.type)) {
hasLines = true;
}
if (!hasPolygons && [GEO_JSON_TYPE.POLYGON, GEO_JSON_TYPE.MULTI_POLYGON].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]);
@ -157,7 +178,11 @@ export class VectorStyle extends AbstractStyle {
}
const featuresMeta = {
isPointsOnly
hasFeatureType: {
[VECTOR_SHAPE_TYPES.POINT]: hasPoints,
[VECTOR_SHAPE_TYPES.LINE]: hasLines,
[VECTOR_SHAPE_TYPES.POLYGON]: hasPolygons
}
};
scaledFields.forEach(({ min, max, name }) => {
@ -215,8 +240,36 @@ export class VectorStyle extends AbstractStyle {
return type === VectorStyle.STYLE_TYPE.DYNAMIC && options.field && options.field.name;
}
_getIsPointsOnly = () => {
return _.get(this._descriptor, '__styleMeta.isPointsOnly', false);
_checkIfOnlyFeatureType = async (featureType) => {
const supportedFeatures = await this._source.getSupportedShapeTypes();
if (supportedFeatures.length === 1) {
return supportedFeatures[0] === featureType;
}
if (!this._descriptor.__styleMeta || !this._descriptor.__styleMeta.hasFeatureType) {
return false;
}
const featureTypes = Object.keys(this._descriptor.__styleMeta.hasFeatureType);
return featureTypes.reduce((isOnlySingleFeatureType, featureTypeKey) => {
const hasFeature = this._descriptor.__styleMeta.hasFeatureType[featureTypeKey];
return featureTypeKey === featureType
? isOnlySingleFeatureType && hasFeature
: isOnlySingleFeatureType && !hasFeature;
}, true);
}
_getIsPointsOnly = async () => {
return this._checkIfOnlyFeatureType(VECTOR_SHAPE_TYPES.POINT);
}
_getIsLinesOnly = async () => {
return this._checkIfOnlyFeatureType(VECTOR_SHAPE_TYPES.LINE);
}
_getIsPolygonsOnly = async () => {
return this._checkIfOnlyFeatureType(VECTOR_SHAPE_TYPES.POLYGON);
}
_getFieldRange = (fieldName) => {
@ -227,7 +280,8 @@ export class VectorStyle extends AbstractStyle {
const styles = this.getProperties();
return (
<VectorIcon
isPointsOnly={this._getIsPointsOnly()}
loadIsPointsOnly={this._getIsPointsOnly}
loadIsLinesOnly={this._getIsLinesOnly}
fillColor={styles.fillColor}
lineColor={styles.lineColor}
/>

View file

@ -6,6 +6,7 @@
import { VectorStyle } from './vector_style';
import { DataRequest } from '../util/data_request';
import { VECTOR_SHAPE_TYPES } from '../sources/vector_feature_types';
describe('getDescriptorWithMissingStylePropsRemoved', () => {
const fieldName = 'doIStillExist';
@ -70,8 +71,14 @@ describe('getDescriptorWithMissingStylePropsRemoved', () => {
describe('pluckStyleMetaFromSourceDataRequest', () => {
describe('isPointsOnly', () => {
it('Should identify when feature collection only contains points', () => {
const sourceMock = {
getSupportedShapeTypes: () => {
return Object.values(VECTOR_SHAPE_TYPES);
}
};
describe('has features', () => {
it('Should identify when feature collection only contains points', async () => {
const sourceDataRequest = new DataRequest({
data: {
type: 'FeatureCollection',
@ -84,43 +91,51 @@ describe('pluckStyleMetaFromSourceDataRequest', () => {
},
{
geometry: {
type: 'Point'
type: 'MultiPoint'
},
properties: {}
}
],
}
});
const vectorStyle = new VectorStyle({});
const vectorStyle = new VectorStyle({}, sourceMock);
const featuresMeta = vectorStyle.pluckStyleMetaFromSourceDataRequest(sourceDataRequest);
expect(featuresMeta).toEqual({ isPointsOnly: true });
const featuresMeta = await vectorStyle.pluckStyleMetaFromSourceDataRequest(sourceDataRequest);
expect(featuresMeta.hasFeatureType).toEqual({
LINE: false,
POINT: true,
POLYGON: false
});
});
it('Should identify when feature collection contains features other than points', () => {
it('Should identify when feature collection only contains lines', async () => {
const sourceDataRequest = new DataRequest({
data: {
type: 'FeatureCollection',
features: [
{
geometry: {
type: 'Point'
type: 'LineString'
},
properties: {}
},
{
geometry: {
type: 'Polygon'
type: 'MultiLineString'
},
properties: {}
}
],
}
});
const vectorStyle = new VectorStyle({});
const vectorStyle = new VectorStyle({}, sourceMock);
const featuresMeta = vectorStyle.pluckStyleMetaFromSourceDataRequest(sourceDataRequest);
expect(featuresMeta).toEqual({ isPointsOnly: false });
const featuresMeta = await vectorStyle.pluckStyleMetaFromSourceDataRequest(sourceDataRequest);
expect(featuresMeta.hasFeatureType).toEqual({
LINE: true,
POINT: false,
POLYGON: false
});
});
});
@ -149,7 +164,7 @@ describe('pluckStyleMetaFromSourceDataRequest', () => {
}
});
it('Should not extract scaled field range when scaled field has not values', () => {
it('Should not extract scaled field range when scaled field has no values', async () => {
const vectorStyle = new VectorStyle({
properties: {
fillColor: {
@ -161,13 +176,17 @@ describe('pluckStyleMetaFromSourceDataRequest', () => {
}
}
}
});
}, sourceMock);
const featuresMeta = vectorStyle.pluckStyleMetaFromSourceDataRequest(sourceDataRequest);
expect(featuresMeta).toEqual({ isPointsOnly: true });
const featuresMeta = await vectorStyle.pluckStyleMetaFromSourceDataRequest(sourceDataRequest);
expect(featuresMeta.hasFeatureType).toEqual({
LINE: false,
POINT: true,
POLYGON: false
});
});
it('Should extract scaled field range', () => {
it('Should extract scaled field range', async () => {
const vectorStyle = new VectorStyle({
properties: {
fillColor: {
@ -179,18 +198,89 @@ describe('pluckStyleMetaFromSourceDataRequest', () => {
}
}
}
});
}, sourceMock);
const featuresMeta = vectorStyle.pluckStyleMetaFromSourceDataRequest(sourceDataRequest);
expect(featuresMeta).toEqual({
isPointsOnly: true,
myDynamicField: {
delta: 9,
max: 10,
min: 1
}
const featuresMeta = await vectorStyle.pluckStyleMetaFromSourceDataRequest(sourceDataRequest);
expect(featuresMeta.myDynamicField).toEqual({
delta: 9,
max: 10,
min: 1
});
});
});
});
describe('checkIfOnlyFeatureType', () => {
describe('source supports single feature type', () => {
const sourceMock = {
getSupportedShapeTypes: () => {
return [VECTOR_SHAPE_TYPES.POINT];
}
};
it('isPointsOnly should be true when source feature type only supports points', async () => {
const vectorStyle = new VectorStyle({}, sourceMock);
const isPointsOnly = await vectorStyle._getIsPointsOnly();
expect(isPointsOnly).toBe(true);
});
it('isLineOnly should be false when source feature type only supports points', async () => {
const vectorStyle = new VectorStyle({}, sourceMock);
const isLineOnly = await vectorStyle._getIsLinesOnly();
expect(isLineOnly).toBe(false);
});
});
describe('source supports multiple feature types', () => {
const sourceMock = {
getSupportedShapeTypes: () => {
return Object.values(VECTOR_SHAPE_TYPES);
}
};
it('isPointsOnly should be true when data contains just points', async () => {
const vectorStyle = new VectorStyle({
__styleMeta: {
hasFeatureType: {
POINT: true,
LINE: false,
POLYGON: false
}
}
}, sourceMock);
const isPointsOnly = await vectorStyle._getIsPointsOnly();
expect(isPointsOnly).toBe(true);
});
it('isPointsOnly should be false when data contains just lines', async () => {
const vectorStyle = new VectorStyle({
__styleMeta: {
hasFeatureType: {
POINT: false,
LINE: true,
POLYGON: false
}
}
}, sourceMock);
const isPointsOnly = await vectorStyle._getIsPointsOnly();
expect(isPointsOnly).toBe(false);
});
it('isPointsOnly should be false when data contains points, lines, and polygons', async () => {
const vectorStyle = new VectorStyle({
__styleMeta: {
hasFeatureType: {
POINT: true,
LINE: true,
POLYGON: true
}
}
}, sourceMock);
const isPointsOnly = await vectorStyle._getIsPointsOnly();
expect(isPointsOnly).toBe(false);
});
});
});

View file

@ -9,7 +9,7 @@ import React from 'react';
import { AbstractLayer } from './layer';
import { VectorStyle } from './styles/vector_style';
import { LeftInnerJoin } from './joins/left_inner_join';
import { FEATURE_ID_PROPERTY_NAME, SOURCE_DATA_ID_ORIGIN } from '../../../common/constants';
import { FEATURE_ID_PROPERTY_NAME, SOURCE_DATA_ID_ORIGIN, GEO_JSON_TYPE } from '../../../common/constants';
import _ from 'lodash';
import { JoinTooltipProperty } from './tooltips/join_tooltip_property';
import { isRefreshOnlyQuery } from './util/is_refresh_only_query';
@ -24,16 +24,16 @@ const EMPTY_FEATURE_COLLECTION = {
const CLOSED_SHAPE_MB_FILTER = [
'any',
['==', ['geometry-type'], 'Polygon'],
['==', ['geometry-type'], 'MultiPolygon']
['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON],
['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POLYGON]
];
const ALL_SHAPE_MB_FILTER = [
'any',
['==', ['geometry-type'], 'Polygon'],
['==', ['geometry-type'], 'MultiPolygon'],
['==', ['geometry-type'], 'LineString'],
['==', ['geometry-type'], 'MultiLineString']
['==', ['geometry-type'], GEO_JSON_TYPE.POLYGON],
['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_POLYGON],
['==', ['geometry-type'], GEO_JSON_TYPE.LINE_STRING],
['==', ['geometry-type'], GEO_JSON_TYPE.MULTI_LINE_STRING]
];
export class VectorLayer extends AbstractLayer {