diff --git a/public/app/plugins/panel/geomap/layers/data/geojsonMapper.ts b/public/app/plugins/panel/geomap/layers/data/geojsonMapper.ts index e64b4af825a..309086d590c 100644 --- a/public/app/plugins/panel/geomap/layers/data/geojsonMapper.ts +++ b/public/app/plugins/panel/geomap/layers/data/geojsonMapper.ts @@ -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 = { @@ -38,8 +42,26 @@ export const geojsonMapper: MapLayerRegistryItem = { 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)) { + return getGeoMapStyle(style, feature); + } + } + } + return defaultStyle; + }, }); return { diff --git a/public/app/plugins/panel/geomap/types.ts b/public/app/plugins/panel/geomap/types.ts index e12ea8d0090..b69ba1546be 100644 --- a/public/app/plugins/panel/geomap/types.ts +++ b/public/app/plugins/panel/geomap/types.ts @@ -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', +} diff --git a/public/app/plugins/panel/geomap/utils/checkFeatureMatchesStyleRule.test.ts b/public/app/plugins/panel/geomap/utils/checkFeatureMatchesStyleRule.test.ts new file mode 100644 index 00000000000..7ac4398fef1 --- /dev/null +++ b/public/app/plugins/panel/geomap/utils/checkFeatureMatchesStyleRule.test.ts @@ -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); + }); +}); diff --git a/public/app/plugins/panel/geomap/utils/checkFeatureMatchesStyleRule.ts b/public/app/plugins/panel/geomap/utils/checkFeatureMatchesStyleRule.ts new file mode 100644 index 00000000000..a61691d7406 --- /dev/null +++ b/public/app/plugins/panel/geomap/utils/checkFeatureMatchesStyleRule.ts @@ -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; + } +}; diff --git a/public/app/plugins/panel/geomap/utils/getGeoMapStyle.ts b/public/app/plugins/panel/geomap/utils/getGeoMapStyle.ts new file mode 100644 index 00000000000..4ec8311cdd4 --- /dev/null +++ b/public/app/plugins/panel/geomap/utils/getGeoMapStyle.ts @@ -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? + }); +};