From 12ce4cc82a118a4f9378fb4faded6891e606f9b0 Mon Sep 17 00:00:00 2001 From: Otto Pellinen Date: Fri, 13 Sep 2019 11:40:15 +0300 Subject: [PATCH 01/15] Add support for extending linear coordinates when drawing Polygons --- src/ol/interaction/Draw.js | 42 ++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/src/ol/interaction/Draw.js b/src/ol/interaction/Draw.js index d6332fe8d8..1d071255f4 100644 --- a/src/ol/interaction/Draw.js +++ b/src/ol/interaction/Draw.js @@ -904,21 +904,45 @@ class Draw extends PointerInteraction { /** * Extend an existing geometry by adding additional points. This only works - * on features with `LineString` geometries, where the interaction will + * when drawing LineStrings or Polygons. Extending supports only input + * features with `LineString` geometries, where the interaction will * extend lines by adding points to the end of the coordinates array. * @param {!Feature} feature Feature to be extended. * @api */ extend(feature) { - const geometry = feature.getGeometry(); - const lineString = geometry; - this.sketchFeature_ = feature; - this.sketchCoords_ = lineString.getCoordinates(); - const last = this.sketchCoords_[this.sketchCoords_.length - 1]; - this.finishCoordinate_ = last.slice(); - this.sketchCoords_.push(last.slice()); + const lineStringGeometry = feature.getGeometry();; + const extendCoordinates = lineStringGeometry.getCoordinates(); + const ending = extendCoordinates[extendCoordinates.length-1].slice(); + const mode = this.mode_; + + let coordinates = []; + if (mode === Mode.LINE_STRING) { + coordinates = this.sketchCoords_; + } else if (mode === Mode.POLYGON) { + coordinates = (this.sketchCoords_)[0]; + } else { + return; + } + + // (1) Remove last coordinate, (2) extend coordinate list and (3) clone last coordinate + coordinates.pop(); + Array.prototype.push.apply(coordinates, extendCoordinates); + coordinates.push(ending) + + // Update geometry and sketch line + this.geometryFunction_(this.sketchCoords_, this.sketchFeature_.getGeometry()); + + if (mode === Mode.POLYGON) { + this.sketchLineCoords_ = this.sketchCoords_[0]; + if (this.sketchLine_ !== null) { + this.sketchLine_.getGeometry().setCoordinates(this.sketchLineCoords_); + } else { + this.sketchLine_ = new Feature( + new LineString(this.sketchLineCoords_)); + } + } this.updateSketchFeatures_(); - this.dispatchEvent(new DrawEvent(DrawEventType.DRAWSTART, this.sketchFeature_)); } /** From 00261dff541a6ee8859846518effc48aa8a1a258 Mon Sep 17 00:00:00 2001 From: Otto Pellinen Date: Fri, 13 Sep 2019 11:44:50 +0300 Subject: [PATCH 02/15] Add example for using the Draw extend function --- examples/draw-and-extend-features.html | 20 +++++ examples/draw-and-extend-features.js | 112 +++++++++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 examples/draw-and-extend-features.html create mode 100644 examples/draw-and-extend-features.js diff --git a/examples/draw-and-extend-features.html b/examples/draw-and-extend-features.html new file mode 100644 index 0000000000..6319c63250 --- /dev/null +++ b/examples/draw-and-extend-features.html @@ -0,0 +1,20 @@ +--- +layout: example.html +title: Draw and Extend Features +shortdesc: Example of using the extend function of ol/interaction/Draw interaction. +docs: > + Example of using the the extend function of Draw interaction. Select a geometry type from the + dropdown above to start drawing. To finish drawing, click the last + point. Click the green LineString Feature to extend coordinates to your drawing. + Extending is supported for drawing LineStrings and Polygons. +tags: "draw, edit, freehand, vector" +--- +
+
+ + +
diff --git a/examples/draw-and-extend-features.js b/examples/draw-and-extend-features.js new file mode 100644 index 0000000000..c5e0227274 --- /dev/null +++ b/examples/draw-and-extend-features.js @@ -0,0 +1,112 @@ +import Map from '../src/ol/Map.js'; +import View from '../src/ol/View.js'; +import Draw from '../src/ol/interaction/Draw.js'; +import Snap from '../src/ol/interaction/Snap.js'; +import LineString from '../src/ol/geom/LineString'; +import Style from '../src/ol/style/Style'; +import Stroke from '../src/ol/style/Stroke'; +import Collection from '../src/ol/Collection.js'; +import GeoJSON from '../src/ol/format/GeoJSON.js' +import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; +import {OSM, Vector as VectorSource} from '../src/ol/source.js'; + +const raster = new TileLayer({ + source: new OSM() +}); + +const sampleFeatures = new Collection(); +sampleFeatures.push( + new GeoJSON().readFeature({ + type: 'LineString', + coordinates: [ + [-12000000, 4600000], + [-12000000, 4000000], + [-10000000, 5600000], + [ -9000000, 3000000], + [-10000000, 4000000], + [-11000000, 3000000], + [-13000000, 4000000], + [-12000000, 5600000] + ] + }) +); + +const sampleVector = new VectorLayer({ + source: new VectorSource({ + features: sampleFeatures, + wrapX: false + }), + style: new Style({ + stroke: new Stroke({ + color: 'rgba(0, 256, 0, 1)', + width: 3 + }) + }) +}); + +const source = new VectorSource({wrapX: false}); + +const vector = new VectorLayer({ + source: source +}); + +const map = new Map({ + layers: [raster, sampleVector, vector], + target: 'map', + view: new View({ + center: [-11000000, 4600000], + zoom: 4 + }) +}); + +map.on('click', (event) => { + let clickedFeature = null; + map.forEachFeatureAtPixel( + event.pixel, + (feature, layer) => { + clickedFeature = feature; + }, { + hitTolerance: 10, + layerFilter: (layer) => { + return layer === sampleVector + } + } + ); + if (clickedFeature !== null) { + // In this demo we remove the new point that was clicked, + // and add the whole feature instead: + draw.removeLastPoint(); + draw.extend(clickedFeature); + } +}); + +const snapInteraction = new Snap({ + source: sampleVector.getSource() +}); + +const typeSelect = document.getElementById('type'); + +let draw; // global so we can remove it later +function addInteraction() { + const value = typeSelect.value; + if (value !== 'None') { + draw = new Draw({ + source: source, + type: typeSelect.value + }); + map.addInteraction(draw); + map.addInteraction(snapInteraction); + } +} + + +/** + * Handle change event. + */ +typeSelect.onchange = function() { + map.removeInteraction(draw); + map.removeInteraction(snapInteraction); + addInteraction(); +}; + +addInteraction(); From 25a5e8391015ed04b7dbb1202dfddc60c6e067a5 Mon Sep 17 00:00:00 2001 From: Otto Pellinen Date: Fri, 13 Sep 2019 13:11:19 +0300 Subject: [PATCH 03/15] Fix eslint errors --- examples/draw-and-extend-features.js | 16 ++++++++-------- src/ol/interaction/Draw.js | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/draw-and-extend-features.js b/examples/draw-and-extend-features.js index c5e0227274..2270da66c5 100644 --- a/examples/draw-and-extend-features.js +++ b/examples/draw-and-extend-features.js @@ -2,11 +2,10 @@ import Map from '../src/ol/Map.js'; import View from '../src/ol/View.js'; import Draw from '../src/ol/interaction/Draw.js'; import Snap from '../src/ol/interaction/Snap.js'; -import LineString from '../src/ol/geom/LineString'; -import Style from '../src/ol/style/Style'; -import Stroke from '../src/ol/style/Stroke'; +import Style from '../src/ol/style/Style.js'; +import Stroke from '../src/ol/style/Stroke.js'; import Collection from '../src/ol/Collection.js'; -import GeoJSON from '../src/ol/format/GeoJSON.js' +import GeoJSON from '../src/ol/format/GeoJSON.js'; import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; import {OSM, Vector as VectorSource} from '../src/ol/source.js'; @@ -22,7 +21,7 @@ sampleFeatures.push( [-12000000, 4600000], [-12000000, 4000000], [-10000000, 5600000], - [ -9000000, 3000000], + [-9000000, 3000000], [-10000000, 4000000], [-11000000, 3000000], [-13000000, 4000000], @@ -59,16 +58,18 @@ const map = new Map({ }) }); +let draw; // global so we can remove it later + map.on('click', (event) => { let clickedFeature = null; map.forEachFeatureAtPixel( event.pixel, - (feature, layer) => { + (feature) => { clickedFeature = feature; }, { hitTolerance: 10, layerFilter: (layer) => { - return layer === sampleVector + return layer === sampleVector; } } ); @@ -86,7 +87,6 @@ const snapInteraction = new Snap({ const typeSelect = document.getElementById('type'); -let draw; // global so we can remove it later function addInteraction() { const value = typeSelect.value; if (value !== 'None') { diff --git a/src/ol/interaction/Draw.js b/src/ol/interaction/Draw.js index 1d071255f4..112cf9a3ac 100644 --- a/src/ol/interaction/Draw.js +++ b/src/ol/interaction/Draw.js @@ -911,9 +911,9 @@ class Draw extends PointerInteraction { * @api */ extend(feature) { - const lineStringGeometry = feature.getGeometry();; + const lineStringGeometry = feature.getGeometry(); const extendCoordinates = lineStringGeometry.getCoordinates(); - const ending = extendCoordinates[extendCoordinates.length-1].slice(); + const ending = extendCoordinates[extendCoordinates.length - 1].slice(); const mode = this.mode_; let coordinates = []; @@ -928,7 +928,7 @@ class Draw extends PointerInteraction { // (1) Remove last coordinate, (2) extend coordinate list and (3) clone last coordinate coordinates.pop(); Array.prototype.push.apply(coordinates, extendCoordinates); - coordinates.push(ending) + coordinates.push(ending); // Update geometry and sketch line this.geometryFunction_(this.sketchCoords_, this.sketchFeature_.getGeometry()); From 83c0a258e6d7d0611f2824c382973278d2ac2bc9 Mon Sep 17 00:00:00 2001 From: Otto Pellinen Date: Fri, 13 Sep 2019 14:14:58 +0300 Subject: [PATCH 04/15] Separate appendCoordinates function from extend --- examples/draw-and-extend-features.html | 12 ++++---- examples/draw-and-extend-features.js | 4 +-- src/ol/interaction/Draw.js | 41 +++++++++++++++++++------- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/examples/draw-and-extend-features.html b/examples/draw-and-extend-features.html index 6319c63250..e1d1cb7706 100644 --- a/examples/draw-and-extend-features.html +++ b/examples/draw-and-extend-features.html @@ -1,12 +1,12 @@ --- layout: example.html -title: Draw and Extend Features -shortdesc: Example of using the extend function of ol/interaction/Draw interaction. +title: Draw and Append Features +shortdesc: Example of using the appendCoordinates function of ol/interaction/Draw interaction. docs: > - Example of using the the extend function of Draw interaction. Select a geometry type from the - dropdown above to start drawing. To finish drawing, click the last - point. Click the green LineString Feature to extend coordinates to your drawing. - Extending is supported for drawing LineStrings and Polygons. + Example of using the the appendCoordinates function of Draw interaction. Select a geometry type from the + dropdown above to start drawing. To finish drawing, click the last point. + Click the green LineString Feature to append its coordinates to your drawing. + Appending is supported for drawing LineStrings and Polygons. tags: "draw, edit, freehand, vector" ---
diff --git a/examples/draw-and-extend-features.js b/examples/draw-and-extend-features.js index 2270da66c5..0b7e81865c 100644 --- a/examples/draw-and-extend-features.js +++ b/examples/draw-and-extend-features.js @@ -21,7 +21,7 @@ sampleFeatures.push( [-12000000, 4600000], [-12000000, 4000000], [-10000000, 5600000], - [-9000000, 3000000], + [-9000000, 3000000], [-10000000, 4000000], [-11000000, 3000000], [-13000000, 4000000], @@ -77,7 +77,7 @@ map.on('click', (event) => { // In this demo we remove the new point that was clicked, // and add the whole feature instead: draw.removeLastPoint(); - draw.extend(clickedFeature); + draw.appendCoordinates(clickedFeature.getGeometry().getCoordinates()); } }); diff --git a/src/ol/interaction/Draw.js b/src/ol/interaction/Draw.js index 112cf9a3ac..17a541131b 100644 --- a/src/ol/interaction/Draw.js +++ b/src/ol/interaction/Draw.js @@ -903,17 +903,17 @@ class Draw extends PointerInteraction { } /** - * Extend an existing geometry by adding additional points. This only works - * when drawing LineStrings or Polygons. Extending supports only input - * features with `LineString` geometries, where the interaction will - * extend lines by adding points to the end of the coordinates array. - * @param {!Feature} feature Feature to be extended. + * Extend the geometry that is being drawn, by appending the linear coordinates + * given as the parameter to the coordinate array of currently drawn feature.. + * This can be used when drawing LineStrings or Polygons. Extending supports + * only linear coordinates, such as the coordinates from a LineString + * or a LinearRing of a Polygon. + * @param {!LineCoordType} coordinateExtension Linear coordinates to be appended into + * the coordinate array. * @api */ - extend(feature) { - const lineStringGeometry = feature.getGeometry(); - const extendCoordinates = lineStringGeometry.getCoordinates(); - const ending = extendCoordinates[extendCoordinates.length - 1].slice(); + appendCoordinates(coordinateExtension) { + const ending = coordinateExtension[coordinateExtension.length - 1].slice(); const mode = this.mode_; let coordinates = []; @@ -925,9 +925,9 @@ class Draw extends PointerInteraction { return; } - // (1) Remove last coordinate, (2) extend coordinate list and (3) clone last coordinate + // (1) Remove last coordinate, (2) append coordinate list and (3) clone last coordinate coordinates.pop(); - Array.prototype.push.apply(coordinates, extendCoordinates); + Array.prototype.push.apply(coordinates, coordinateExtension); coordinates.push(ending); // Update geometry and sketch line @@ -945,6 +945,25 @@ class Draw extends PointerInteraction { this.updateSketchFeatures_(); } + /** + * Extend an existing geometry by adding additional points. This only works + * on features with `LineString` geometries, where the interaction will + * extend lines by adding points to the end of the coordinates array. + * @param {!Feature} feature Feature to be extended. + * @api + */ + extend(feature) { + const geometry = feature.getGeometry(); + const lineString = geometry; + this.sketchFeature_ = feature; + this.sketchCoords_ = lineString.getCoordinates(); + const last = this.sketchCoords_[this.sketchCoords_.length - 1]; + this.finishCoordinate_ = last.slice(); + this.sketchCoords_.push(last.slice()); + this.updateSketchFeatures_(); + this.dispatchEvent(new DrawEvent(DrawEventType.DRAWSTART, this.sketchFeature_)); + } + /** * Redraw the sketch features. * @private From 934ad5b88298fa1831544519a101f0738b50bcde Mon Sep 17 00:00:00 2001 From: Otto Pellinen Date: Fri, 13 Sep 2019 14:15:41 +0300 Subject: [PATCH 05/15] Rename example files --- ...raw-and-extend-features.html => draw-and-append-features.html} | 0 .../{draw-and-extend-features.js => draw-and-append-features.js} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename examples/{draw-and-extend-features.html => draw-and-append-features.html} (100%) rename examples/{draw-and-extend-features.js => draw-and-append-features.js} (100%) diff --git a/examples/draw-and-extend-features.html b/examples/draw-and-append-features.html similarity index 100% rename from examples/draw-and-extend-features.html rename to examples/draw-and-append-features.html diff --git a/examples/draw-and-extend-features.js b/examples/draw-and-append-features.js similarity index 100% rename from examples/draw-and-extend-features.js rename to examples/draw-and-append-features.js From 175695f877f898bebcd58f15115f12c041ef34c0 Mon Sep 17 00:00:00 2001 From: Otto Pellinen Date: Fri, 13 Sep 2019 14:32:32 +0300 Subject: [PATCH 06/15] Update function descriptions for clarity --- src/ol/interaction/Draw.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ol/interaction/Draw.js b/src/ol/interaction/Draw.js index 17a541131b..016c9ddff4 100644 --- a/src/ol/interaction/Draw.js +++ b/src/ol/interaction/Draw.js @@ -903,9 +903,9 @@ class Draw extends PointerInteraction { } /** - * Extend the geometry that is being drawn, by appending the linear coordinates - * given as the parameter to the coordinate array of currently drawn feature.. - * This can be used when drawing LineStrings or Polygons. Extending supports + * Append the geometry that is currently being drawn with a copy of the + * linear coordinates given as the parameter. + * This can be used when drawing LineStrings or Polygons. Appending supports * only linear coordinates, such as the coordinates from a LineString * or a LinearRing of a Polygon. * @param {!LineCoordType} coordinateExtension Linear coordinates to be appended into @@ -949,6 +949,11 @@ class Draw extends PointerInteraction { * Extend an existing geometry by adding additional points. This only works * on features with `LineString` geometries, where the interaction will * extend lines by adding points to the end of the coordinates array. + * This will change the original feature, instead of drawing a copy. + * + * Extend can be used without having triggered the startDrawing_ function. + * The function will dispatch a DRAWSTART event. + * * @param {!Feature} feature Feature to be extended. * @api */ From f43bc8d122600d9911e54be5b9f4a79b85358227 Mon Sep 17 00:00:00 2001 From: Otto Pellinen Date: Wed, 4 Dec 2019 20:42:59 +0200 Subject: [PATCH 07/15] Fix comment for appendCoordinates --- src/ol/interaction/Draw.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/ol/interaction/Draw.js b/src/ol/interaction/Draw.js index 016c9ddff4..9807de8b8a 100644 --- a/src/ol/interaction/Draw.js +++ b/src/ol/interaction/Draw.js @@ -903,11 +903,10 @@ class Draw extends PointerInteraction { } /** - * Append the geometry that is currently being drawn with a copy of the - * linear coordinates given as the parameter. - * This can be used when drawing LineStrings or Polygons. Appending supports - * only linear coordinates, such as the coordinates from a LineString - * or a LinearRing of a Polygon. + * Append coordinates to the end of the geometry that is currently being drawn. + * This can be used when drawing LineStrings or Polygons. Coordinates will + * either be appended to the current LineString or the outer ring of the current + * Polygon. * @param {!LineCoordType} coordinateExtension Linear coordinates to be appended into * the coordinate array. * @api From bc79d59811ee3f4cc7bcdd9df0195353301b3bbd Mon Sep 17 00:00:00 2001 From: Otto Pellinen Date: Sat, 7 Dec 2019 16:13:43 +0200 Subject: [PATCH 08/15] Use private function addToDrawing_ in appendCoordinates --- src/ol/interaction/Draw.js | 42 ++++++++++++++------------------------ 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/src/ol/interaction/Draw.js b/src/ol/interaction/Draw.js index 9807de8b8a..3449bd565f 100644 --- a/src/ol/interaction/Draw.js +++ b/src/ol/interaction/Draw.js @@ -505,7 +505,7 @@ class Draw extends PointerInteraction { if (this.freehand_ && event.type === MapBrowserEventType.POINTERDRAG && this.sketchFeature_ !== null) { - this.addToDrawing_(event); + this.addToDrawing_(event.coordinate); pass = false; } else if (this.freehand_ && event.type === MapBrowserEventType.POINTERDOWN) { @@ -580,7 +580,7 @@ class Draw extends PointerInteraction { this.finishDrawing(); } } else { - this.addToDrawing_(event); + this.addToDrawing_(event.coordinate); } pass = false; } else if (this.freehand_) { @@ -764,11 +764,10 @@ class Draw extends PointerInteraction { /** * Add a new coordinate to the drawing. - * @param {import("../MapBrowserEvent.js").default} event Event. + * @param {!PointCoordType} coordinate Coordinate * @private */ - addToDrawing_(event) { - const coordinate = event.coordinate; + addToDrawing_(coordinate) { const geometry = this.sketchFeature_.getGeometry(); const projection = event.map.getView().getProjection(); let done; @@ -912,36 +911,25 @@ class Draw extends PointerInteraction { * @api */ appendCoordinates(coordinateExtension) { - const ending = coordinateExtension[coordinateExtension.length - 1].slice(); const mode = this.mode_; - let coordinates = []; if (mode === Mode.LINE_STRING) { - coordinates = this.sketchCoords_; + coordinates = /** @type {LineCoordType} */ this.sketchCoords_; } else if (mode === Mode.POLYGON) { - coordinates = (this.sketchCoords_)[0]; - } else { - return; + coordinates = this.sketchCoords_ && this.sketchCoords_.length ? /** @type {PolyCoordType} */ (this.sketchCoords_)[0] : []; } - // (1) Remove last coordinate, (2) append coordinate list and (3) clone last coordinate - coordinates.pop(); - Array.prototype.push.apply(coordinates, coordinateExtension); - coordinates.push(ending); + // Remove last coordinate from sketch drawing (this coordinate follows cursor position) + const ending = coordinates.pop(); - // Update geometry and sketch line - this.geometryFunction_(this.sketchCoords_, this.sketchFeature_.getGeometry()); - - if (mode === Mode.POLYGON) { - this.sketchLineCoords_ = this.sketchCoords_[0]; - if (this.sketchLine_ !== null) { - this.sketchLine_.getGeometry().setCoordinates(this.sketchLineCoords_); - } else { - this.sketchLine_ = new Feature( - new LineString(this.sketchLineCoords_)); - } + // Append coordinate list + for (let i = 0; i < coordinateExtension.length; i++) { + this.addToDrawing_(coordinateExtension[i]); } - this.updateSketchFeatures_(); + + // Duplicate last coordinate for sketch drawing + this.addToDrawing_(ending); + //this.addToDrawing_(coordinateExtension[coordinateExtension.length - 1]); } /** From 0b3bd0721e787633e287aecfa2bfc571bfd1b44e Mon Sep 17 00:00:00 2001 From: Otto Pellinen Date: Sat, 7 Dec 2019 16:15:54 +0200 Subject: [PATCH 09/15] Add dedicated tests for appendCoordinates --- test/spec/ol/interaction/draw.test.js | 125 ++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/test/spec/ol/interaction/draw.test.js b/test/spec/ol/interaction/draw.test.js index fc571891d8..7ad50aea49 100644 --- a/test/spec/ol/interaction/draw.test.js +++ b/test/spec/ol/interaction/draw.test.js @@ -1399,4 +1399,129 @@ describe('ol.interaction.Draw', function() { }); }); + + describe('append coordinates when drawing a Polygon feature', function() { + let draw; + let coordinates; + let coordinates2; + + beforeEach(function() { + draw = new Draw({ + source: source, + type: 'Polygon' + }); + map.addInteraction(draw); + coordinates = [[0, 0], [1, 1], [2, 0], [0, 3], [3, 2], [4, 4]]; + coordinates2 = [[10, 10], [11, 11], [12, 10], [10, 13], [13, 12], [14, 14]]; + }); + + function isClosed(polygon) { + const first = polygon.getFirstCoordinate(); + const last = polygon.getLastCoordinate(); + expect(first).to.eql(last); + } + + it('draws polygon with clicks, adds coordinates to drawing, finishing on first point', function() { + // first point + simulateEvent('pointermove', 10, 20); + simulateEvent('pointerdown', 10, 20); + simulateEvent('pointerup', 10, 20); + isClosed(draw.sketchFeature_.getGeometry()); + + // add coordinates + draw.appendCoordinates(coordinates); + + // finish on first point + simulateEvent('pointermove', 10, 20); + simulateEvent('pointerdown', 10, 20); + simulateEvent('pointerup', 10, 20); + + const features = source.getFeatures(); + expect(features).to.have.length(1); + const geometry = features[0].getGeometry(); + expect(geometry).to.be.a(Polygon); + + expect(geometry.getCoordinates()).to.eql([ + [[10, -20], [0, 0], [1, 1], [2, 0], [0, 3], [3, 2], [4, 4], [10, -20]] + ]); + }); + + it('adds coordinates to empty drawing', function() { + // first point + simulateEvent('pointermove', 0, 0); + simulateEvent('pointerdown', 0, 0); + simulateEvent('pointerup', 0, 0); + draw.removeLastPoint(); + draw.appendCoordinates(coordinates); + isClosed(draw.sketchFeature_.getGeometry()); + + // finish drawing + simulateEvent('pointerdown', 0, 0); + simulateEvent('pointerup', 0, 0); + + const features = source.getFeatures(); + expect(features).to.have.length(1); + const geometry = features[0].getGeometry(); + expect(geometry).to.be.a(Polygon); + + expect(geometry.getCoordinates()).to.eql([ + [[0, 0], [1, 1], [2, 0], [0, 3], [3, 2], [4, 4], [0, 0]] + ]); + }); + + it('keeps updating the sketch feature after appending coordinates', function() { + // first point + simulateEvent('pointermove', 10, 20); + simulateEvent('pointerdown', 10, 20); + simulateEvent('pointerup', 10, 20); + isClosed(draw.sketchFeature_.getGeometry()); + + // add coordinates + draw.appendCoordinates(coordinates); + + // add another point + simulateEvent('pointermove', 30, 20); + simulateEvent('pointerdown', 30, 20); + simulateEvent('pointerup', 30, 20); + + // sketchGeom should have a complete ring, with a double coordinate for cursor + const sketchGeom = draw.sketchFeature_.getGeometry(); + expect(sketchGeom.getCoordinates()).to.eql([ + [[10, -20], [0, 0], [1, 1], [2, 0], [0, 3], [3, 2], [4, 4], [30, -20], [30, -20], [10, -20]] + ]); + }); + + it('keeps updating the sketch feature after multiple appendiges', function() { + // first point + simulateEvent('pointermove', 10, 20); + simulateEvent('pointerdown', 10, 20); + simulateEvent('pointerup', 10, 20); + isClosed(draw.sketchFeature_.getGeometry()); + + // add coordinates + draw.appendCoordinates(coordinates); + + // another point + simulateEvent('pointermove', 100, 100); + simulateEvent('pointerdown', 100, 100); + simulateEvent('pointerup', 100, 100); + + // add another array of coordinates + draw.appendCoordinates(coordinates2); + + // finish on first point + simulateEvent('pointermove', 10, 20); + simulateEvent('pointerdown', 10, 20); + simulateEvent('pointerup', 10, 20); + + const features = source.getFeatures(); + expect(features).to.have.length(1); + const geometry = features[0].getGeometry(); + expect(geometry).to.be.a(Polygon); + + expect(geometry.getCoordinates()).to.eql([ + [[10, -20], [0, 0], [1, 1], [2, 0], [0, 3], [3, 2], [4, 4], [100, -100], [10, 10], [11, 11], [12, 10], [10, 13], [13, 12], [14, 14], [10, -20]] + ]); + }); + }); }); From 5ff681563f645467d573aca720e7c9ffb19dece4 Mon Sep 17 00:00:00 2001 From: Otto Pellinen Date: Sat, 7 Dec 2019 16:17:10 +0200 Subject: [PATCH 10/15] Update examples to demonstrate typical use case --- examples/draw-and-append-features.html | 2 +- examples/draw-and-append-features.js | 135 +++++++++++++++++++++---- 2 files changed, 114 insertions(+), 23 deletions(-) diff --git a/examples/draw-and-append-features.html b/examples/draw-and-append-features.html index e1d1cb7706..f309570fd8 100644 --- a/examples/draw-and-append-features.html +++ b/examples/draw-and-append-features.html @@ -5,7 +5,7 @@ shortdesc: Example of using the appendCoordinates function of ol/interaction/Dra docs: > Example of using the the appendCoordinates function of Draw interaction. Select a geometry type from the dropdown above to start drawing. To finish drawing, click the last point. - Click the green LineString Feature to append its coordinates to your drawing. + Click the purple Point to append the coordinates of the connected purple LineString feature to your drawing. Appending is supported for drawing LineStrings and Polygons. tags: "draw, edit, freehand, vector" --- diff --git a/examples/draw-and-append-features.js b/examples/draw-and-append-features.js index 0b7e81865c..4015adbc60 100644 --- a/examples/draw-and-append-features.js +++ b/examples/draw-and-append-features.js @@ -2,8 +2,10 @@ import Map from '../src/ol/Map.js'; import View from '../src/ol/View.js'; import Draw from '../src/ol/interaction/Draw.js'; import Snap from '../src/ol/interaction/Snap.js'; +import Circle from '../src/ol/style/Circle.js'; import Style from '../src/ol/style/Style.js'; import Stroke from '../src/ol/style/Stroke.js'; +import Fill from '../src/ol/style/Fill.js'; import Collection from '../src/ol/Collection.js'; import GeoJSON from '../src/ol/format/GeoJSON.js'; import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; @@ -13,32 +15,41 @@ const raster = new TileLayer({ source: new OSM() }); -const sampleFeatures = new Collection(); -sampleFeatures.push( +const baseFeature = new Collection(); +baseFeature.push( new GeoJSON().readFeature({ - type: 'LineString', - coordinates: [ - [-12000000, 4600000], - [-12000000, 4000000], - [-10000000, 5600000], - [-9000000, 3000000], - [-10000000, 4000000], - [-11000000, 3000000], + type: 'Polygon', + coordinates: [[ + [-11500000, 5000000], + [-12000000, 4500000], + [-12000000, 5500000], [-13000000, 4000000], - [-12000000, 5600000] - ] + [-11000000, 3000000], + [-10500000, 3500000], + [-10000000, 3500000], + [-10000000, 4000000], + [-11000000, 4000000], + [-10000000, 4500000], + [-10500000, 5500000], + [-11000000, 5000000], + [-11500000, 5000000] + ]] }) ); -const sampleVector = new VectorLayer({ + +const baseVector = new VectorLayer({ source: new VectorSource({ - features: sampleFeatures, + features: baseFeature, wrapX: false }), style: new Style({ stroke: new Stroke({ - color: 'rgba(0, 256, 0, 1)', + color: 'rgba(100, 255, 0, 1)', width: 3 + }), + fill: new Fill({ + color: 'rgba(100, 255, 0, 0.3)' }) }) }); @@ -49,8 +60,82 @@ const vector = new VectorLayer({ source: source }); + +const appendLine = new Collection(); +const LineA = new GeoJSON().readFeature({ + type: 'LineString', + coordinates: [ + [-10000000, 3500000], + [-10000000, 4000000], + [-11000000, 4000000], + [-10000000, 4500000], + [-10500000, 5500000], + [-11000000, 5000000], + [-11500000, 5000000], + [-12000000, 4500000], + [-12000000, 5500000] + ] +}); +appendLine.push(LineA); + +const LineB = new GeoJSON().readFeature({ + type: 'LineString', + coordinates: [ + [-13000000, 4000000], + [-11000000, 3000000], + [-10500000, 3500000] + ] +}); +appendLine.push(LineB); + +const appendHandleFeature = new Collection(); +const appendHandle = new GeoJSON().readFeature({ + type: 'Point', + coordinates: [-10000000, 3500000] +}); +appendHandleFeature.push(appendHandle); + +const appendHandleB = new GeoJSON().readFeature({ + type: 'Point', + coordinates: [-13000000, 4000000] +}); +appendHandleFeature.push(appendHandleB); + +const handleVector = new VectorLayer({ + source: new VectorSource({ + features: appendHandleFeature, + wrapX: false + }), + style: new Style({ + image: new Circle({ + radius: 5, + fill: new Fill({ + color: 'rgba(100, 0, 255, 1)' + }), + stroke: new Stroke({ + color: 'rgba(100, 0, 255, 1)', + width: 2 + }) + }), + radius: 7 + }) +}); + +const appendLineVector = new VectorLayer({ + source: new VectorSource({ + features: appendLine, + wrapX: false + }), + style: new Style({ + stroke: new Stroke({ + color: 'rgba(100, 0, 255, 1)', + width: 2 + }) + }) +}); + const map = new Map({ - layers: [raster, sampleVector, vector], + layers: [raster, baseVector, appendLineVector, handleVector, vector], target: 'map', view: new View({ center: [-11000000, 4600000], @@ -69,20 +154,26 @@ map.on('click', (event) => { }, { hitTolerance: 10, layerFilter: (layer) => { - return layer === sampleVector; + return layer === handleVector; } } ); - if (clickedFeature !== null) { - // In this demo we remove the new point that was clicked, - // and add the whole feature instead: + + if (clickedFeature == appendHandle) { + // In this demo we remove the new point that was clicked from the drawing, + // and add the connected feature coordinates: draw.removeLastPoint(); - draw.appendCoordinates(clickedFeature.getGeometry().getCoordinates()); + draw.appendCoordinates(LineA.getGeometry().getCoordinates()); + } else if (clickedFeature == appendHandleB) { + // In this demo we remove the new point that was clicked from the drawing, + // and add the connected feature coordinates: + draw.removeLastPoint(); + draw.appendCoordinates(LineB.getGeometry().getCoordinates()); } }); const snapInteraction = new Snap({ - source: sampleVector.getSource() + source: handleVector.getSource() }); const typeSelect = document.getElementById('type'); From 02597229e3cb804cd616748b6536683e90977fc5 Mon Sep 17 00:00:00 2001 From: Otto Pellinen Date: Wed, 8 Jan 2020 17:24:07 +0200 Subject: [PATCH 11/15] Cleanup comments --- src/ol/interaction/Draw.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/ol/interaction/Draw.js b/src/ol/interaction/Draw.js index 3449bd565f..f76659ac48 100644 --- a/src/ol/interaction/Draw.js +++ b/src/ol/interaction/Draw.js @@ -929,17 +929,16 @@ class Draw extends PointerInteraction { // Duplicate last coordinate for sketch drawing this.addToDrawing_(ending); - //this.addToDrawing_(coordinateExtension[coordinateExtension.length - 1]); } /** - * Extend an existing geometry by adding additional points. This only works - * on features with `LineString` geometries, where the interaction will - * extend lines by adding points to the end of the coordinates array. + * Initiate draw mode by starting from an existing geometry which will + * receive new additional points. This only works on features with + * `LineString` geometries, where the interaction will extend lines by adding + * points to the end of the coordinates array. * This will change the original feature, instead of drawing a copy. * - * Extend can be used without having triggered the startDrawing_ function. - * The function will dispatch a DRAWSTART event. + * The function will dispatch a `drawstart` event. * * @param {!Feature} feature Feature to be extended. * @api From 8722d16158aaf2b6d25e32b84a0119c9e39bf703 Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Mon, 10 Feb 2020 16:40:45 +0100 Subject: [PATCH 12/15] Rewrite the example showcasing DrawInteraction#appendCoordinates The example is now focused on showing how a kind of "tracing" mode can be achieved using the Draw interaction, making it easier for the user to snap to an existing geometry while preserving topology. --- examples/draw-and-append-features.html | 20 -- examples/draw-and-append-features.js | 203 ---------------- examples/tracing.html | 22 ++ examples/tracing.js | 306 +++++++++++++++++++++++++ 4 files changed, 328 insertions(+), 223 deletions(-) delete mode 100644 examples/draw-and-append-features.html delete mode 100644 examples/draw-and-append-features.js create mode 100644 examples/tracing.html create mode 100644 examples/tracing.js diff --git a/examples/draw-and-append-features.html b/examples/draw-and-append-features.html deleted file mode 100644 index f309570fd8..0000000000 --- a/examples/draw-and-append-features.html +++ /dev/null @@ -1,20 +0,0 @@ ---- -layout: example.html -title: Draw and Append Features -shortdesc: Example of using the appendCoordinates function of ol/interaction/Draw interaction. -docs: > - Example of using the the appendCoordinates function of Draw interaction. Select a geometry type from the - dropdown above to start drawing. To finish drawing, click the last point. - Click the purple Point to append the coordinates of the connected purple LineString feature to your drawing. - Appending is supported for drawing LineStrings and Polygons. -tags: "draw, edit, freehand, vector" ---- -
-
- - -
diff --git a/examples/draw-and-append-features.js b/examples/draw-and-append-features.js deleted file mode 100644 index 4015adbc60..0000000000 --- a/examples/draw-and-append-features.js +++ /dev/null @@ -1,203 +0,0 @@ -import Map from '../src/ol/Map.js'; -import View from '../src/ol/View.js'; -import Draw from '../src/ol/interaction/Draw.js'; -import Snap from '../src/ol/interaction/Snap.js'; -import Circle from '../src/ol/style/Circle.js'; -import Style from '../src/ol/style/Style.js'; -import Stroke from '../src/ol/style/Stroke.js'; -import Fill from '../src/ol/style/Fill.js'; -import Collection from '../src/ol/Collection.js'; -import GeoJSON from '../src/ol/format/GeoJSON.js'; -import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; -import {OSM, Vector as VectorSource} from '../src/ol/source.js'; - -const raster = new TileLayer({ - source: new OSM() -}); - -const baseFeature = new Collection(); -baseFeature.push( - new GeoJSON().readFeature({ - type: 'Polygon', - coordinates: [[ - [-11500000, 5000000], - [-12000000, 4500000], - [-12000000, 5500000], - [-13000000, 4000000], - [-11000000, 3000000], - [-10500000, 3500000], - [-10000000, 3500000], - [-10000000, 4000000], - [-11000000, 4000000], - [-10000000, 4500000], - [-10500000, 5500000], - [-11000000, 5000000], - [-11500000, 5000000] - ]] - }) -); - - -const baseVector = new VectorLayer({ - source: new VectorSource({ - features: baseFeature, - wrapX: false - }), - style: new Style({ - stroke: new Stroke({ - color: 'rgba(100, 255, 0, 1)', - width: 3 - }), - fill: new Fill({ - color: 'rgba(100, 255, 0, 0.3)' - }) - }) -}); - -const source = new VectorSource({wrapX: false}); - -const vector = new VectorLayer({ - source: source -}); - - -const appendLine = new Collection(); -const LineA = new GeoJSON().readFeature({ - type: 'LineString', - coordinates: [ - [-10000000, 3500000], - [-10000000, 4000000], - [-11000000, 4000000], - [-10000000, 4500000], - [-10500000, 5500000], - [-11000000, 5000000], - [-11500000, 5000000], - [-12000000, 4500000], - [-12000000, 5500000] - ] -}); -appendLine.push(LineA); - -const LineB = new GeoJSON().readFeature({ - type: 'LineString', - coordinates: [ - [-13000000, 4000000], - [-11000000, 3000000], - [-10500000, 3500000] - ] -}); -appendLine.push(LineB); - -const appendHandleFeature = new Collection(); -const appendHandle = new GeoJSON().readFeature({ - type: 'Point', - coordinates: [-10000000, 3500000] -}); -appendHandleFeature.push(appendHandle); - -const appendHandleB = new GeoJSON().readFeature({ - type: 'Point', - coordinates: [-13000000, 4000000] -}); -appendHandleFeature.push(appendHandleB); - -const handleVector = new VectorLayer({ - source: new VectorSource({ - features: appendHandleFeature, - wrapX: false - }), - style: new Style({ - image: new Circle({ - radius: 5, - fill: new Fill({ - color: 'rgba(100, 0, 255, 1)' - }), - stroke: new Stroke({ - color: 'rgba(100, 0, 255, 1)', - width: 2 - }) - }), - radius: 7 - }) -}); - -const appendLineVector = new VectorLayer({ - source: new VectorSource({ - features: appendLine, - wrapX: false - }), - style: new Style({ - stroke: new Stroke({ - color: 'rgba(100, 0, 255, 1)', - width: 2 - }) - }) -}); - -const map = new Map({ - layers: [raster, baseVector, appendLineVector, handleVector, vector], - target: 'map', - view: new View({ - center: [-11000000, 4600000], - zoom: 4 - }) -}); - -let draw; // global so we can remove it later - -map.on('click', (event) => { - let clickedFeature = null; - map.forEachFeatureAtPixel( - event.pixel, - (feature) => { - clickedFeature = feature; - }, { - hitTolerance: 10, - layerFilter: (layer) => { - return layer === handleVector; - } - } - ); - - if (clickedFeature == appendHandle) { - // In this demo we remove the new point that was clicked from the drawing, - // and add the connected feature coordinates: - draw.removeLastPoint(); - draw.appendCoordinates(LineA.getGeometry().getCoordinates()); - } else if (clickedFeature == appendHandleB) { - // In this demo we remove the new point that was clicked from the drawing, - // and add the connected feature coordinates: - draw.removeLastPoint(); - draw.appendCoordinates(LineB.getGeometry().getCoordinates()); - } -}); - -const snapInteraction = new Snap({ - source: handleVector.getSource() -}); - -const typeSelect = document.getElementById('type'); - -function addInteraction() { - const value = typeSelect.value; - if (value !== 'None') { - draw = new Draw({ - source: source, - type: typeSelect.value - }); - map.addInteraction(draw); - map.addInteraction(snapInteraction); - } -} - - -/** - * Handle change event. - */ -typeSelect.onchange = function() { - map.removeInteraction(draw); - map.removeInteraction(snapInteraction); - addInteraction(); -}; - -addInteraction(); diff --git a/examples/tracing.html b/examples/tracing.html new file mode 100644 index 0000000000..ba08af16af --- /dev/null +++ b/examples/tracing.html @@ -0,0 +1,22 @@ +--- +layout: example.html +title: Tracing around a polygon +shortdesc: Example of setting up a draw interaction to easily snap to an existing feature. +docs: > + This example showcases how the draw interaction API can be set up to make snapping along + an existing geometry easier while preserving topology, which is sometimes called "tracing". + When the user clicks on two different points on the Idaho state border, + the part of the border comprised between these two points is added to + the currently drawn feature. + This leverages the `appendCoordinates` method of the `ol/interaction/Draw` interaction. +tags: "draw, edit, freehand, vector" +--- +
+
+ + +
diff --git a/examples/tracing.js b/examples/tracing.js new file mode 100644 index 0000000000..59ad451d94 --- /dev/null +++ b/examples/tracing.js @@ -0,0 +1,306 @@ +import Map from '../src/ol/Map.js'; +import View from '../src/ol/View.js'; +import Draw from '../src/ol/interaction/Draw.js'; +import Snap from '../src/ol/interaction/Snap.js'; +import Style from '../src/ol/style/Style.js'; +import Stroke from '../src/ol/style/Stroke.js'; +import Fill from '../src/ol/style/Fill.js'; +import GeoJSON from '../src/ol/format/GeoJSON.js'; +import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; +import {OSM, Vector as VectorSource} from '../src/ol/source.js'; +import LineString from '../src/ol/geom/LineString.js'; +import Feature from '../src/ol/Feature.js'; + + +// math utilities + +// coordinates; will return the length of the [a, b] segment +function length(a, b) { + return Math.sqrt((b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1])); +} + +// coordinates; will return true if c is on the [a, b] segment +function isOnSegment(c, a, b) { + const lengthAc = length(a, c); + const lengthAb = length(a, b); + const dot = ((c[0] - a[0]) * (b[0] - a[0]) + (c[1] - a[1]) * (b[1] - a[1])) / lengthAb; + return Math.abs(lengthAc - dot) < 1e-6 && lengthAc < lengthAb; +} + +// modulo for negative values, eg: mod(-1, 4) returns 3 +function mod(a, b) { + return ((a % b) + b) % b; +} + +// returns a coordinates array which contains the segments of the feature's +// outer ring between the start and end points +// Note: this assumes the base feature is a single polygon +function getPartialRingCoords(feature, startPoint, endPoint) { + const ringCoords = feature.getGeometry().getLinearRing().getCoordinates(); + + let i, pointA, pointB, startSegmentIndex = -1; + for (i = 0; i < ringCoords.length; i++) { + pointA = ringCoords[i]; + pointB = ringCoords[mod(i + 1, ringCoords.length)]; + + // check if this is the start segment dot product + if (isOnSegment(startPoint, pointA, pointB)) { + startSegmentIndex = i; + break; + } + } + + const cwCoordinates = []; + let cwLength = 0; + const ccwCoordinates = []; + let ccwLength = 0; + + // build clockwise coordinates + for (i = 0; i < ringCoords.length; i++) { + pointA = i === 0 ? startPoint : ringCoords[mod(i + startSegmentIndex, ringCoords.length)]; + pointB = ringCoords[mod(i + startSegmentIndex + 1, ringCoords.length)]; + cwCoordinates.push(pointA); + + if (isOnSegment(endPoint, pointA, pointB)) { + cwCoordinates.push(endPoint); + cwLength += length(pointA, endPoint); + break; + } else { + cwLength += length(pointA, pointB); + } + } + + // build counter-clockwise coordinates + for (i = 0; i < ringCoords.length; i++) { + pointA = ringCoords[mod(startSegmentIndex - i, ringCoords.length)]; + pointB = i === 0 ? startPoint : ringCoords[mod(startSegmentIndex - i + 1, ringCoords.length)]; + ccwCoordinates.push(pointB); + + if (isOnSegment(endPoint, pointA, pointB)) { + ccwCoordinates.push(endPoint); + ccwLength += length(endPoint, pointB); + break; + } else { + ccwLength += length(pointA, pointB); + } + } + + return ccwLength < cwLength ? ccwCoordinates : cwCoordinates; +} + + +// layers definition + +const raster = new TileLayer({ + source: new OSM() +}); + +// Idaho state GeoJSON +const baseFeature = new GeoJSON().readFeature({ + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [[ + [-111.08518023825431, 44.506142112334651], + [-111.049728402187043, 44.48816653348365], + [-111.050246928500059, 42.001596004902879], + [-114.034225025564496, 41.993120340230973], + [-117.028253570918153, 42.000021221285593], + [-117.013965290292987, 43.79706698163281], + [-116.926654421365328, 44.081212999602798], + [-117.00760854178904, 44.211414316643626], + [-117.194393242542148, 44.279130012187053], + [-117.192299932611846, 44.438938541544815], + [-117.051529640968823, 44.665957043251822], + [-116.836142772727712, 44.863848449598407], + [-116.693285572713776, 45.186647083345598], + [-116.558872029574673, 45.444357861691202], + [-116.457797807894295, 45.574530371714637], + [-116.511257230609658, 45.726407368951158], + [-116.678997292088653, 45.807361489374898], + [-116.91500358322115, 45.999984412318923], + [-116.906527918549244, 46.17777492208711], + [-116.998070218253446, 46.330170445636647], + [-117.026646779503764, 47.722925720821678], + [-117.031428744390425, 48.999307047312811], + [-116.04823243777011, 49.000369706176514], + [-115.967796843659372, 47.950481953519962], + [-115.704276650140244, 47.684842843832847], + [-115.70479517645326, 47.504930217116986], + [-115.519066733004436, 47.345118486979523], + [-115.288328925274485, 47.250397813023341], + [-115.121651522659207, 47.095368047772524], + [-114.843318120642479, 46.786319963659238], + [-114.585610543076569, 46.641337445917912], + [-114.284519597322202, 46.631805523941722], + [-114.394043877436815, 46.41008751343437], + [-114.491430800891379, 46.147079444668812], + [-114.407275900757696, 45.889912798873901], + [-114.523701061926985, 45.825369076023016], + [-114.513650613637765, 45.569236282074407], + [-114.335315971318892, 45.470274575002556], + [-114.137936689725905, 45.589337178652805], + [-114.035799809181796, 45.730101068736438], + [-113.914099763050558, 45.702587166349815], + [-113.794493026849608, 45.564998449738447], + [-113.680212388086019, 45.249075090808205], + [-113.502934003071431, 45.124225477442394], + [-113.439424132066847, 44.862792192294137], + [-113.378577309780937, 44.789769603991623], + [-113.174841279684017, 44.765430875077264], + [-113.052084976248494, 44.619910626344669], + [-112.874812992793338, 44.360084132610794], + [-112.690147208208202, 44.498729106526447], + [-112.362592215812043, 44.462221013154888], + [-112.336134570729712, 44.560638587676046], + [-111.771491423659086, 44.498216981772842], + [-111.542386013581194, 44.530487242808441], + [-111.400015332083171, 44.728922781705712], + [-111.291547309272815, 44.701402477759679], + [-111.194192393615367, 44.561157113989054], + [-111.08518023825431, 44.506142112334651] + ]] + } +}, { + featureProjection: 'EPSG:3857' +}); + +// this is were the drawn features go +const baseVector = new VectorLayer({ + source: new VectorSource({ + features: [baseFeature] + }) +}); +const drawVector = new VectorLayer({ + source: new VectorSource(), + style: new Style({ + stroke: new Stroke({ + color: 'rgba(100, 255, 0, 1)', + width: 2 + }), + fill: new Fill({ + color: 'rgba(100, 255, 0, 0.3)' + }) + }) +}); + +// this line only appears when we're tracing a feature outer ring +const previewLine = new Feature({ + geometry: new LineString([]) +}); +const previewVector = new VectorLayer({ + source: new VectorSource({ + features: [previewLine] + }), + style: new Style({ + stroke: new Stroke({ + color: 'rgba(255, 0, 0, 1)', + width: 2 + }) + }) +}); + +const map = new Map({ + layers: [raster, baseVector, drawVector, previewVector], + target: 'map', + view: new View({ + center: [-12986427, 5678422], + zoom: 5 + }) +}); + +let drawInteraction, tracingFeature, startPoint, endPoint; + +const getFeatureOptions = { + hitTolerance: 10, + layerFilter: (layer) => { + return layer === baseVector; + } +}; + +// the click event is used to start/end tracing around a feature +map.on('click', (event) => { + let hit = false; + map.forEachFeatureAtPixel( + event.pixel, + (feature) => { + if (tracingFeature && feature !== tracingFeature) { + return; + } + + hit = true; + const coord = map.getCoordinateFromPixel(event.pixel); + + // second click on the tracing feature: append the ring coordinates + if (feature === tracingFeature) { + endPoint = tracingFeature.getGeometry().getClosestPoint(coord); + const appendCoords = getPartialRingCoords(tracingFeature, startPoint, endPoint); + drawInteraction.removeLastPoint(); + drawInteraction.appendCoordinates(appendCoords); + tracingFeature = null; + } + + // start tracing on the feature ring + tracingFeature = feature; + startPoint = tracingFeature.getGeometry().getClosestPoint(coord); + }, + getFeatureOptions + ); + + if (!hit) { + // clear current tracing feature & preview + previewLine.getGeometry().setCoordinates([]); + tracingFeature = null; + } +}); + +// the pointermove event is used to show a preview of the result of the tracing +map.on('pointermove', (event) => { + if (tracingFeature) { + let coord = null; + map.forEachFeatureAtPixel( + event.pixel, + (feature) => { + if (tracingFeature === feature) { + coord = map.getCoordinateFromPixel(event.pixel); + } + }, + getFeatureOptions + ); + + let previewCoords = []; + if (coord) { + endPoint = tracingFeature.getGeometry().getClosestPoint(coord); + previewCoords = getPartialRingCoords(tracingFeature, startPoint, endPoint); + } + previewLine.getGeometry().setCoordinates(previewCoords); + } +}); + +const snapInteraction = new Snap({ + source: baseVector.getSource() +}); + +const typeSelect = document.getElementById('type'); + +function addInteraction() { + const value = typeSelect.value; + if (value !== 'None') { + drawInteraction = new Draw({ + source: drawVector.getSource(), + type: typeSelect.value + }); + drawInteraction.on('drawend', () => { + tracingFeature = null; + }); + map.addInteraction(drawInteraction); + map.addInteraction(snapInteraction); + } +} + +typeSelect.onchange = function() { + map.removeInteraction(drawInteraction); + map.removeInteraction(snapInteraction); + addInteraction(); +}; +addInteraction(); From 3c5d0f223e35b0bb8f2da9329f9d1af9bddc8a04 Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Mon, 10 Feb 2020 16:55:35 +0100 Subject: [PATCH 13/15] Draw / fix map object in addToDrawing method --- src/ol/interaction/Draw.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ol/interaction/Draw.js b/src/ol/interaction/Draw.js index f76659ac48..e8611b6c33 100644 --- a/src/ol/interaction/Draw.js +++ b/src/ol/interaction/Draw.js @@ -769,7 +769,7 @@ class Draw extends PointerInteraction { */ addToDrawing_(coordinate) { const geometry = this.sketchFeature_.getGeometry(); - const projection = event.map.getView().getProjection(); + const projection = this.getMap().getView().getProjection(); let done; let coordinates; if (this.mode_ === Mode.LINE_STRING) { From 6641832621add9c4a8de2bd18151596adeb0e731 Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Tue, 11 Feb 2020 11:03:36 +0100 Subject: [PATCH 14/15] Improvements to the tracing example * no embedded geojson * better behaviour on draw end (no lingering preview line) * clearer comments * support for multi polygons to snap to --- examples/tracing.html | 2 +- examples/tracing.js | 99 ++++++++++--------------------------------- 2 files changed, 24 insertions(+), 77 deletions(-) diff --git a/examples/tracing.html b/examples/tracing.html index ba08af16af..b0a97a6409 100644 --- a/examples/tracing.html +++ b/examples/tracing.html @@ -9,7 +9,7 @@ docs: > the part of the border comprised between these two points is added to the currently drawn feature. This leverages the `appendCoordinates` method of the `ol/interaction/Draw` interaction. -tags: "draw, edit, freehand, vector" +tags: "draw, trace, snap, vector, topology" ---
diff --git a/examples/tracing.js b/examples/tracing.js index 59ad451d94..3379631b0c 100644 --- a/examples/tracing.js +++ b/examples/tracing.js @@ -11,7 +11,6 @@ import {OSM, Vector as VectorSource} from '../src/ol/source.js'; import LineString from '../src/ol/geom/LineString.js'; import Feature from '../src/ol/Feature.js'; - // math utilities // coordinates; will return the length of the [a, b] segment @@ -36,7 +35,11 @@ function mod(a, b) { // outer ring between the start and end points // Note: this assumes the base feature is a single polygon function getPartialRingCoords(feature, startPoint, endPoint) { - const ringCoords = feature.getGeometry().getLinearRing().getCoordinates(); + let polygon = feature.getGeometry(); + if (polygon.getType() === 'MultiPolygon') { + polygon = polygon.getPolygon(0); + } + const ringCoords = polygon.getLinearRing().getCoordinates(); let i, pointA, pointB, startSegmentIndex = -1; for (i = 0; i < ringCoords.length; i++) { @@ -85,6 +88,7 @@ function getPartialRingCoords(feature, startPoint, endPoint) { } } + // keep the shortest path return ccwLength < cwLength ? ccwCoordinates : cwCoordinates; } @@ -95,82 +99,15 @@ const raster = new TileLayer({ source: new OSM() }); -// Idaho state GeoJSON -const baseFeature = new GeoJSON().readFeature({ - type: 'Feature', - geometry: { - type: 'Polygon', - coordinates: [[ - [-111.08518023825431, 44.506142112334651], - [-111.049728402187043, 44.48816653348365], - [-111.050246928500059, 42.001596004902879], - [-114.034225025564496, 41.993120340230973], - [-117.028253570918153, 42.000021221285593], - [-117.013965290292987, 43.79706698163281], - [-116.926654421365328, 44.081212999602798], - [-117.00760854178904, 44.211414316643626], - [-117.194393242542148, 44.279130012187053], - [-117.192299932611846, 44.438938541544815], - [-117.051529640968823, 44.665957043251822], - [-116.836142772727712, 44.863848449598407], - [-116.693285572713776, 45.186647083345598], - [-116.558872029574673, 45.444357861691202], - [-116.457797807894295, 45.574530371714637], - [-116.511257230609658, 45.726407368951158], - [-116.678997292088653, 45.807361489374898], - [-116.91500358322115, 45.999984412318923], - [-116.906527918549244, 46.17777492208711], - [-116.998070218253446, 46.330170445636647], - [-117.026646779503764, 47.722925720821678], - [-117.031428744390425, 48.999307047312811], - [-116.04823243777011, 49.000369706176514], - [-115.967796843659372, 47.950481953519962], - [-115.704276650140244, 47.684842843832847], - [-115.70479517645326, 47.504930217116986], - [-115.519066733004436, 47.345118486979523], - [-115.288328925274485, 47.250397813023341], - [-115.121651522659207, 47.095368047772524], - [-114.843318120642479, 46.786319963659238], - [-114.585610543076569, 46.641337445917912], - [-114.284519597322202, 46.631805523941722], - [-114.394043877436815, 46.41008751343437], - [-114.491430800891379, 46.147079444668812], - [-114.407275900757696, 45.889912798873901], - [-114.523701061926985, 45.825369076023016], - [-114.513650613637765, 45.569236282074407], - [-114.335315971318892, 45.470274575002556], - [-114.137936689725905, 45.589337178652805], - [-114.035799809181796, 45.730101068736438], - [-113.914099763050558, 45.702587166349815], - [-113.794493026849608, 45.564998449738447], - [-113.680212388086019, 45.249075090808205], - [-113.502934003071431, 45.124225477442394], - [-113.439424132066847, 44.862792192294137], - [-113.378577309780937, 44.789769603991623], - [-113.174841279684017, 44.765430875077264], - [-113.052084976248494, 44.619910626344669], - [-112.874812992793338, 44.360084132610794], - [-112.690147208208202, 44.498729106526447], - [-112.362592215812043, 44.462221013154888], - [-112.336134570729712, 44.560638587676046], - [-111.771491423659086, 44.498216981772842], - [-111.542386013581194, 44.530487242808441], - [-111.400015332083171, 44.728922781705712], - [-111.291547309272815, 44.701402477759679], - [-111.194192393615367, 44.561157113989054], - [-111.08518023825431, 44.506142112334651] - ]] - } -}, { - featureProjection: 'EPSG:3857' +// features in this layer will be snapped to +const baseVector = new VectorLayer({ + source: new VectorSource({ + format: new GeoJSON(), + url: 'https://ahocevar.com/geoserver/wfs?service=wfs&request=getfeature&typename=topp:states&cql_filter=STATE_NAME=\'Idaho\'&outputformat=application/json' + }) }); // this is were the drawn features go -const baseVector = new VectorLayer({ - source: new VectorSource({ - features: [baseFeature] - }) -}); const drawVector = new VectorLayer({ source: new VectorSource(), style: new Style({ @@ -210,6 +147,7 @@ const map = new Map({ }); let drawInteraction, tracingFeature, startPoint, endPoint; +let drawing = false; const getFeatureOptions = { hitTolerance: 10, @@ -220,6 +158,10 @@ const getFeatureOptions = { // the click event is used to start/end tracing around a feature map.on('click', (event) => { + if (!drawing) { + return; + } + let hit = false; map.forEachFeatureAtPixel( event.pixel, @@ -256,7 +198,7 @@ map.on('click', (event) => { // the pointermove event is used to show a preview of the result of the tracing map.on('pointermove', (event) => { - if (tracingFeature) { + if (tracingFeature && drawing) { let coord = null; map.forEachFeatureAtPixel( event.pixel, @@ -290,7 +232,12 @@ function addInteraction() { source: drawVector.getSource(), type: typeSelect.value }); + drawInteraction.on('drawstart', () => { + drawing = true; + }); drawInteraction.on('drawend', () => { + drawing = false; + previewLine.getGeometry().setCoordinates([]); tracingFeature = null; }); map.addInteraction(drawInteraction); From 8d4c3b28875500484372bea736e1706b9e3dbca1 Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Tue, 11 Feb 2020 11:19:55 +0100 Subject: [PATCH 15/15] Draw / clearer variable names --- src/ol/interaction/Draw.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ol/interaction/Draw.js b/src/ol/interaction/Draw.js index e8611b6c33..5848ab2f37 100644 --- a/src/ol/interaction/Draw.js +++ b/src/ol/interaction/Draw.js @@ -906,25 +906,25 @@ class Draw extends PointerInteraction { * This can be used when drawing LineStrings or Polygons. Coordinates will * either be appended to the current LineString or the outer ring of the current * Polygon. - * @param {!LineCoordType} coordinateExtension Linear coordinates to be appended into + * @param {!LineCoordType} coordinates Linear coordinates to be appended into * the coordinate array. * @api */ - appendCoordinates(coordinateExtension) { + appendCoordinates(coordinates) { const mode = this.mode_; - let coordinates = []; + let sketchCoords = []; if (mode === Mode.LINE_STRING) { - coordinates = /** @type {LineCoordType} */ this.sketchCoords_; + sketchCoords = /** @type {LineCoordType} */ this.sketchCoords_; } else if (mode === Mode.POLYGON) { - coordinates = this.sketchCoords_ && this.sketchCoords_.length ? /** @type {PolyCoordType} */ (this.sketchCoords_)[0] : []; + sketchCoords = this.sketchCoords_ && this.sketchCoords_.length ? /** @type {PolyCoordType} */ (this.sketchCoords_)[0] : []; } // Remove last coordinate from sketch drawing (this coordinate follows cursor position) - const ending = coordinates.pop(); + const ending = sketchCoords.pop(); // Append coordinate list - for (let i = 0; i < coordinateExtension.length; i++) { - this.addToDrawing_(coordinateExtension[i]); + for (let i = 0; i < coordinates.length; i++) { + this.addToDrawing_(coordinates[i]); } // Duplicate last coordinate for sketch drawing