From f2d65ba0abda1a8c23bfbe5f8eb37171c2dd5831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Sat, 29 Jan 2022 16:03:47 +0100 Subject: [PATCH 1/3] Only set id if it is defined when creating feature --- src/ol/format/MVT.js | 4 +++- src/ol/format/OSMXML.js | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/ol/format/MVT.js b/src/ol/format/MVT.js index dd64218eff..4916a47657 100644 --- a/src/ol/format/MVT.js +++ b/src/ol/format/MVT.js @@ -248,7 +248,9 @@ class MVT extends FeatureFormat { } const geometry = transformGeometryWithOptions(geom, false, options); feature.setGeometry(geometry); - feature.setId(id); + if (id !== undefined) { + feature.setId(id); + } feature.setProperties(values, true); } diff --git a/src/ol/format/OSMXML.js b/src/ol/format/OSMXML.js index 50bba52f56..c75a217426 100644 --- a/src/ol/format/OSMXML.js +++ b/src/ol/format/OSMXML.js @@ -96,7 +96,9 @@ class OSMXML extends XMLFeature { } transformGeometryWithOptions(geometry, false, options); const feature = new Feature(geometry); - feature.setId(values.id); + if (values.id !== undefined) { + feature.setId(values.id); + } feature.setProperties(values.tags, true); state.features.push(feature); } @@ -146,7 +148,9 @@ function readNode(node, objectStack) { const geometry = new Point(coordinates); transformGeometryWithOptions(geometry, false, options); const feature = new Feature(geometry); - feature.setId(id); + if (id !== undefined) { + feature.setId(id); + } feature.setProperties(values.tags, true); state.features.push(feature); } From 856f23928086f553fa599c06bb0208e8c4605161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Fri, 28 Jan 2022 22:24:18 +0100 Subject: [PATCH 2/3] Add function to convert RenderFeature to Feature --- src/ol/format/MVT.js | 33 +--- src/ol/geom/flat/orient.js | 28 ++++ src/ol/render/Feature.js | 83 ++++++++++ test/node/ol/render/Feature.test.js | 247 ++++++++++++++++++++++++++++ 4 files changed, 366 insertions(+), 25 deletions(-) create mode 100644 test/node/ol/render/Feature.test.js diff --git a/src/ol/format/MVT.js b/src/ol/format/MVT.js index 4916a47657..dbdf15cbf5 100644 --- a/src/ol/format/MVT.js +++ b/src/ol/format/MVT.js @@ -19,7 +19,7 @@ import RenderFeature from '../render/Feature.js'; import Units from '../proj/Units.js'; import {assert} from '../asserts.js'; import {get} from '../proj.js'; -import {linearRingIsClockwise} from '../geom/flat/orient.js'; +import {inflateEnds} from '../geom/flat/orient.js'; /** * @typedef {Object} Options @@ -185,8 +185,8 @@ class MVT extends FeatureFormat { values[this.layerName_] = rawFeature.layer.name; - const flatCoordinates = []; - const ends = []; + const flatCoordinates = /** @type {Array} */ ([]); + const ends = /** @type {Array} */ ([]); this.readRawGeometry_(pbf, rawFeature, flatCoordinates, ends); const geometryType = getGeometryType(type, ends.length); @@ -203,28 +203,11 @@ class MVT extends FeatureFormat { } else { let geom; if (geometryType == GeometryType.POLYGON) { - const endss = []; - let offset = 0; - let prevEndIndex = 0; - for (let i = 0, ii = ends.length; i < ii; ++i) { - const end = ends[i]; - // classifies an array of rings into polygons with outer rings and holes - if (!linearRingIsClockwise(flatCoordinates, offset, end, 2)) { - endss.push(ends.slice(prevEndIndex, i + 1)); - } else { - if (endss.length === 0) { - continue; - } - endss[endss.length - 1].push(ends[prevEndIndex]); - } - prevEndIndex = i + 1; - offset = end; - } - if (endss.length > 1) { - geom = new MultiPolygon(flatCoordinates, GeometryLayout.XY, endss); - } else { - geom = new Polygon(flatCoordinates, GeometryLayout.XY, ends); - } + const endss = inflateEnds(flatCoordinates, ends); + geom = + endss.length > 1 + ? new MultiPolygon(flatCoordinates, GeometryLayout.XY, endss) + : new Polygon(flatCoordinates, GeometryLayout.XY, ends); } else { geom = geometryType === GeometryType.POINT diff --git a/src/ol/geom/flat/orient.js b/src/ol/geom/flat/orient.js index 30d46c6139..3a87e0e408 100644 --- a/src/ol/geom/flat/orient.js +++ b/src/ol/geom/flat/orient.js @@ -178,3 +178,31 @@ export function orientLinearRingsArray( } return offset; } + +/** + * Return a two-dimensional endss + * @param {Array} flatCoordinates Flat coordinates + * @param {Array} ends Linear ring end indexes + * @return {Array>} Two dimensional endss array that can + * be used to contruct a MultiPolygon + */ +export function inflateEnds(flatCoordinates, ends) { + const endss = []; + let offset = 0; + let prevEndIndex = 0; + for (let i = 0, ii = ends.length; i < ii; ++i) { + const end = ends[i]; + // classifies an array of rings into polygons with outer rings and holes + if (!linearRingIsClockwise(flatCoordinates, offset, end, 2)) { + endss.push(ends.slice(prevEndIndex, i + 1)); + } else { + if (endss.length === 0) { + continue; + } + endss[endss.length - 1].push(ends[prevEndIndex]); + } + prevEndIndex = i + 1; + offset = end; + } + return endss; +} diff --git a/src/ol/render/Feature.js b/src/ol/render/Feature.js index f9e64c1454..34a220a713 100644 --- a/src/ol/render/Feature.js +++ b/src/ol/render/Feature.js @@ -1,7 +1,17 @@ /** * @module ol/render/Feature */ +import Feature from '../Feature.js'; +import GeometryLayout from '../geom/GeometryLayout.js'; import GeometryType from '../geom/GeometryType.js'; +import { + LineString, + MultiLineString, + MultiPoint, + MultiPolygon, + Point, + Polygon, +} from '../geom.js'; import { compose as composeTransform, create as createTransform, @@ -18,6 +28,7 @@ import { getInteriorPointsOfMultiArray, } from '../geom/flat/interiorpoint.js'; import {get as getProjection} from '../proj.js'; +import {inflateEnds} from '../geom/flat/orient.js'; import {interpolatePoint} from '../geom/flat/interpolate.js'; import {linearRingss as linearRingssCenter} from '../geom/flat/center.js'; import {transform2D} from '../geom/flat/transform.js'; @@ -326,4 +337,76 @@ RenderFeature.prototype.getEndss = RenderFeature.prototype.getEnds; RenderFeature.prototype.getFlatCoordinates = RenderFeature.prototype.getOrientedFlatCoordinates; +/** + * Create a geometry from an `ol/render/Feature` + * @param {RenderFeature} renderFeature + * Render Feature + * @return {Point|MultiPoint|LineString|MultiLineString|Polygon|MultiPolygon} + * New geometry instance. + * @api + */ +export function toGeometry(renderFeature) { + const geometryType = renderFeature.getType(); + switch (geometryType) { + case GeometryType.POINT: + return new Point(renderFeature.getFlatCoordinates()); + case GeometryType.MULTI_POINT: + return new MultiPoint( + renderFeature.getFlatCoordinates(), + GeometryLayout.XY + ); + case GeometryType.LINE_STRING: + return new LineString( + renderFeature.getFlatCoordinates(), + GeometryLayout.XY + ); + case GeometryType.MULTI_LINE_STRING: + return new MultiLineString( + renderFeature.getFlatCoordinates(), + GeometryLayout.XY, + /** @type {Array} */ (renderFeature.getEnds()) + ); + case GeometryType.POLYGON: + const flatCoordinates = renderFeature.getFlatCoordinates(); + const ends = /** @type {Array} */ (renderFeature.getEnds()); + const endss = inflateEnds(flatCoordinates, ends); + return endss.length > 1 + ? new MultiPolygon(flatCoordinates, GeometryLayout.XY, endss) + : new Polygon(flatCoordinates, GeometryLayout.XY, ends); + case GeometryType.MULTI_POLYGON: + return new MultiPolygon( + renderFeature.getFlatCoordinates(), + GeometryLayout.XY, + /** @type {Array>} */ (renderFeature.getEndss()) + ); + default: + throw new Error('Invalid geometry type:' + geometryType); + } +} + +/** + * Create an `ol/Feature` from an `ol/render/Feature` + * @param {RenderFeature} renderFeature RenderFeature + * @param {string} [opt_geometryName='geometry'] Geometry name to use + * when creating the Feature. + * @return {Feature} Newly constructed `ol/Feature` with properties, + * geometry, and id copied over. + * @api + */ +export function toFeature(renderFeature, opt_geometryName) { + const id = renderFeature.getId(); + const geometry = toGeometry(renderFeature); + const properties = renderFeature.getProperties(); + const feature = new Feature(); + if (opt_geometryName !== undefined) { + feature.setGeometryName(opt_geometryName); + } + feature.setGeometry(geometry); + if (id !== undefined) { + feature.setId(id); + } + feature.setProperties(properties, true); + return feature; +} + export default RenderFeature; diff --git a/test/node/ol/render/Feature.test.js b/test/node/ol/render/Feature.test.js new file mode 100644 index 0000000000..d062199add --- /dev/null +++ b/test/node/ol/render/Feature.test.js @@ -0,0 +1,247 @@ +import GeometryType from '../../../../src/ol/geom/GeometryType.js'; +import RenderFeature, { + toFeature, + toGeometry, +} from '../../../../src/ol/render/Feature.js'; +import expect from '../../expect.js'; +import { + LineString, + MultiLineString, + MultiPoint, + MultiPolygon, + Point, + Polygon, +} from '../../../../src/ol/geom.js'; + +describe('ol/render/Feature', function () { + describe('ol/render/Feature.toGeometry()', function () { + it('creates a Point', function () { + const geometry = new Point([0, 0]); + const renderFeature = new RenderFeature( + geometry.getType(), + geometry.getFlatCoordinates().slice(), + [] + ); + const converted = toGeometry(renderFeature); + expect(converted).to.be.a(Point); + expect(converted.getFlatCoordinates()).to.eql( + geometry.getFlatCoordinates() + ); + expect(converted.getProperties()).to.eql({}); + }); + it('creates a MultiPoint', function () { + const geometry = new MultiPoint([ + [0, 0], + [4, 5], + ]); + const renderFeature = new RenderFeature( + geometry.getType(), + geometry.getFlatCoordinates().slice(), + [] + ); + const converted = toGeometry(renderFeature); + expect(converted).to.be.a(MultiPoint); + expect(converted.getFlatCoordinates()).to.eql( + geometry.getFlatCoordinates() + ); + expect(converted.getProperties()).to.eql({}); + }); + it('creates a LineString', function () { + const geometry = new LineString([ + [0, 0], + [4, 5], + ]); + const renderFeature = new RenderFeature( + geometry.getType(), + geometry.getFlatCoordinates().slice(), + [] + ); + const converted = toGeometry(renderFeature); + expect(converted).to.be.a(LineString); + expect(converted.getFlatCoordinates()).to.eql( + geometry.getFlatCoordinates() + ); + expect(converted.getProperties()).to.eql({}); + }); + it('creates a MultiLineString', function () { + const geometry = new MultiLineString([ + [ + [0, 0], + [4, 5], + ], + [ + [0, 0], + [4, 5], + ], + ]); + const renderFeature = new RenderFeature( + geometry.getType(), + geometry.getFlatCoordinates().slice(), + geometry.getEnds().slice() + ); + const converted = toGeometry(renderFeature); + expect(converted).to.be.a(MultiLineString); + expect(converted.getFlatCoordinates()).to.eql( + geometry.getFlatCoordinates() + ); + expect(converted.getEnds()).to.eql(geometry.getEnds()); + expect(converted.getProperties()).to.eql({}); + }); + it('creates a Polygon', function () { + const geometry = new Polygon([ + [ + [0, 0], + [5, 0], + [5, 5], + [0, 0], + ], + [ + [1, 1], + [4, 4], + [4, 1], + [1, 1], + ], + ]); + const renderFeature = new RenderFeature( + geometry.getType(), + geometry.getFlatCoordinates().slice(), + geometry.getEnds().slice() + ); + const converted = toGeometry(renderFeature); + expect(converted).to.be.a(Polygon); + expect(converted.getFlatCoordinates()).to.eql( + geometry.getFlatCoordinates() + ); + expect(converted.getEnds()).to.eql(geometry.getEnds()); + expect(converted.getProperties()).to.eql({}); + }); + it('creates a MultiPolygon from oriented polygon rings', function () { + const geometry = new MultiPolygon([ + [ + [ + [0, 0], + [5, 0], + [5, 5], + [0, 0], + ], + [ + [1, 1], + [4, 4], + [4, 1], + [1, 1], + ], + ], + [ + [ + [-0, -0], + [-5, -0], + [-5, -5], + [-0, -0], + ], + ], + ]); + const renderFeature = new RenderFeature( + GeometryType.POLYGON, + geometry.getFlatCoordinates().slice(), + geometry.getEndss().flat(1) + ); + const converted = toGeometry(renderFeature); + expect(converted).to.be.a(MultiPolygon); + expect(converted.getFlatCoordinates()).to.eql( + geometry.getFlatCoordinates() + ); + expect(converted.getEndss()).to.eql(geometry.getEndss()); + expect(converted.getProperties()).to.eql({}); + }); + it('creates a MultiPolygon', function () { + const geometry = new MultiPolygon([ + [ + [ + [0, 0], + [5, 0], + [5, 5], + [0, 0], + ], + [ + [1, 1], + [4, 1], + [4, 4], + [1, 1], + ], + ], + [ + [ + [-0, -0], + [-5, -0], + [-5, -5], + [-0, -0], + ], + ], + ]); + const renderFeature = new RenderFeature( + geometry.getType(), + geometry.getFlatCoordinates().slice(), + geometry.getEndss().slice() + ); + const converted = toGeometry(renderFeature); + expect(converted).to.be.a(MultiPolygon); + expect(converted.getFlatCoordinates()).to.eql( + geometry.getFlatCoordinates() + ); + expect(converted.getEndss()).to.eql(geometry.getEndss()); + expect(converted.getProperties()).to.eql({}); + }); + }); + + describe('ol/render/Feature.toFeature()', function () { + it('creates a Feature', function () { + const id = 'asdf'; + const properties = {test: '123'}; + const geometry = new Point([0, 0]); + const renderFeature = new RenderFeature( + geometry.getType(), + geometry.getFlatCoordinates().slice(), + [], + properties, + id + ); + const feature = toFeature(renderFeature); + const converted = feature.getGeometry(); + expect(converted).to.be.a(Point); + expect(converted.getFlatCoordinates()).to.eql( + geometry.getFlatCoordinates() + ); + expect(feature.getId()).to.be(id); + const props = feature.getProperties(); + delete props.geometry; + expect(props).to.eql(properties); + }); + }); + it('creates a Feature with non-default geometry name', function () { + const id = 'asdf'; + const properties = {geometry: '123'}; + const geometry = new LineString([ + [0, 0], + [5, 5], + ]); + const renderFeature = new RenderFeature( + geometry.getType(), + geometry.getFlatCoordinates().slice(), + [], + properties, + id + ); + const geometryName = 'geom'; + const feature = toFeature(renderFeature, geometryName); + const converted = feature.getGeometry(); + expect(converted).to.be.a(LineString); + expect(feature.get(geometryName)).to.be(converted); + expect(converted.getFlatCoordinates()).to.eql( + geometry.getFlatCoordinates() + ); + expect(feature.getId()).to.be(id); + const props = feature.getProperties(); + delete props.geom; + expect(props).to.eql(properties); + }); +}); From f0e768bb9a8669a675e58d94aa4998295c6c66a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Kr=C3=B6g?= Date: Sat, 29 Jan 2022 21:50:34 +0100 Subject: [PATCH 3/3] Remove case of MulitPolygon RenderFeature --- src/ol/render/Feature.js | 6 ----- test/node/ol/render/Feature.test.js | 38 ----------------------------- 2 files changed, 44 deletions(-) diff --git a/src/ol/render/Feature.js b/src/ol/render/Feature.js index 34a220a713..69820c6916 100644 --- a/src/ol/render/Feature.js +++ b/src/ol/render/Feature.js @@ -373,12 +373,6 @@ export function toGeometry(renderFeature) { return endss.length > 1 ? new MultiPolygon(flatCoordinates, GeometryLayout.XY, endss) : new Polygon(flatCoordinates, GeometryLayout.XY, ends); - case GeometryType.MULTI_POLYGON: - return new MultiPolygon( - renderFeature.getFlatCoordinates(), - GeometryLayout.XY, - /** @type {Array>} */ (renderFeature.getEndss()) - ); default: throw new Error('Invalid geometry type:' + geometryType); } diff --git a/test/node/ol/render/Feature.test.js b/test/node/ol/render/Feature.test.js index d062199add..3c432e6215 100644 --- a/test/node/ol/render/Feature.test.js +++ b/test/node/ol/render/Feature.test.js @@ -153,44 +153,6 @@ describe('ol/render/Feature', function () { expect(converted.getEndss()).to.eql(geometry.getEndss()); expect(converted.getProperties()).to.eql({}); }); - it('creates a MultiPolygon', function () { - const geometry = new MultiPolygon([ - [ - [ - [0, 0], - [5, 0], - [5, 5], - [0, 0], - ], - [ - [1, 1], - [4, 1], - [4, 4], - [1, 1], - ], - ], - [ - [ - [-0, -0], - [-5, -0], - [-5, -5], - [-0, -0], - ], - ], - ]); - const renderFeature = new RenderFeature( - geometry.getType(), - geometry.getFlatCoordinates().slice(), - geometry.getEndss().slice() - ); - const converted = toGeometry(renderFeature); - expect(converted).to.be.a(MultiPolygon); - expect(converted.getFlatCoordinates()).to.eql( - geometry.getFlatCoordinates() - ); - expect(converted.getEndss()).to.eql(geometry.getEndss()); - expect(converted.getProperties()).to.eql({}); - }); }); describe('ol/render/Feature.toFeature()', function () {