[OpenLayers-Commits] r10828 - in trunk/openlayers: examples
lib/OpenLayers/Handler tests/Handler
commits-20090109 at openlayers.org
commits-20090109 at openlayers.org
Wed Oct 13 18:12:53 EDT 2010
Author: tschaub
Date: 2010-10-13 15:12:52 -0700 (Wed, 13 Oct 2010)
New Revision: 10828
Added:
trunk/openlayers/examples/donut.html
trunk/openlayers/examples/donut.js
Modified:
trunk/openlayers/lib/OpenLayers/Handler/Polygon.js
trunk/openlayers/tests/Handler/Polygon.html
Log:
Adding interior ring digitizing for polygons. Thanks jachym for the initial work. r=ahocevar (closes #1894)
Added: trunk/openlayers/examples/donut.html
===================================================================
--- trunk/openlayers/examples/donut.html (rev 0)
+++ trunk/openlayers/examples/donut.html 2010-10-13 22:12:52 UTC (rev 10828)
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>OpenLayers Polygon Hole Digitizing</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style>
+ #controlToggle li {
+ list-style: none;
+ }
+ .olControlAttribution {
+ font-size: 9px;
+ bottom: 2px;
+ }
+ #output {
+ margin: 1em;
+ font-size: 0.9em;
+ }
+ </style>
+ </head>
+ <body>
+ <h1 id="title">Drawing Holes in Polygons</h1>
+ <div id="tags">
+ draw polygon hole
+ </div>
+ <p id="shortdesc">
+ The DrawFeature control can be used to digitize donut polygons.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+ <ul id="controlToggle">
+ <li>
+ <input type="radio" name="type" value="none" id="noneToggle"
+ onclick="toggleControl(this);" checked="checked">
+ <label for="noneToggle">navigate</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="polygon" id="polygonToggle" onclick="toggleControl(this);">
+ <label for="polygonToggle">draw polygon</label>
+ </li>
+ </ul>
+ <div id="output"></div>
+ <div id="docs">
+ <p>
+ To digitize holes in polygons, hold down the <code>Alt</code>
+ key and draw over an existing polygon. By default, the
+ <code>Shift</code> key triggers freehand drawing. Use a
+ combination of the <code>Shift</code> and <code>Alt</code> keys
+ to digitize holes in freehand mode.
+ </p>
+ <p>
+ See the <a href="donut.js" target="_blank">
+ donut.js source</a> for details on how this is done.
+ </p>
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="donut.js"></script>
+ </body>
+</html>
Added: trunk/openlayers/examples/donut.js
===================================================================
--- trunk/openlayers/examples/donut.js (rev 0)
+++ trunk/openlayers/examples/donut.js 2010-10-13 22:12:52 UTC (rev 10828)
@@ -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
Modified: trunk/openlayers/lib/OpenLayers/Handler/Polygon.js
===================================================================
--- trunk/openlayers/lib/OpenLayers/Handler/Polygon.js 2010-10-13 21:25:21 UTC (rev 10827)
+++ trunk/openlayers/lib/OpenLayers/Handler/Polygon.js 2010-10-13 22:12:52 UTC (rev 10828)
@@ -20,7 +20,21 @@
*/
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
* {<OpenLayers.Feature.Vector>}
*/
@@ -68,15 +82,152 @@
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 <finalizeInteriorRing> 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<jj; ++j) {
+ if (ring.containsPoint(points[j])) {
+ modified = false;
+ break outer;
+ }
+ }
+ }
+ }
+ }
+ if (modified) {
+ if (this.polygon.state !== OpenLayers.State.INSERT) {
+ this.polygon.state = OpenLayers.State.UPDATE;
+ }
+ } else {
+ this.polygon.geometry.removeComponent(ring);
+ }
+ this.restoreFeature();
+ return false;
+ },
/**
+ * APIMethod: cancel
+ * Finish the geometry and call the "cancel" callback.
+ */
+ cancel: function() {
+ if (this.drawingHole) {
+ this.polygon.geometry.removeComponent(this.line.geometry);
+ this.restoreFeature(true);
+ }
+ return OpenLayers.Handler.Path.prototype.cancel.apply(this, arguments);
+ },
+
+ /**
+ * Method: restoreFeature
+ * Move the feature from the sketch layer to the target layer.
+ *
+ * Properties:
+ * cancel - {Boolean} Cancel drawing. If falsey, the "sketchcomplete" event
+ * will be fired.
+ */
+ restoreFeature: function(cancel) {
+ this.control.layer.events.unregister(
+ "sketchcomplete", this, this.finalizeInteriorRing
+ );
+ this.control.layer.events.unregister(
+ "sketchmodified", this, this.enforceTopology
+ );
+ this.layer.removeFeatures([this.polygon], {silent: true});
+ this.control.layer.addFeatures([this.polygon], {silent: true});
+ this.drawingHole = false;
+ if (!cancel) {
+ // Re-trigger "sketchcomplete" so other listeners can do their
+ // business. While this is somewhat sloppy (if a listener is
+ // registered with registerPriority - not common - between the start
+ // and end of a single ring drawing - very uncommon - it will be
+ // called twice).
+ // TODO: In 3.0, collapse sketch handlers into geometry specific
+ // drawing controls.
+ this.control.layer.events.triggerEvent(
+ "sketchcomplete", {feature : this.polygon}
+ );
+ }
+ },
+
+ /**
* Method: destroyFeature
* Destroy temporary geometries
*/
Modified: trunk/openlayers/tests/Handler/Polygon.html
===================================================================
--- trunk/openlayers/tests/Handler/Polygon.html 2010-10-13 21:25:21 UTC (rev 10827)
+++ trunk/openlayers/tests/Handler/Polygon.html 2010-10-13 22:12:52 UTC (rev 10828)
@@ -158,6 +158,187 @@
map.destroy();
}
+ function test_rings(t) {
+ t.plan(12);
+
+ var log = [];
+ var map = new OpenLayers.Map({
+ div: "map",
+ resolutions: [1],
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ layers: [
+ new OpenLayers.Layer.Vector(null, {
+ isBaseLayer: true,
+ eventListeners: {
+ featureadded: function(event) {
+ log.push(event);
+ },
+ sketchmodified: function(event) {
+ log.push(event);
+ },
+ sketchcomplete: function(event) {
+ log.push(event);
+ }
+ }
+ })
+ ],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 0
+ });
+
+ // create control for drawing polygons with holes
+ var draw = new OpenLayers.Control.DrawFeature(
+ map.layers[0],
+ OpenLayers.Handler.Polygon,
+ {handlerOptions: {holeModifier: "altKey"}}
+ );
+ map.addControl(draw);
+ draw.activate();
+
+ var event;
+ function trigger(type, event) {
+ map.events.triggerEvent(type, OpenLayers.Util.extend({}, event));
+ }
+
+ // a) draw a polygon
+ log = [];
+ // start at -9, 9
+ event = {xy: new OpenLayers.Pixel(-9, 9)};
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to -1, 9
+ event = {xy: new OpenLayers.Pixel(-1, 9)};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to -1, 1
+ event = {xy: new OpenLayers.Pixel(-1, 1)};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to -9, 1
+ event = {xy: new OpenLayers.Pixel(-9, 1)};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // finish
+ event = {xy: new OpenLayers.Pixel(-9, 1)};
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ trigger("dblclick", event);
+
+ // make assertions
+ t.eq(log.length, 9, "a) correct number of events");
+ t.eq(log[log.length-1].type, "featureadded", "a) featureadded event last");
+ t.eq(log[log.length-1].feature.geometry.getArea(), 64, "a) correct polygon area");
+
+ // b) draw a hole
+ log = [];
+ // start at -6, 6
+ event = {xy: new OpenLayers.Pixel(-6, 6), altKey: true};
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to -3, 6
+ event = {xy: new OpenLayers.Pixel(-3, 6), altKey: true};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to -3, 3
+ event = {xy: new OpenLayers.Pixel(-3, 3), altKey: true};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to -6, 3
+ event = {xy: new OpenLayers.Pixel(-6, 3), altKey: true};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // finish
+ event = {xy: new OpenLayers.Pixel(-6, 3), altKey: true};
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ trigger("dblclick", event);
+
+ // make assertions
+ t.eq(log.length, 8, "b) correct number of events");
+ t.eq(log[log.length-1].type, "sketchcomplete", "b) sketchcomplete event last");
+ t.eq(log[log.length-1].feature.geometry.getArea(), 55, "b) correct polygon area");
+
+
+ // c) draw a polygon that overlaps the first
+ log = [];
+ // start at -2, 2
+ event = {xy: new OpenLayers.Pixel(-2, 2)};
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to 2, 2
+ event = {xy: new OpenLayers.Pixel(2, 2)};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to 2, -2
+ event = {xy: new OpenLayers.Pixel(2, -2)};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to -2, -2
+ event = {xy: new OpenLayers.Pixel(-2, -2)};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // finish
+ event = {xy: new OpenLayers.Pixel(-2, -2)};
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ trigger("dblclick", event);
+
+ // make assertions
+ t.eq(log.length, 9, "c) correct number of events");
+ t.eq(log[log.length-1].type, "featureadded", "c) featureadded event last");
+ t.eq(log[log.length-1].feature.geometry.getArea(), 16, "c) correct polygon area");
+
+ // d) draw a hole that tries to go outside the exterior ring
+ log = [];
+ // start at -1, 1
+ event = {xy: new OpenLayers.Pixel(-1, 1), altKey: true};
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to 1, 1
+ event = {xy: new OpenLayers.Pixel(1, 1), altKey: true};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // try to draw to -8, 8 (ouside active polygon)
+ event = {xy: new OpenLayers.Pixel(-8, 8), altKey: true};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to 1, -1
+ event = {xy: new OpenLayers.Pixel(1, -1), altKey: true};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to -1, -1
+ event = {xy: new OpenLayers.Pixel(-1, -1), altKey: true};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // finish
+ event = {xy: new OpenLayers.Pixel(-1, 1), altKey: true};
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ trigger("dblclick", event);
+
+ // make assertions
+ t.eq(log.length, 11, "d) correct number of events");
+ t.eq(log[log.length-1].type, "sketchcomplete", "d) sketchcomplete event last");
+ t.eq(log[log.length-1].feature.geometry.getArea(), 12, "d) correct polygon area");
+
+
+ map.destroy();
+ }
+
+
function test_Handler_Polygon_destroy(t) {
t.plan(8);
var map = new OpenLayers.Map('map');
More information about the Commits
mailing list