Project

General

Profile

Download (10.4 KB) Statistics
| Branch: | Tag: | Revision:
1
/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
2
 * full list of contributors). Published under the 2-clause BSD license.
3
 * See license.txt in the OpenLayers distribution or repository for the
4
 * full text of the license. */
5

    
6

    
7
/**
8
 * @requires OpenLayers/Handler/Path.js
9
 * @requires OpenLayers/Geometry/Polygon.js
10
 */
11

    
12
/**
13
 * Class: OpenLayers.Handler.Polygon
14
 * Handler to draw a polygon on the map.  Polygon is displayed on mouse down,
15
 * moves on mouse move, and is finished on mouse up.
16
 *
17
 * Inherits from:
18
 *  - <OpenLayers.Handler.Path>
19
 *  - <OpenLayers.Handler>
20
 */
21
OpenLayers.Handler.Polygon = OpenLayers.Class(OpenLayers.Handler.Path, {
22
    
23
    /** 
24
     * APIProperty: holeModifier
25
     * {String} Key modifier to trigger hole digitizing.  Acceptable values are
26
     *     "altKey", "shiftKey", or "ctrlKey".  If not set, no hole digitizing
27
     *     will take place.  Default is null.
28
     */
29
    holeModifier: null,
30
    
31
    /**
32
     * Property: drawingHole
33
     * {Boolean} Currently drawing an interior ring.
34
     */
35
    drawingHole: false,
36
    
37
    /**
38
     * Property: polygon
39
     * {<OpenLayers.Feature.Vector>}
40
     */
41
    polygon: null,
42

    
43
    /**
44
     * Constructor: OpenLayers.Handler.Polygon
45
     * Create a Polygon Handler.
46
     *
47
     * Parameters:
48
     * control - {<OpenLayers.Control>} The control that owns this handler
49
     * callbacks - {Object} An object with a properties whose values are
50
     *     functions.  Various callbacks described below.
51
     * options - {Object} An optional object with properties to be set on the
52
     *           handler
53
     *
54
     * Named callbacks:
55
     * create - Called when a sketch is first created.  Callback called with
56
     *     the creation point geometry and sketch feature.
57
     * modify - Called with each move of a vertex with the vertex (point)
58
     *     geometry and the sketch feature.
59
     * point - Called as each point is added.  Receives the new point geometry.
60
     * done - Called when the point drawing is finished.  The callback will
61
     *     recieve a single argument, the polygon geometry.
62
     * cancel - Called when the handler is deactivated while drawing.  The
63
     *     cancel callback will receive a geometry.
64
     */
65
    
66
    /**
67
     * Method: createFeature
68
     * Add temporary geometries
69
     *
70
     * Parameters:
71
     * pixel - {<OpenLayers.Pixel>} The initial pixel location for the new
72
     *     feature.
73
     */
74
    createFeature: function(pixel) {
75
        var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
76
        var geometry = new OpenLayers.Geometry.Point(
77
            lonlat.lon, lonlat.lat
78
        );
79
        this.point = new OpenLayers.Feature.Vector(geometry);
80
        this.line = new OpenLayers.Feature.Vector(
81
            new OpenLayers.Geometry.LinearRing([this.point.geometry])
82
        );
83
        this.polygon = new OpenLayers.Feature.Vector(
84
            new OpenLayers.Geometry.Polygon([this.line.geometry])
85
        );
86
        this.callback("create", [this.point.geometry, this.getSketch()]);
87
        this.point.geometry.clearBounds();
88
        this.layer.addFeatures([this.polygon, this.point], {silent: true});
89
    },
90

    
91
    /**
92
     * Method: addPoint
93
     * Add point to geometry.
94
     *
95
     * Parameters:
96
     * pixel - {<OpenLayers.Pixel>} The pixel location for the new point.
97
     */
98
    addPoint: function(pixel) {
99
        if(!this.drawingHole && this.holeModifier &&
100
           this.evt && this.evt[this.holeModifier]) {
101
            var geometry = this.point.geometry;
102
            var features = this.control.layer.features;
103
            var candidate, polygon;
104
            // look for intersections, last drawn gets priority
105
            for (var i=features.length-1; i>=0; --i) {
106
                candidate = features[i].geometry;
107
                if ((candidate instanceof OpenLayers.Geometry.Polygon || 
108
                    candidate instanceof OpenLayers.Geometry.MultiPolygon) && 
109
                    candidate.intersects(geometry)) {
110
                    polygon = features[i];
111
                    this.control.layer.removeFeatures([polygon], {silent: true});
112
                    this.control.layer.events.registerPriority(
113
                        "sketchcomplete", this, this.finalizeInteriorRing
114
                    );
115
                    this.control.layer.events.registerPriority(
116
                        "sketchmodified", this, this.enforceTopology
117
                    );
118
                    polygon.geometry.addComponent(this.line.geometry);
119
                    this.polygon = polygon;
120
                    this.drawingHole = true;
121
                    break;
122
                }
123
            }
124
        }
125
        OpenLayers.Handler.Path.prototype.addPoint.apply(this, arguments);
126
    },
127

    
128
    /**
129
     * Method: getCurrentPointIndex
130
     * 
131
     * Returns:
132
     * {Number} The index of the most recently drawn point.
133
     */
134
    getCurrentPointIndex: function() {
135
        return this.line.geometry.components.length - 2;
136
    },
137

    
138
    /**
139
     * Method: enforceTopology
140
     * Simple topology enforcement for drawing interior rings.  Ensures vertices
141
     *     of interior rings are contained by exterior ring.  Other topology 
142
     *     rules are enforced in <finalizeInteriorRing> to allow drawing of 
143
     *     rings that intersect only during the sketch (e.g. a "C" shaped ring
144
     *     that nearly encloses another ring).
145
     */
146
    enforceTopology: function(event) {
147
        var point = event.vertex;
148
        var components = this.line.geometry.components;
149
        // ensure that vertices of interior ring are contained by exterior ring
150
        if (!this.polygon.geometry.intersects(point)) {
151
            var last = components[components.length-3];
152
            point.x = last.x;
153
            point.y = last.y;
154
        }
155
    },
156

    
157
    /**
158
     * Method: finishGeometry
159
     * Finish the geometry and send it back to the control.
160
     */
161
    finishGeometry: function() {
162
        var index = this.line.geometry.components.length - 2;
163
        this.line.geometry.removeComponent(this.line.geometry.components[index]);
164
        this.removePoint();
165
        this.finalize();
166
    },
167

    
168
    /**
169
     * Method: finalizeInteriorRing
170
     * Enforces that new ring has some area and doesn't contain vertices of any
171
     *     other rings.
172
     */
173
    finalizeInteriorRing: function() {
174
        var ring = this.line.geometry;
175
        // ensure that ring has some area
176
        var modified = (ring.getArea() !== 0);
177
        if (modified) {
178
            // ensure that new ring doesn't intersect any other rings
179
            var rings = this.polygon.geometry.components;
180
            for (var i=rings.length-2; i>=0; --i) {
181
                if (ring.intersects(rings[i])) {
182
                    modified = false;
183
                    break;
184
                }
185
            }
186
            if (modified) {
187
                // ensure that new ring doesn't contain any other rings
188
                var target;
189
                outer: for (var i=rings.length-2; i>0; --i) {
190
                    var points = rings[i].components;
191
                    for (var j=0, jj=points.length; j<jj; ++j) {
192
                        if (ring.containsPoint(points[j])) {
193
                            modified = false;
194
                            break outer;
195
                        }
196
                    }
197
                }
198
            }
199
        }
200
        if (modified) {
201
            if (this.polygon.state !== OpenLayers.State.INSERT) {
202
                this.polygon.state = OpenLayers.State.UPDATE;
203
            }
204
        } else {
205
            this.polygon.geometry.removeComponent(ring);
206
        }
207
        this.restoreFeature();
208
        return false;
209
    },
210

    
211
    /**
212
     * APIMethod: cancel
213
     * Finish the geometry and call the "cancel" callback.
214
     */
215
    cancel: function() {
216
        if (this.drawingHole) {
217
            this.polygon.geometry.removeComponent(this.line.geometry);
218
            this.restoreFeature(true);
219
        }
220
        return OpenLayers.Handler.Path.prototype.cancel.apply(this, arguments);
221
    },
222
    
223
    /**
224
     * Method: restoreFeature
225
     * Move the feature from the sketch layer to the target layer.
226
     *
227
     * Properties: 
228
     * cancel - {Boolean} Cancel drawing.  If falsey, the "sketchcomplete" event
229
     *     will be fired.
230
     */
231
    restoreFeature: function(cancel) {
232
        this.control.layer.events.unregister(
233
            "sketchcomplete", this, this.finalizeInteriorRing
234
        );
235
        this.control.layer.events.unregister(
236
            "sketchmodified", this, this.enforceTopology
237
        );
238
        this.layer.removeFeatures([this.polygon], {silent: true});
239
        this.control.layer.addFeatures([this.polygon], {silent: true});
240
        this.drawingHole = false;
241
        if (!cancel) {
242
            // Re-trigger "sketchcomplete" so other listeners can do their
243
            // business.  While this is somewhat sloppy (if a listener is 
244
            // registered with registerPriority - not common - between the start
245
            // and end of a single ring drawing - very uncommon - it will be 
246
            // called twice).
247
            // TODO: In 3.0, collapse sketch handlers into geometry specific
248
            // drawing controls.
249
            this.control.layer.events.triggerEvent(
250
                "sketchcomplete", {feature : this.polygon}
251
            );
252
        }
253
    },
254

    
255
    /**
256
     * Method: destroyFeature
257
     * Destroy temporary geometries
258
     *
259
     * Parameters:
260
     * force - {Boolean} Destroy even if persist is true.
261
     */
262
    destroyFeature: function(force) {
263
        OpenLayers.Handler.Path.prototype.destroyFeature.call(
264
            this, force);
265
        this.polygon = null;
266
    },
267

    
268
    /**
269
     * Method: drawFeature
270
     * Render geometries on the temporary layer.
271
     */
272
    drawFeature: function() {
273
        this.layer.drawFeature(this.polygon, this.style);
274
        this.layer.drawFeature(this.point, this.style);
275
    },
276
    
277
    /**
278
     * Method: getSketch
279
     * Return the sketch feature.
280
     *
281
     * Returns:
282
     * {<OpenLayers.Feature.Vector>}
283
     */
284
    getSketch: function() {
285
        return this.polygon;
286
    },
287

    
288
    /**
289
     * Method: getGeometry
290
     * Return the sketch geometry.  If <multi> is true, this will return
291
     *     a multi-part geometry.
292
     *
293
     * Returns:
294
     * {<OpenLayers.Geometry.Polygon>}
295
     */
296
    getGeometry: function() {
297
        var geometry = this.polygon && this.polygon.geometry;
298
        if(geometry && this.multi) {
299
            geometry = new OpenLayers.Geometry.MultiPolygon([geometry]);
300
        }
301
        return geometry;
302
    },
303

    
304
    CLASS_NAME: "OpenLayers.Handler.Polygon"
305
});
(11-11/12)