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
This commit is contained in:
59
examples/donut.html
Normal file
59
examples/donut.html
Normal file
@@ -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>
|
||||||
38
examples/donut.js
Normal file
38
examples/donut.js
Normal file
@@ -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;
|
||||||
@@ -20,6 +20,20 @@
|
|||||||
*/
|
*/
|
||||||
OpenLayers.Handler.Polygon = OpenLayers.Class(OpenLayers.Handler.Path, {
|
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
|
* Parameter: polygon
|
||||||
* {<OpenLayers.Feature.Vector>}
|
* {<OpenLayers.Feature.Vector>}
|
||||||
@@ -68,14 +82,151 @@ OpenLayers.Handler.Polygon = OpenLayers.Class(OpenLayers.Handler.Path, {
|
|||||||
this.line = new OpenLayers.Feature.Vector(
|
this.line = new OpenLayers.Feature.Vector(
|
||||||
new OpenLayers.Geometry.LinearRing([this.point.geometry])
|
new OpenLayers.Geometry.LinearRing([this.point.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(
|
this.polygon = new OpenLayers.Feature.Vector(
|
||||||
new OpenLayers.Geometry.Polygon([this.line.geometry])
|
new OpenLayers.Geometry.Polygon([this.line.geometry])
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
this.callback("create", [this.point.geometry, this.getSketch()]);
|
this.callback("create", [this.point.geometry, this.getSketch()]);
|
||||||
this.point.geometry.clearBounds();
|
this.point.geometry.clearBounds();
|
||||||
this.layer.addFeatures([this.polygon, this.point], {silent: true});
|
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
|
* Method: destroyFeature
|
||||||
* Destroy temporary geometries
|
* Destroy temporary geometries
|
||||||
|
|||||||
@@ -158,6 +158,187 @@
|
|||||||
map.destroy();
|
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) {
|
function test_Handler_Polygon_destroy(t) {
|
||||||
t.plan(8);
|
t.plan(8);
|
||||||
var map = new OpenLayers.Map('map');
|
var map = new OpenLayers.Map('map');
|
||||||
|
|||||||
Reference in New Issue
Block a user