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