Geomap: Style based on feature property (#39498)

* hardcode styles

* add custom styles and rules

* refactor and test to check feature matches rule

* update types and default style

* format geojsonmapper

* adjust types and reformat tests

* remove unused property

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
nikki-kiga 2021-09-27 09:44:19 -07:00 committed by GitHub
parent 6eceb69d43
commit 8d92e7ab90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 268 additions and 6 deletions

View File

@ -3,20 +3,24 @@ import Map from 'ol/Map';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import GeoJSON from 'ol/format/GeoJSON';
import { Feature } from 'ol';
import { Geometry } from 'ol/geom';
import { getGeoMapStyle } from '../../utils/getGeoMapStyle';
import { checkFeatureMatchesStyleRule } from '../../utils/checkFeatureMatchesStyleRule';
import { FeatureStyleConfig } from '../../types';
import { Stroke, Style } from 'ol/style';
import { FeatureLike } from 'ol/Feature';
export interface GeoJSONMapperConfig {
// URL for a geojson file
src?: string;
// Field name that will map to each featureId
idField?: string;
// Field to use that will set color
valueField?: string;
// Styles that can be applied
styles?: FeatureStyleConfig[];
}
const defaultOptions: GeoJSONMapperConfig = {
src: 'public/maps/countries.geojson',
styles: [],
};
export const geojsonMapper: MapLayerRegistryItem<GeoJSONMapperConfig> = {
@ -38,8 +42,26 @@ export const geojsonMapper: MapLayerRegistryItem<GeoJSONMapperConfig> = {
format: new GeoJSON(),
});
const defaultStyle = new Style({
stroke: new Stroke({
color: '#1F60C4',
width: 1,
}),
});
const vectorLayer = new VectorLayer({
source,
style: (feature: FeatureLike) => {
if (feature && config?.styles?.length) {
for (const style of config.styles) {
//check if there is no style rule or if the rule matches feature property
if (!style.rule || checkFeatureMatchesStyleRule(style.rule, feature as Feature<Geometry>)) {
return getGeoMapStyle(style, feature);
}
}
}
return defaultStyle;
},
});
return {

View File

@ -43,3 +43,21 @@ export interface GeomapPanelOptions {
basemap: MapLayerOptions;
layers: MapLayerOptions[];
}
export interface FeatureStyleConfig {
fillColor: string; //eventually be ColorDimensionConfig
strokeWidth?: number;
rule?: FeatureRuleConfig;
}
export interface FeatureRuleConfig {
property: string;
operation: ComparisonOperation;
value: string | boolean | number;
}
export enum ComparisonOperation {
EQ = 'eq',
LT = 'lt',
LTE = 'lte',
GT = 'gt',
GTE = 'gte',
}

View File

@ -0,0 +1,176 @@
import { Feature } from 'ol';
import { ComparisonOperation } from '../types';
import { checkFeatureMatchesStyleRule } from './checkFeatureMatchesStyleRule';
describe('check if feature matches style rule', () => {
it('can compare with numbers', () => {
const feature = new Feature({
number: 3,
});
expect(
checkFeatureMatchesStyleRule(
{
operation: ComparisonOperation.EQ,
property: 'number',
value: 3,
},
feature
)
).toEqual(true);
expect(
checkFeatureMatchesStyleRule(
{
operation: ComparisonOperation.LT,
property: 'number',
value: 2,
},
feature
)
).toEqual(false);
expect(
checkFeatureMatchesStyleRule(
{
operation: ComparisonOperation.LTE,
property: 'number',
value: 3,
},
feature
)
).toEqual(true);
expect(
checkFeatureMatchesStyleRule(
{
operation: ComparisonOperation.GT,
property: 'number',
value: 3,
},
feature
)
).toEqual(false);
expect(
checkFeatureMatchesStyleRule(
{
operation: ComparisonOperation.GTE,
property: 'number',
value: 3,
},
feature
)
).toEqual(true);
});
it('can compare with strings', () => {
const feature = new Feature({
string: 'b',
});
expect(
checkFeatureMatchesStyleRule(
{
operation: ComparisonOperation.EQ,
property: 'string',
value: 'B',
},
feature
)
).toEqual(false);
expect(
checkFeatureMatchesStyleRule(
{
operation: ComparisonOperation.LT,
property: 'string',
value: 'c',
},
feature
)
).toEqual(true);
expect(
checkFeatureMatchesStyleRule(
{
operation: ComparisonOperation.LTE,
property: 'string',
value: 'bc',
},
feature
)
).toEqual(true);
expect(
checkFeatureMatchesStyleRule(
{
operation: ComparisonOperation.GT,
property: 'string',
value: 'ab',
},
feature
)
).toEqual(true);
expect(
checkFeatureMatchesStyleRule(
{
operation: ComparisonOperation.GTE,
property: 'string',
value: 'abc',
},
feature
)
).toEqual(true);
});
it('can compare with booleans', () => {
const feature = new Feature({
name: 'test polygon',
});
feature.setProperties({ boolean: false });
expect(
checkFeatureMatchesStyleRule(
{
operation: ComparisonOperation.EQ,
property: 'boolean',
value: false,
},
feature
)
).toEqual(true);
expect(
checkFeatureMatchesStyleRule(
{
operation: ComparisonOperation.LT,
property: 'boolean',
value: true,
},
feature
)
).toEqual(true);
expect(
checkFeatureMatchesStyleRule(
{
operation: ComparisonOperation.LTE,
property: 'boolean',
value: true,
},
feature
)
).toEqual(true);
expect(
checkFeatureMatchesStyleRule(
{
operation: ComparisonOperation.GT,
property: 'boolean',
value: false,
},
feature
)
).toEqual(false);
expect(
checkFeatureMatchesStyleRule(
{
operation: ComparisonOperation.GTE,
property: 'boolean',
value: false,
},
feature
)
).toEqual(true);
});
});

View File

@ -0,0 +1,25 @@
import { FeatureLike } from 'ol/Feature';
import { FeatureRuleConfig, ComparisonOperation } from '../types';
/**
* Check whether feature has property value that matches rule
* @param rule - style rule with an operation, property, and value
* @param feature - feature with properties and values
* @returns boolean
*/
export const checkFeatureMatchesStyleRule = (rule: FeatureRuleConfig, feature: FeatureLike) => {
switch (rule.operation) {
case ComparisonOperation.EQ:
return feature.get(rule.property) === rule.value;
case ComparisonOperation.GT:
return feature.get(rule.property) > rule.value;
case ComparisonOperation.GTE:
return feature.get(rule.property) >= rule.value;
case ComparisonOperation.LT:
return feature.get(rule.property) < rule.value;
case ComparisonOperation.LTE:
return feature.get(rule.property) <= rule.value;
default:
return false;
}
};

View File

@ -0,0 +1,21 @@
import { Style, Stroke, Fill } from 'ol/style';
import { FeatureStyleConfig } from '../types';
/**
* Gets a geomap style based on fill, stroke, and stroke width
* @returns ol style
*/
export const getGeoMapStyle = (config: FeatureStyleConfig, property: any) => {
return new Style({
fill: new Fill({
color: `${config.fillColor ?? '#1F60C4'}`,
}),
stroke: config?.strokeWidth
? new Stroke({
color: `${config.fillColor ?? '#1F60C4'}`,
width: config.strokeWidth,
})
: undefined,
//handle a shape/marker too?
});
};