From 1397f816b51ff76e57cd873f6a8f4c28e830a44d Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 13 Oct 2010 22:12:52 +0000 Subject: [PATCH] Adding interior ring digitizing for polygons. Thanks jachym for the initial work. r=ahocevar (closes #1894) git-svn-id: http://svn.openlayers.org/trunk/openlayers@10828 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf --- examples/donut.html | 59 ++++++++++ examples/donut.js | 38 +++++++ lib/OpenLayers/Handler/Polygon.js | 157 +++++++++++++++++++++++++- tests/Handler/Polygon.html | 181 ++++++++++++++++++++++++++++++ 4 files changed, 432 insertions(+), 3 deletions(-) create mode 100644 examples/donut.html create mode 100644 examples/donut.js diff --git a/examples/donut.html b/examples/donut.html new file mode 100644 index 0000000000..b0e60b778b --- /dev/null +++ b/examples/donut.html @@ -0,0 +1,59 @@ + + + + OpenLayers Polygon Hole Digitizing + + + + + +

Drawing Holes in Polygons

+
+ draw polygon hole +
+

+ The DrawFeature control can be used to digitize donut polygons. +

+ +
+ +
+
+

+ To digitize holes in polygons, hold down the Alt + key and draw over an existing polygon. By default, the + Shift key triggers freehand drawing. Use a + combination of the Shift and Alt keys + to digitize holes in freehand mode. +

+

+ See the + donut.js source for details on how this is done. +

+
+ + + + diff --git a/examples/donut.js b/examples/donut.js new file mode 100644 index 0000000000..88336ececd --- /dev/null +++ b/examples/donut.js @@ -0,0 +1,38 @@ +var map = new OpenLayers.Map({ + div: "map", + layers: [ + new OpenLayers.Layer.OSM(), + new OpenLayers.Layer.Vector() + ], + center: new OpenLayers.LonLat(0, 0), + zoom: 1 +}); + +var draw = new OpenLayers.Control.DrawFeature( + map.layers[1], + OpenLayers.Handler.Polygon, + {handlerOptions: {holeModifier: "altKey"}} +); +map.addControl(draw); + +// optionally listen for sketch events on the layer +var output = document.getElementById("output"); +function updateOutput(event) { + window.setTimeout(function() { + output.innerHTML = event.type + " " + event.feature.id; + }, 100); +} +map.layers[1].events.on({ + sketchmodified: updateOutput, + sketchcomplete: updateOutput +}) + +// add behavior to UI elements +function toggleControl(element) { + if (element.value === "polygon" && element.checked) { + draw.activate(); + } else { + draw.deactivate(); + } +} +document.getElementById("noneToggle").checked = true; \ No newline at end of file diff --git a/lib/OpenLayers/Handler/Polygon.js b/lib/OpenLayers/Handler/Polygon.js index 1c4abde9f9..dd53e7ecdd 100644 --- a/lib/OpenLayers/Handler/Polygon.js +++ b/lib/OpenLayers/Handler/Polygon.js @@ -20,6 +20,20 @@ */ OpenLayers.Handler.Polygon = OpenLayers.Class(OpenLayers.Handler.Path, { + /** + * APIProperty: holeModifier + * {String} Key modifier to trigger hole digitizing. Acceptable values are + * "altKey", "shiftKey", or "ctrlKey". If not set, no hole digitizing + * will take place. Default is null. + */ + holeModifier: null, + + /** + * Property: drawingHole + * {Boolean} Currently drawing an interior ring. + */ + drawingHole: false, + /** * Parameter: polygon * {} @@ -68,13 +82,150 @@ OpenLayers.Handler.Polygon = OpenLayers.Class(OpenLayers.Handler.Path, { this.line = new OpenLayers.Feature.Vector( new OpenLayers.Geometry.LinearRing([this.point.geometry]) ); - this.polygon = new OpenLayers.Feature.Vector( - new OpenLayers.Geometry.Polygon([this.line.geometry]) - ); + + // check for hole digitizing + var polygon; + if (this.holeModifier && (this.evt[this.holeModifier])) { + var geometry = this.point.geometry; + var features = this.control.layer.features; + var candidate; + // look for intersections, last drawn gets priority + for (var i=features.length-1; i>=0; --i) { + candidate = features[i].geometry; + if ((candidate instanceof OpenLayers.Geometry.Polygon || + candidate instanceof OpenLayers.Geometry.MultiPolygon) && + candidate.intersects(geometry)) { + polygon = features[i]; + this.control.layer.removeFeatures([polygon], {silent: true}); + this.control.layer.events.registerPriority( + "sketchcomplete", this, this.finalizeInteriorRing + ); + this.control.layer.events.registerPriority( + "sketchmodified", this, this.enforceTopology + ); + polygon.geometry.addComponent(this.line.geometry); + this.polygon = polygon; + this.drawingHole = true; + break; + } + } + } + if (!polygon) { + this.polygon = new OpenLayers.Feature.Vector( + new OpenLayers.Geometry.Polygon([this.line.geometry]) + ); + } + this.callback("create", [this.point.geometry, this.getSketch()]); this.point.geometry.clearBounds(); this.layer.addFeatures([this.polygon, this.point], {silent: true}); }, + + /** + * Method: enforceTopology + * Simple topology enforcement for drawing interior rings. Ensures vertices + * of interior rings are contained by exterior ring. Other topology + * rules are enforced in to allow drawing of + * rings that intersect only during the sketch (e.g. a "C" shaped ring + * that nearly encloses another ring). + */ + enforceTopology: function(event) { + var point = event.vertex; + var components = this.line.geometry.components; + // ensure that vertices of interior ring are contained by exterior ring + if (!this.polygon.geometry.intersects(point)) { + var last = components[components.length-3]; + point.x = last.x; + point.y = last.y; + } + }, + + /** + * Method: finalizeInteriorRing + * Enforces that new ring has some area and doesn't contain vertices of any + * other rings. + */ + finalizeInteriorRing: function() { + var ring = this.line.geometry; + // ensure that ring has some area + var modified = (ring.getArea() !== 0); + if (modified) { + // ensure that new ring doesn't intersect any other rings + var rings = this.polygon.geometry.components; + for (var i=rings.length-2; i>=0; --i) { + if (ring.intersects(rings[i])) { + modified = false; + break; + } + } + if (modified) { + // ensure that new ring doesn't contain any other rings + var target; + outer: for (var i=rings.length-2; i>0; --i) { + points = rings[i].components; + for (var j=0, jj=points.length; j