diff --git a/src/ol/interaction/Draw.js b/src/ol/interaction/Draw.js index 0d44266267..fd2e623be8 100644 --- a/src/ol/interaction/Draw.js +++ b/src/ol/interaction/Draw.js @@ -5,6 +5,7 @@ import Circle from '../geom/Circle.js'; import Event from '../events/Event.js'; import EventType from '../events/EventType.js'; import Feature from '../Feature.js'; +import GeometryLayout from '../geom/GeometryLayout.js'; import GeometryType from '../geom/GeometryType.js'; import InteractionProperty from './Property.js'; import LineString from '../geom/LineString.js'; @@ -29,6 +30,7 @@ import { } from '../extent.js'; import {createEditingStyle} from '../style/Style.js'; import {fromUserCoordinate, getUserProjection} from '../proj.js'; +import {getStrideForLayout} from '../geom/SimpleGeometry.js'; import {squaredDistance as squaredCoordinateDistance} from '../coordinate.js'; /** @@ -82,6 +84,8 @@ import {squaredDistance as squaredCoordinateDistance} from '../coordinate.js'; * Shift key activates freehand drawing. * @property {boolean} [wrapX=false] Wrap the world horizontally on the sketch * overlay. + * @property {GeometryLayout} [geometryLayout='XY'] Layout of the + * feature geometries created by the draw interaction. */ /** @@ -335,6 +339,14 @@ class Draw extends PointerInteraction { ? options.finishCondition : TRUE; + /** + * @private + * @type {import("../geom/GeometryLayout").default} + */ + this.geometryLayout_ = options.geometryLayout + ? options.geometryLayout + : GeometryLayout.XY; + let geometryFunction = options.geometryFunction; if (!geometryFunction) { const mode = this.mode_; @@ -354,7 +366,11 @@ class Draw extends PointerInteraction { center, fromUserCoordinate(coordinates[coordinates.length - 1], projection) ); - circle.setCenterAndRadius(center, Math.sqrt(squaredLength)); + circle.setCenterAndRadius( + center, + Math.sqrt(squaredLength), + this.geometryLayout_ + ); const userProjection = getUserProjection(); if (userProjection) { circle.transform(projection, userProjection); @@ -381,17 +397,18 @@ class Draw extends PointerInteraction { if (mode === Mode.POLYGON) { if (coordinates[0].length) { // Add a closing coordinate to match the first - geometry.setCoordinates([ - coordinates[0].concat([coordinates[0][0]]), - ]); + geometry.setCoordinates( + [coordinates[0].concat([coordinates[0][0]])], + this.geometryLayout_ + ); } else { - geometry.setCoordinates([]); + geometry.setCoordinates([], this.geometryLayout_); } } else { - geometry.setCoordinates(coordinates); + geometry.setCoordinates(coordinates, this.geometryLayout_); } } else { - geometry = new Constructor(coordinates); + geometry = new Constructor(coordinates, this.geometryLayout_); } return geometry; }; @@ -803,6 +820,10 @@ class Draw extends PointerInteraction { */ startDrawing_(start) { const projection = this.getMap().getView().getProjection(); + const stride = getStrideForLayout(this.geometryLayout_); + while (start.length < stride) { + start.push(0); + } this.finishCoordinate_ = start; if (this.mode_ === Mode.POINT) { this.sketchCoords_ = start.slice(); @@ -840,7 +861,11 @@ class Draw extends PointerInteraction { const map = this.getMap(); const geometry = this.sketchFeature_.getGeometry(); const projection = map.getView().getProjection(); + const stride = getStrideForLayout(this.geometryLayout_); let coordinates, last; + while (coordinate.length < stride) { + coordinate.push(0); + } if (this.mode_ === Mode.POINT) { last = this.sketchCoords_; } else if (this.mode_ === Mode.POLYGON) { diff --git a/test/browser/spec/ol/interaction/draw.test.js b/test/browser/spec/ol/interaction/draw.test.js index b09f73aee9..6a381760fa 100644 --- a/test/browser/spec/ol/interaction/draw.test.js +++ b/test/browser/spec/ol/interaction/draw.test.js @@ -4,6 +4,7 @@ import Draw, { createRegularPolygon, } from '../../../../../src/ol/interaction/Draw.js'; import Feature from '../../../../../src/ol/Feature.js'; +import GeometryLayout from '../../../../../src/ol/geom/GeometryLayout.js'; import GeometryType from '../../../../../src/ol/geom/GeometryType.js'; import Interaction from '../../../../../src/ol/interaction/Interaction.js'; import LineString from '../../../../../src/ol/geom/LineString.js'; @@ -162,6 +163,15 @@ describe('ol.interaction.Draw', function () { unByKey(clickKey); //}, 300); }); + + it('accepts a geometryLayout option', function () { + const draw = new Draw({ + source: source, + type: 'Point', + geometryLayout: 'XYZ', + }); + expect(draw.geometryLayout_).to.be('XYZ'); + }); }); describe('specifying a geometryName', function () { @@ -1946,4 +1956,247 @@ describe('ol.interaction.Draw', function () { ]); }); }); + + describe('drawing with geometryLayout', function () { + let draw; + function createDrawInteraction(type, geometryLayout) { + draw = new Draw({ + source: source, + type: type, + geometryLayout: geometryLayout, + }); + map.addInteraction(draw); + } + + function drawPoint(geometryLayout) { + createDrawInteraction(GeometryType.POINT, geometryLayout); + simulateEvent('pointermove', 10, 20); + simulateEvent('pointerdown', 10, 20); + simulateEvent('pointerup', 10, 20); + } + + function drawLineString(geometryLayout) { + createDrawInteraction(GeometryType.LINE_STRING, geometryLayout); + // first point + simulateEvent('pointermove', 10, 20); + simulateEvent('pointerdown', 10, 20); + simulateEvent('pointerup', 10, 20); + + // second point + simulateEvent('pointermove', 30, 20); + simulateEvent('pointerdown', 30, 20); + simulateEvent('pointerup', 30, 20); + + // finish on second point + simulateEvent('pointerdown', 30, 20); + simulateEvent('pointerup', 30, 20); + } + + function drawPolygon(geometryLayout) { + createDrawInteraction(GeometryType.POLYGON, geometryLayout); + // first point + simulateEvent('pointermove', 10, 20); + simulateEvent('pointerdown', 10, 20); + simulateEvent('pointerup', 10, 20); + + // second point + simulateEvent('pointermove', 30, 20); + simulateEvent('pointerdown', 30, 20); + simulateEvent('pointerup', 30, 20); + + // third point + simulateEvent('pointermove', 40, 10); + simulateEvent('pointerdown', 40, 10); + simulateEvent('pointerup', 40, 10); + + // finish on last point + simulateEvent('pointerdown', 40, 10); + simulateEvent('pointerup', 40, 10); + } + + function drawCircle(geometryLayout) { + createDrawInteraction(GeometryType.CIRCLE, geometryLayout); + // first point + simulateEvent('pointermove', 10, 20); + simulateEvent('pointerdown', 10, 20); + simulateEvent('pointerup', 10, 20); + + // finish on second point + simulateEvent('pointermove', 30, 20); + simulateEvent('pointerdown', 30, 20); + simulateEvent('pointerup', 30, 20); + } + + it('respects XY layout for POINT type', function () { + drawPoint(GeometryLayout.XY); + const features = source.getFeatures(); + const geometry = features[0].getGeometry(); + expect(geometry.getCoordinates()).to.eql([10, -20]); + expect(geometry.getLayout()).to.eql(GeometryLayout.XY); + }); + + it('respects XYZ layout for POINT type', function () { + drawPoint(GeometryLayout.XYZ); + const features = source.getFeatures(); + const geometry = features[0].getGeometry(); + expect(geometry.getCoordinates()).to.eql([10, -20, 0]); + expect(geometry.getLayout()).to.eql(GeometryLayout.XYZ); + }); + + it('respects XYM layout for POINT type', function () { + drawPoint(GeometryLayout.XYM); + const features = source.getFeatures(); + const geometry = features[0].getGeometry(); + expect(geometry.getCoordinates()).to.eql([10, -20, 0]); + expect(geometry.getLayout()).to.eql(GeometryLayout.XYM); + }); + + it('respects XYZM layout for POINT type', function () { + drawPoint(GeometryLayout.XYZM); + const features = source.getFeatures(); + const geometry = features[0].getGeometry(); + expect(geometry.getCoordinates()).to.eql([10, -20, 0, 0]); + expect(geometry.getLayout()).to.eql(GeometryLayout.XYZM); + }); + + it('respects XY layout for LINESTRING type', function () { + drawLineString(GeometryLayout.XY); + const features = source.getFeatures(); + const geometry = features[0].getGeometry(); + expect(geometry.getCoordinates()).to.eql([ + [10, -20], + [30, -20], + ]); + expect(geometry.getLayout()).to.eql(GeometryLayout.XY); + }); + + it('respects XYZ layout for LINESTRING type', function () { + drawLineString(GeometryLayout.XYZ); + const features = source.getFeatures(); + const geometry = features[0].getGeometry(); + expect(geometry.getCoordinates()).to.eql([ + [10, -20, 0], + [30, -20, 0], + ]); + expect(geometry.getLayout()).to.eql(GeometryLayout.XYZ); + }); + + it('respects XYM layout for LINESTRING type', function () { + drawLineString(GeometryLayout.XYM); + const features = source.getFeatures(); + const geometry = features[0].getGeometry(); + expect(geometry.getCoordinates()).to.eql([ + [10, -20, 0], + [30, -20, 0], + ]); + expect(geometry.getLayout()).to.eql(GeometryLayout.XYM); + }); + + it('respects XYZM layout for LINESTRING type', function () { + drawLineString(GeometryLayout.XYZM); + const features = source.getFeatures(); + const geometry = features[0].getGeometry(); + expect(geometry.getCoordinates()).to.eql([ + [10, -20, 0, 0], + [30, -20, 0, 0], + ]); + expect(geometry.getLayout()).to.eql(GeometryLayout.XYZM); + }); + + it('respects XY layout for POLYGON type', function () { + drawPolygon(GeometryLayout.XY); + const features = source.getFeatures(); + const geometry = features[0].getGeometry(); + expect(geometry.getCoordinates()).to.eql([ + [ + [10, -20], + [30, -20], + [40, -10], + [10, -20], + ], + ]); + expect(geometry.getLayout()).to.eql(GeometryLayout.XY); + }); + + it('respects XYZ layout for POLYGON type', function () { + drawPolygon(GeometryLayout.XYZ); + const features = source.getFeatures(); + const geometry = features[0].getGeometry(); + expect(geometry.getCoordinates()).to.eql([ + [ + [10, -20, 0], + [30, -20, 0], + [40, -10, 0], + [10, -20, 0], + ], + ]); + expect(geometry.getLayout()).to.eql(GeometryLayout.XYZ); + }); + + it('respects XYM layout for POLYGON type', function () { + drawPolygon(GeometryLayout.XYM); + const features = source.getFeatures(); + const geometry = features[0].getGeometry(); + expect(geometry.getCoordinates()).to.eql([ + [ + [10, -20, 0], + [30, -20, 0], + [40, -10, 0], + [10, -20, 0], + ], + ]); + expect(geometry.getLayout()).to.eql(GeometryLayout.XYM); + }); + + it('respects XYZM layout for POLYGON type', function () { + drawPolygon(GeometryLayout.XYZM); + const features = source.getFeatures(); + const geometry = features[0].getGeometry(); + expect(geometry.getCoordinates()).to.eql([ + [ + [10, -20, 0, 0], + [30, -20, 0, 0], + [40, -10, 0, 0], + [10, -20, 0, 0], + ], + ]); + expect(geometry.getLayout()).to.eql(GeometryLayout.XYZM); + }); + + it('respects XY layout for CIRCLE type', function () { + drawCircle(GeometryLayout.XY); + const features = source.getFeatures(); + const geometry = features[0].getGeometry(); + expect(geometry.getCenter()).to.eql([10, -20]); + expect(geometry.getRadius()).to.eql(20); + expect(geometry.getLayout()).to.eql(GeometryLayout.XY); + }); + + it('respects XYZ layout for CIRCLE type', function () { + drawCircle(GeometryLayout.XYZ); + const features = source.getFeatures(); + const geometry = features[0].getGeometry(); + expect(geometry.getCenter()).to.eql([10, -20, 0]); + expect(geometry.getRadius()).to.eql(20); + expect(geometry.getLayout()).to.eql(GeometryLayout.XYZ); + }); + + it('respects XYM layout for CIRCLE type', function () { + drawCircle(GeometryLayout.XYM); + const features = source.getFeatures(); + const geometry = features[0].getGeometry(); + expect(geometry.getCenter()).to.eql([10, -20, 0]); + expect(geometry.getRadius()).to.eql(20); + expect(geometry.getLayout()).to.eql(GeometryLayout.XYM); + }); + + it('respects XYZM layout for CIRCLE type', function () { + drawCircle(GeometryLayout.XYZM); + const features = source.getFeatures(); + const geometry = features[0].getGeometry(); + expect(geometry.getCenter()).to.eql([10, -20, 0, 0]); + expect(geometry.getRadius()).to.eql(20); + expect(geometry.getLayout()).to.eql(GeometryLayout.XYZM); + }); + }); });