Project

General

Profile

Download (19.1 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
 * @requires OpenLayers/Control.js
8
 * @requires OpenLayers/Handler/Path.js
9
 * @requires OpenLayers/Layer/Vector.js
10
 */
11

    
12
/**
13
 * Class: OpenLayers.Control.Split
14
 * Acts as a split feature agent while editing vector features.
15
 *
16
 * Inherits from:
17
 *  - <OpenLayers.Control>
18
 */
19
OpenLayers.Control.Split = OpenLayers.Class(OpenLayers.Control, {
20

    
21
    /** 
22
     * APIProperty: events
23
     * {<OpenLayers.Events>} Events instance for listeners and triggering
24
     *     control specific events.
25
     *
26
     * Register a listener for a particular event with the following syntax:
27
     * (code)
28
     * control.events.register(type, obj, listener);
29
     * (end)
30
     *
31
     * Supported event types (in addition to those from <OpenLayers.Control.events>):
32
     * beforesplit - Triggered before a split occurs.  Listeners receive an
33
     *     event object with *source* and *target* properties.
34
     * split - Triggered when a split occurs.  Listeners receive an event with
35
     *     an *original* property and a *features* property.  The original
36
     *     is a reference to the target feature that the sketch or modified
37
     *     feature intersects.  The features property is a list of all features
38
     *     that result from this single split.  This event is triggered before
39
     *     the resulting features are added to the layer (while the layer still
40
     *     has a reference to the original).
41
     * aftersplit - Triggered after all splits resulting from a single sketch
42
     *     or feature modification have occurred.  The original features
43
     *     have been destroyed and features that result from the split
44
     *     have already been added to the layer.  Listeners receive an event
45
     *     with a *source* and *features* property.  The source references the
46
     *     sketch or modified feature used as a splitter.  The features
47
     *     property is a list of all resulting features.
48
     */
49
    
50
    /**
51
     * APIProperty: layer
52
     * {<OpenLayers.Layer.Vector>} The target layer with features to be split.
53
     *     Set at construction or after construction with <setLayer>.
54
     */
55
    layer: null,
56
    
57
    /**
58
     * Property: source
59
     * {<OpenLayers.Layer.Vector>} Optional source layer.  Any newly created
60
     *     or modified features from this layer will be used to split features
61
     *     on the target layer.  If not provided, a temporary sketch layer will
62
     *     be created.
63
     */
64
    source: null,
65
    
66
    /**
67
     * Property: sourceOptions
68
     * {Options} If a temporary sketch layer is created, these layer options
69
     *     will be applied.
70
     */
71
    sourceOptions: null,
72

    
73
    /**
74
     * APIProperty: tolerance
75
     * {Number} Distance between the calculated intersection and a vertex on
76
     *     the source geometry below which the existing vertex will be used
77
     *     for the split.  Default is null.
78
     */
79
    tolerance: null,
80
    
81
    /**
82
     * APIProperty: edge
83
     * {Boolean} Allow splits given intersection of edges only.  Default is
84
     *     true.  If false, a vertex on the source must be within the
85
     *     <tolerance> distance of the calculated intersection for a split
86
     *     to occur.
87
     */
88
    edge: true,
89
    
90
    /**
91
     * APIProperty: deferDelete
92
     * {Boolean} Instead of removing features from the layer, set feature
93
     *     states of split features to DELETE.  This assumes a save strategy
94
     *     or other component is in charge of removing features from the
95
     *     layer.  Default is false.  If false, split features will be
96
     *     immediately deleted from the layer.
97
     */
98
    deferDelete: false,
99
    
100
    /**
101
     * APIProperty: mutual
102
     * {Boolean} If source and target layers are the same, split source
103
     *     features and target features where they intersect.  Default is
104
     *     true.  If false, only target features will be split.
105
     */
106
    mutual: true,
107
    
108
    /**
109
     * APIProperty: targetFilter
110
     * {<OpenLayers.Filter>} Optional filter that will be evaluated
111
     *     to determine if a feature from the target layer is eligible for
112
     *     splitting.
113
     */
114
    targetFilter: null,
115
    
116
    /**
117
     * APIProperty: sourceFilter
118
     * {<OpenLayers.Filter>} Optional filter that will be evaluated
119
     *     to determine if a feature from the source layer is eligible for
120
     *     splitting.
121
     */
122
    sourceFilter: null,
123
    
124
    /**
125
     * Property: handler
126
     * {<OpenLayers.Handler.Path>} The temporary sketch handler created if
127
     *     no source layer is provided.
128
     */
129
    handler: null,
130

    
131
    /**
132
     * Constructor: OpenLayers.Control.Split
133
     * Creates a new split control. A control is constructed with a target
134
     *     layer and an optional source layer. While the control is active,
135
     *     creating new features or modifying existing features on the source
136
     *     layer will result in splitting any eligible features on the target
137
     *     layer.  If no source layer is provided, a temporary sketch layer will
138
     *     be created to create lines for splitting features on the target.
139
     *
140
     * Parameters:
141
     * options - {Object} An object containing all configuration properties for
142
     *     the control.
143
     *
144
     * Valid options:
145
     * layer - {<OpenLayers.Layer.Vector>} The target layer.  Features from this
146
     *     layer will be split by new or modified features on the source layer
147
     *     or temporary sketch layer.
148
     * source - {<OpenLayers.Layer.Vector>} Optional source layer.  If provided
149
     *     newly created features or modified features will be used to split
150
     *     features on the target layer.  If not provided, a temporary sketch
151
     *     layer will be created for drawing lines.
152
     * tolerance - {Number} Optional value for the distance between a source
153
     *     vertex and the calculated intersection below which the split will
154
     *     occur at the vertex.
155
     * edge - {Boolean} Allow splits given intersection of edges only.  Default
156
     *     is true.  If false, a vertex on the source must be within the
157
     *     <tolerance> distance of the calculated intersection for a split
158
     *     to occur.
159
     * mutual - {Boolean} If source and target are the same, split source
160
     *     features and target features where they intersect.  Default is
161
     *     true.  If false, only target features will be split.
162
     * targetFilter - {<OpenLayers.Filter>} Optional filter that will be evaluated
163
     *     to determine if a feature from the target layer is eligible for
164
     *     splitting.
165
     * sourceFilter - {<OpenLayers.Filter>} Optional filter that will be evaluated
166
     *     to determine if a feature from the target layer is eligible for
167
     *     splitting.
168
     */
169
    initialize: function(options) {
170
        OpenLayers.Control.prototype.initialize.apply(this, [options]);
171
        this.options = options || {}; // TODO: this could be done by the super
172
        
173
        // set the source layer if provided
174
        if(this.options.source) {
175
            this.setSource(this.options.source);
176
        }
177
    },
178
    
179
    /**
180
     * APIMethod: setSource
181
     * Set the source layer for edits layer.
182
     *
183
     * Parameters:
184
     * layer - {<OpenLayers.Layer.Vector>}  The new source layer layer.  If
185
     *     null, a temporary sketch layer will be created.
186
     */
187
    setSource: function(layer) {
188
        if(this.active) {
189
            this.deactivate();
190
            if(this.handler) {
191
                this.handler.destroy();
192
                delete this.handler;
193
            }
194
            this.source = layer;
195
            this.activate();
196
        } else {
197
            this.source = layer;
198
        }
199
    },
200
    
201
    /**
202
     * APIMethod: activate
203
     * Activate the control.  Activating the control registers listeners for
204
     *     editing related events so that during feature creation and
205
     *     modification, features in the target will be considered for
206
     *     splitting.
207
     */
208
    activate: function() {
209
        var activated = OpenLayers.Control.prototype.activate.call(this);
210
        if(activated) {
211
            if(!this.source) {
212
                if(!this.handler) {
213
                    this.handler = new OpenLayers.Handler.Path(this,
214
                        {done: function(geometry) {
215
                            this.onSketchComplete({
216
                                feature: new OpenLayers.Feature.Vector(geometry)
217
                            });
218
                        }},
219
                        {layerOptions: this.sourceOptions}
220
                    );
221
                }
222
                this.handler.activate();
223
            } else if(this.source.events) {
224
                this.source.events.on({
225
                    sketchcomplete: this.onSketchComplete,
226
                    afterfeaturemodified: this.afterFeatureModified,
227
                    scope: this
228
                });
229
            }
230
        }
231
        return activated;
232
    },
233
    
234
    /**
235
     * APIMethod: deactivate
236
     * Deactivate the control.  Deactivating the control unregisters listeners
237
     *     so feature editing may proceed without engaging the split agent.
238
     */
239
    deactivate: function() {
240
        var deactivated = OpenLayers.Control.prototype.deactivate.call(this);
241
        if(deactivated) {
242
            if(this.source && this.source.events) {
243
                this.source.events.un({
244
                    sketchcomplete: this.onSketchComplete,
245
                    afterfeaturemodified: this.afterFeatureModified,
246
                    scope: this
247
                });
248
            }
249
        }
250
        return deactivated;
251
    },
252
    
253
    /**
254
     * Method: onSketchComplete
255
     * Registered as a listener for the sketchcomplete event on the editable
256
     *     layer.
257
     *
258
     * Parameters:
259
     * event - {Object} The sketch complete event.
260
     *
261
     * Returns:
262
     * {Boolean} Stop the sketch from being added to the layer (it has been
263
     *     split).
264
     */
265
    onSketchComplete: function(event) {
266
        this.feature = null;
267
        return !this.considerSplit(event.feature);
268
    },
269
    
270
    /**
271
     * Method: afterFeatureModified
272
     * Registered as a listener for the afterfeaturemodified event on the
273
     *     editable layer.
274
     *
275
     * Parameters:
276
     * event - {Object} The after feature modified event.
277
     */
278
    afterFeatureModified: function(event) {
279
        if(event.modified) {
280
            var feature = event.feature;
281
            if (typeof feature.geometry.split === "function") {
282
                this.feature = event.feature;
283
                this.considerSplit(event.feature);
284
            }
285
        }
286
    },
287
    
288
    /**
289
     * Method: removeByGeometry
290
     * Remove a feature from a list based on the given geometry.
291
     *
292
     * Parameters:
293
     * features - {Array(<OpenLayers.Feature.Vector>)} A list of features.
294
     * geometry - {<OpenLayers.Geometry>} A geometry.
295
     */
296
    removeByGeometry: function(features, geometry) {
297
        for(var i=0, len=features.length; i<len; ++i) {
298
            if(features[i].geometry === geometry) {
299
                features.splice(i, 1);
300
                break;
301
            }
302
        }
303
    },
304
    
305
    /**
306
     * Method: isEligible
307
     * Test if a target feature is eligible for splitting.
308
     *
309
     * Parameters:
310
     * target - {<OpenLayers.Feature.Vector>} The target feature.
311
     *
312
     * Returns:
313
     * {Boolean} The target is eligible for splitting.
314
     */
315
    isEligible: function(target) {
316
        if (!target.geometry) {
317
            return false;
318
        } else {
319
            return (
320
                target.state !== OpenLayers.State.DELETE
321
            ) && (
322
                typeof target.geometry.split === "function"
323
            ) && (
324
                this.feature !== target
325
            ) && (
326
                !this.targetFilter ||
327
                this.targetFilter.evaluate(target.attributes)
328
            );
329
        }
330
    },
331

    
332
    /**
333
     * Method: considerSplit
334
     * Decide whether or not to split target features with the supplied
335
     *     feature.  If <mutual> is true, both the source and target features
336
     *     will be split if eligible.
337
     *
338
     * Parameters:
339
     * feature - {<OpenLayers.Feature.Vector>} The newly created or modified
340
     *     feature.
341
     *
342
     * Returns:
343
     * {Boolean} The supplied feature was split (and destroyed).
344
     */
345
    considerSplit: function(feature) {
346
        var sourceSplit = false;
347
        var targetSplit = false;
348
        if(!this.sourceFilter ||
349
           this.sourceFilter.evaluate(feature.attributes)) {
350
            var features = this.layer && this.layer.features || [];
351
            var target, results, proceed;
352
            var additions = [], removals = [];
353
            var mutual = (this.layer === this.source) && this.mutual;
354
            var options = {
355
                edge: this.edge,
356
                tolerance: this.tolerance,
357
                mutual: mutual
358
            };
359
            var sourceParts = [feature.geometry];
360
            var targetFeature, targetParts;
361
            var source, parts;
362
            for(var i=0, len=features.length; i<len; ++i) {
363
                targetFeature = features[i];
364
                if(this.isEligible(targetFeature)) {
365
                    targetParts = [targetFeature.geometry];
366
                    // work through source geoms - this array may change
367
                    for(var j=0; j<sourceParts.length; ++j) { 
368
                        source = sourceParts[j];
369
                        // work through target parts - this array may change
370
                        for(var k=0; k<targetParts.length; ++k) {
371
                            target = targetParts[k];
372
                            if(source.getBounds().intersectsBounds(target.getBounds())) {
373
                                results = source.split(target, options);
374
                                if(results) {
375
                                    proceed = this.events.triggerEvent(
376
                                        "beforesplit", {source: feature, target: targetFeature}
377
                                    );
378
                                    if(proceed !== false) {
379
                                        if(mutual) {
380
                                            parts = results[0];
381
                                            // handle parts that result from source splitting
382
                                            if(parts.length > 1) {
383
                                                // splice in new source parts
384
                                                parts.unshift(j, 1); // add args for splice below
385
                                                Array.prototype.splice.apply(sourceParts, parts);
386
                                                j += parts.length - 3;
387
                                            }
388
                                            results = results[1];
389
                                        }
390
                                        // handle parts that result from target splitting
391
                                        if(results.length > 1) {
392
                                            // splice in new target parts
393
                                            results.unshift(k, 1); // add args for splice below
394
                                            Array.prototype.splice.apply(targetParts, results);
395
                                            k += results.length - 3;
396
                                        }
397
                                    }
398
                                }
399
                            }
400
                        }
401
                    }
402
                    if(targetParts && targetParts.length > 1) {
403
                        this.geomsToFeatures(targetFeature, targetParts);
404
                        this.events.triggerEvent("split", {
405
                            original: targetFeature,
406
                            features: targetParts
407
                        });
408
                        Array.prototype.push.apply(additions, targetParts);
409
                        removals.push(targetFeature);
410
                        targetSplit = true;
411
                    }
412
                }
413
            }
414
            if(sourceParts && sourceParts.length > 1) {
415
                this.geomsToFeatures(feature, sourceParts);
416
                this.events.triggerEvent("split", {
417
                    original: feature,
418
                    features: sourceParts
419
                });
420
                Array.prototype.push.apply(additions, sourceParts);
421
                removals.push(feature);
422
                sourceSplit = true;
423
            }
424
            if(sourceSplit || targetSplit) {
425
                // remove and add feature events are suppressed
426
                // listen for split event on this control instead
427
                if(this.deferDelete) {
428
                    // Set state instead of removing.  Take care to avoid
429
                    // setting delete for features that have not yet been
430
                    // inserted - those should be destroyed immediately.
431
                    var feat, destroys = [];
432
                    for(var i=0, len=removals.length; i<len; ++i) {
433
                        feat = removals[i];
434
                        if(feat.state === OpenLayers.State.INSERT) {
435
                            destroys.push(feat);
436
                        } else {
437
                            feat.state = OpenLayers.State.DELETE;
438
                            this.layer.drawFeature(feat);
439
                        }
440
                    }
441
                    this.layer.destroyFeatures(destroys, {silent: true});
442
                    for(var i=0, len=additions.length; i<len; ++i) {
443
                        additions[i].state = OpenLayers.State.INSERT;
444
                    }
445
                } else {
446
                    this.layer.destroyFeatures(removals, {silent: true});
447
                }
448
                this.layer.addFeatures(additions, {silent: true});
449
                this.events.triggerEvent("aftersplit", {
450
                    source: feature,
451
                    features: additions
452
                });
453
            }
454
        }
455
        return sourceSplit;
456
    },
457
    
458
    /**
459
     * Method: geomsToFeatures
460
     * Create new features given a template feature and a list of geometries.
461
     *     The list of geometries is modified in place.  The result will be
462
     *     a list of new features.
463
     *
464
     * Parameters:
465
     * feature - {<OpenLayers.Feature.Vector>} The feature to be cloned.
466
     * geoms - {Array(<OpenLayers.Geometry>)} List of goemetries.  This will
467
     *     become a list of new features.
468
     */
469
    geomsToFeatures: function(feature, geoms) {
470
        var clone = feature.clone();
471
        delete clone.geometry;
472
        var newFeature;
473
        for(var i=0, len=geoms.length; i<len; ++i) {
474
            // turn results list from geoms to features
475
            newFeature = clone.clone();
476
            newFeature.geometry = geoms[i];
477
            newFeature.state = OpenLayers.State.INSERT;
478
            geoms[i] = newFeature;
479
        }
480
    },
481
    
482
    /**
483
     * Method: destroy
484
     * Clean up the control.
485
     */
486
    destroy: function() {
487
        if(this.active) {
488
            this.deactivate(); // TODO: this should be handled by the super
489
        }
490
        OpenLayers.Control.prototype.destroy.call(this);
491
    },
492

    
493
    CLASS_NAME: "OpenLayers.Control.Split"
494
});
(34-34/45)