From c949660fefcb500e8a364b9f5aa33ed9c28910b6 Mon Sep 17 00:00:00 2001 From: Olivia Guyot Date: Fri, 24 Nov 2023 10:37:48 +0100 Subject: [PATCH] Implement geometry-type operator in cpu --- src/ol/expr/cpu.js | 8 ++++-- src/ol/expr/expression.js | 43 +++++++++++++++++++++++++++- src/ol/expr/gpu.js | 21 ++------------ src/ol/render/canvas/style.js | 6 ++++ test/node/ol/expr/cpu.test.js | 18 ++++++++++++ test/node/ol/expr/expression.test.js | 34 ++++++++++++++++++++++ test/node/ol/expr/gpu.test.js | 25 ++-------------- 7 files changed, 111 insertions(+), 44 deletions(-) diff --git a/src/ol/expr/cpu.js b/src/ol/expr/cpu.js index 4cf99fd88a4..92541e1fc78 100644 --- a/src/ol/expr/cpu.js +++ b/src/ol/expr/cpu.js @@ -32,6 +32,7 @@ import { * @property {Object} variables The values for variables used in 'var' expressions. * @property {number} resolution The map resolution. * @property {string|number|null} featureId The feature id. + * @property {string} geometryType Geometry type of the current object. */ /** @@ -43,6 +44,7 @@ export function newEvaluationContext() { properties: {}, resolution: NaN, featureId: null, + geometryType: '', }; } @@ -129,7 +131,10 @@ function compileExpression(expression, context) { return compileAccessorExpression(expression, context); } case Ops.Id: { - return (expression) => expression.featureId; + return (context) => context.featureId; + } + case Ops.GeometryType: { + return (context) => context.geometryType; } case Ops.Concat: { const args = expression.args.map((e) => compileExpression(e, context)); @@ -182,7 +187,6 @@ function compileExpression(expression, context) { throw new Error(`Unsupported operator ${operator}`); } // TODO: unimplemented - // Ops.GeometryType // Ops.Zoom // Ops.Time // Ops.Between diff --git a/src/ol/expr/expression.js b/src/ol/expr/expression.js index 7e435c8d044..26c10bf5186 100644 --- a/src/ol/expr/expression.js +++ b/src/ol/expr/expression.js @@ -219,6 +219,7 @@ export class CallExpression { * @property {Set} variables Variables referenced with the 'var' operator. * @property {Set} properties Properties referenced with the 'get' operator. * @property {boolean} featureId The style uses the feature id. + * @property {boolean} geometryType The style uses the feature geometry type. * @property {import("../style/flat.js").FlatStyle|import("../style/webgl.js").WebGLStyle} style The style being parsed */ @@ -230,6 +231,7 @@ export function newParsingContext() { variables: new Set(), properties: new Set(), featureId: false, + geometryType: false, style: {}, }; } @@ -400,7 +402,7 @@ const parsers = { withArgsCount(2, Infinity), parseArgsOfType(AnyType) ), - [Ops.GeometryType]: createParser(StringType, withNoArgs), + [Ops.GeometryType]: createParser(StringType, withNoArgs, usesGeometryType), [Ops.Resolution]: createParser(NumberType, withNoArgs), [Ops.Zoom]: createParser(NumberType, withNoArgs), [Ops.Time]: createParser(NumberType, withNoArgs), @@ -675,6 +677,13 @@ function usesFeatureId(encoded, context) { context.featureId = true; } +/** + * @type ArgValidator + */ +function usesGeometryType(encoded, context) { + context.geometryType = true; +} + /** * @type ArgValidator */ @@ -1097,3 +1106,35 @@ function parseCallExpression(encoded, context, typeHint) { } return parser(encoded, context, typeHint); } + +/** + * Returns a simplified geometry type suited for the `geometry-type` operator + * @param {import('../geom/Geometry.js').default|import('../render/Feature.js').default} geometry Geometry object + * @return {'Point'|'LineString'|'Polygon'|''} Simplified geometry type; empty string of no geometry found + */ +export function computeGeometryType(geometry) { + if (!geometry) { + return ''; + } + const type = geometry.getType(); + switch (type) { + case 'Point': + case 'LineString': + case 'Polygon': + return type; + case 'MultiPoint': + case 'MultiLineString': + case 'MultiPolygon': + return /** @type {'Point'|'LineString'|'Polygon'} */ (type.substring(5)); + case 'Circle': + return 'Polygon'; + case 'GeometryCollection': + return computeGeometryType( + /** @type {import("../geom/GeometryCollection.js").default} */ ( + geometry + ).getGeometries()[0] + ); + default: + return ''; + } +} diff --git a/src/ol/expr/gpu.js b/src/ol/expr/gpu.js index 9722bfb08af..287ba5f1210 100644 --- a/src/ol/expr/gpu.js +++ b/src/ol/expr/gpu.js @@ -11,6 +11,7 @@ import { NumberType, Ops, StringType, + computeGeometryType, isType, overlapsType, parse, @@ -232,31 +233,13 @@ const compilers = { }, [Ops.GeometryType]: (context, expression, type) => { const propName = 'geometryType'; - const computeType = (geometry) => { - const type = geometry.getType(); - switch (type) { - case 'Point': - case 'LineString': - case 'Polygon': - return type; - case 'MultiPoint': - case 'MultiLineString': - case 'MultiPolygon': - return type.substring(5); - case 'Circle': - return 'Polygon'; - case 'GeometryCollection': - return computeType(geometry.getGeometries()[0]); - default: - } - }; const isExisting = propName in context.properties; if (!isExisting) { context.properties[propName] = { name: propName, type: StringType, evaluator: (feature) => { - return computeType(feature.getGeometry()); + return computeGeometryType(feature.getGeometry()); }, }; } diff --git a/src/ol/render/canvas/style.js b/src/ol/render/canvas/style.js index e8c9ec49741..9162495e3d9 100644 --- a/src/ol/render/canvas/style.js +++ b/src/ol/render/canvas/style.js @@ -15,6 +15,7 @@ import { NumberArrayType, NumberType, StringType, + computeGeometryType, newParsingContext, } from '../../expr/expression.js'; import {buildExpression, newEvaluationContext} from '../../expr/cpu.js'; @@ -84,6 +85,11 @@ export function rulesToStyleFunction(rules) { evaluationContext.featureId = null; } } + if (parsingContext.geometryType) { + evaluationContext.geometryType = computeGeometryType( + feature.getGeometry() + ); + } return evaluator(evaluationContext); }; } diff --git a/test/node/ol/expr/cpu.test.js b/test/node/ol/expr/cpu.test.js index 5de5ce53cb2..a1e6c66dd39 100644 --- a/test/node/ol/expr/cpu.test.js +++ b/test/node/ol/expr/cpu.test.js @@ -69,6 +69,24 @@ describe('ol/expr/cpu.js', () => { }, expected: 'forty-two', }, + { + name: 'geometry-type', + type: StringType, + expression: ['geometry-type'], + context: { + geometryType: 'LineString', + }, + expected: 'LineString', + }, + { + name: 'geometry-type (empty)', + type: StringType, + expression: ['geometry-type'], + context: { + geometryType: '', + }, + expected: '', + }, { name: 'resolution', type: NumberType, diff --git a/test/node/ol/expr/expression.test.js b/test/node/ol/expr/expression.test.js index 3a1447e84b0..2b7468e00ab 100644 --- a/test/node/ol/expr/expression.test.js +++ b/test/node/ol/expr/expression.test.js @@ -9,12 +9,21 @@ import { NumberArrayType, NumberType, StringType, + computeGeometryType, includesType, isType, newParsingContext, parse, typeName, } from '../../../../src/ol/expr/expression.js'; +import { + Circle, + GeometryCollection, + MultiLineString, + MultiPoint, + MultiPolygon, + Point, +} from '../../../../src/ol/geom.js'; describe('ol/expr/expression.js', () => { describe('parse()', () => { @@ -624,4 +633,29 @@ describe('ol/expr/expression.js', () => { expect(isType(AnyType, NumberArrayType)).to.be(false); }); }); + describe('computeGeometryType', () => { + it('returns empty string for falsy geom', () => { + expect(computeGeometryType(undefined)).to.eql(''); + }); + it('returns Point for Point geom', () => { + expect(computeGeometryType(new Point([0, 1]))).to.eql('Point'); + }); + it('returns Polygon for MultiPolygon geom', () => { + expect(computeGeometryType(new MultiPolygon([]))).to.eql('Polygon'); + }); + it('returns LineString for MultiLineString geom', () => { + expect(computeGeometryType(new MultiLineString([]))).to.eql('LineString'); + }); + it('returns first geom type in geometry collection', () => { + expect( + computeGeometryType(new GeometryCollection([new Circle([0, 1])])) + ).to.eql('Polygon'); + expect( + computeGeometryType(new GeometryCollection([new MultiPoint([])])) + ).to.eql('Point'); + }); + it('returns empty string for empty geom collection', () => { + expect(computeGeometryType(new GeometryCollection([]))).to.eql(''); + }); + }); }); diff --git a/test/node/ol/expr/gpu.test.js b/test/node/ol/expr/gpu.test.js index ffe1e01933c..39bcd4e92f5 100644 --- a/test/node/ol/expr/gpu.test.js +++ b/test/node/ol/expr/gpu.test.js @@ -9,14 +9,7 @@ import { StringType, newParsingContext, } from '../../../../src/ol/expr/expression.js'; -import { - Circle, - GeometryCollection, - MultiLineString, - MultiPoint, - MultiPolygon, - Point, -} from '../../../../src/ol/geom.js'; +import {MultiPolygon} from '../../../../src/ol/geom.js'; import { arrayToGlsl, buildExpression, @@ -171,20 +164,8 @@ describe('ol/expr/gpu.js', () => { expect(prop.name).to.equal('geometryType'); expect(prop.type).to.equal(StringType); expect(prop.evaluator).to.be.an(Function); - const results = [ - new Feature(new Point([0, 1])), - new Feature(new MultiPolygon([])), - new Feature(new MultiLineString([])), - new Feature(new GeometryCollection([new Circle([0, 1])])), - new Feature(new GeometryCollection([new MultiPoint([])])), - ].map(prop.evaluator); - expect(results).to.eql([ - 'Point', - 'Polygon', - 'LineString', - 'Polygon', - 'Point', - ]); + const feature = new Feature(new MultiPolygon([])); + expect(prop.evaluator(feature)).to.eql('Polygon'); }, }, {