OTP changes
[busui.git] / labs / Split.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
/* Copyright (c) 2006-2010 by OpenLayers Contributors (see authors.txt for 
 * full list of contributors). Published under the Clear BSD license.  
 * See http://svn.openlayers.org/trunk/openlayers/license.txt for the
 * full text of the license. */
 
/**
 * @requires OpenLayers/Control.js
 * @requires OpenLayers/Handler/Path.js
 * @requires OpenLayers/Layer/Vector.js
 */
 
/**
 * Class: OpenLayers.Control.Split
 * Acts as a split feature agent while editing vector features.
 *
 * Inherits from:
 *  - <OpenLayers.Control>
 */
OpenLayers.Control.Split = OpenLayers.Class(OpenLayers.Control, {
 
    /**
     * Constant: EVENT_TYPES
     * {Array(String)} Supported application event types.  Register a listener
     *     for a particular event with the following syntax:
     * (code)
     * control.events.register(type, obj, listener);
     * (end)
     *
     * Listeners will be called with a reference to an event object.  The
     *     properties of this event depends on exactly what happened.
     *
     * Supported control event types (in addition to those from <OpenLayers.Control>):
     * beforesplit - Triggered before a split occurs.  Listeners receive an
     *     event object with *source* and *target* properties.
     * split - Triggered when a split occurs.  Listeners receive an event with
     *     an *original* property and a *features* property.  The original
     *     is a reference to the target feature that the sketch or modified
     *     feature intersects.  The features property is a list of all features
     *     that result from this single split.  This event is triggered before
     *     the resulting features are added to the layer (while the layer still
     *     has a reference to the original).
     * aftersplit - Triggered after all splits resulting from a single sketch
     *     or feature modification have occurred.  The original features
     *     have been destroyed and features that result from the split
     *     have already been added to the layer.  Listeners receive an event
     *     with a *source* and *features* property.  The source references the
     *     sketch or modified feature used as a splitter.  The features
     *     property is a list of all resulting features.
     */
    EVENT_TYPES: ["beforesplit", "split", "aftersplit"],
    
    /**
     * APIProperty: layer
     * {<OpenLayers.Layer.Vector>} The target layer with features to be split.
     *     Set at construction or after construction with <setLayer>.
     */
    layer: null,
    
    /**
     * Property: source
     * {<OpenLayers.Layer.Vector>} Optional source layer.  Any newly created
     *     or modified features from this layer will be used to split features
     *     on the target layer.  If not provided, a temporary sketch layer will
     *     be created.
     */
    source: null,
    
    /**
     * Property: sourceOptions
     * {Options} If a temporary sketch layer is created, these layer options
     *     will be applied.
     */
    sourceOptions: null,
 
    /**
     * APIProperty: tolerance
     * {Number} Distance between the calculated intersection and a vertex on
     *     the source geometry below which the existing vertex will be used
     *     for the split.  Default is null.
     */
    tolerance: null,
    
    /**
     * APIProperty: edge
     * {Boolean} Allow splits given intersection of edges only.  Default is
     *     true.  If false, a vertex on the source must be within the
     *     <tolerance> distance of the calculated intersection for a split
     *     to occur.
     */
    edge: true,
    
    /**
     * APIProperty: deferDelete
     * {Boolean} Instead of removing features from the layer, set feature
     *     states of split features to DELETE.  This assumes a save strategy
     *     or other component is in charge of removing features from the
     *     layer.  Default is false.  If false, split features will be
     *     immediately deleted from the layer.
     */
    deferDelete: false,
    
    /**
     * APIProperty: mutual
     * {Boolean} If source and target layers are the same, split source
     *     features and target features where they intersect.  Default is
     *     true.  If false, only target features will be split.
     */
    mutual: true,
    
    /**
     * APIProperty: targetFilter
     * {OpenLayers.Filter} Optional filter that will be evaluated
     *     to determine if a feature from the target layer is eligible for
     *     splitting.
     */
    targetFilter: null,
    
    /**
     * APIProperty: sourceFilter
     * {OpenLayers.Filter} Optional filter that will be evaluated
     *     to determine if a feature from the target layer is eligible for
     *     splitting.
     */
    sourceFilter: null,
    
    /**
     * Property: handler
     * {<OpenLayers.Handler.Path>} The temporary sketch handler created if
     *     no source layer is provided.
     */
    handler: null,
 
    /**
     * Constructor: OpenLayers.Control.Split
     * Creates a new split control. A control is constructed with a target
     *     layer and an optional source layer. While the control is active,
     *     creating new features or modifying existing features on the source
     *     layer will result in splitting any eligible features on the target
     *     layer.  If no source layer is provided, a temporary sketch layer will
     *     be created to create lines for splitting features on the target.
     *
     * Parameters:
     * options - {Object} An object containing all configuration properties for
     *     the control.
     *
     * Valid options:
     * layer - {OpenLayers.Layer.Vector} The target layer.  Features from this
     *     layer will be split by new or modified features on the source layer
     *     or temporary sketch layer.
     * source - {OpenLayers.Layer.Vector} Optional source layer.  If provided
     *     newly created features or modified features will be used to split
     *     features on the target layer.  If not provided, a temporary sketch
     *     layer will be created for drawing lines.
     * tolerance - {Number} Optional value for the distance between a source
     *     vertex and the calculated intersection below which the split will
     *     occur at the vertex.
     * edge - {Boolean} Allow splits given intersection of edges only.  Default
     *     is true.  If false, a vertex on the source must be within the
     *     <tolerance> distance of the calculated intersection for a split
     *     to occur.
     * mutual - {Boolean} If source and target are the same, split source
     *     features and target features where they intersect.  Default is
     *     true.  If false, only target features will be split.
     * targetFilter - {OpenLayers.Filter} Optional filter that will be evaluated
     *     to determine if a feature from the target layer is eligible for
     *     splitting.
     * sourceFilter - {OpenLayers.Filter} Optional filter that will be evaluated
     *     to determine if a feature from the target layer is eligible for
     *     splitting.
     */
    initialize: function(options) {
        // concatenate events specific to measure with those from the base
        Array.prototype.push.apply(
            this.EVENT_TYPES, OpenLayers.Control.prototype.EVENT_TYPES
        );
        OpenLayers.Control.prototype.initialize.apply(this, [options]);
        this.options = options || {}; // TODO: this could be done by the super
        
        // set the source layer if provided
        if(this.options.source) {
            this.setSource(this.options.source);
        }
    },
    
    /**
     * APIMethod: setSource
     * Set the source layer for edits layer.
     *
     * Parameters:
     * layer - {OpenLayers.Layer.Vector}  The new source layer layer.  If
     *     null, a temporary sketch layer will be created.
     */
    setSource: function(layer) {
        if(this.active) {
            this.deactivate();
            if(this.handler) {
                this.handler.destroy();
                delete this.handler;
            }
            this.source = layer;
            this.activate();
        } else {
            this.source = layer;
        }
    },
    
    /**
     * APIMethod: activate
     * Activate the control.  Activating the control registers listeners for
     *     editing related events so that during feature creation and
     *     modification, features in the target will be considered for
     *     splitting.
     */
    activate: function() {
        var activated = OpenLayers.Control.prototype.activate.call(this);
        if(activated) {
            if(!this.source) {
                if(!this.handler) {
                    this.handler = new OpenLayers.Handler.Path(this,
                        {done: function(geometry) {
                            this.onSketchComplete({
                                feature: new OpenLayers.Feature.Vector(geometry)
                            });
                        }},
                        {layerOptions: this.sourceOptions}
                    );
                }
                this.handler.activate();
            } else if(this.source.events) {
                this.source.events.on({
                    sketchcomplete: this.onSketchComplete,
                    afterfeaturemodified: this.afterFeatureModified,
                    scope: this
                });
            }
        }
        return activated;
    },
    
    /**
     * APIMethod: deactivate
     * Deactivate the control.  Deactivating the control unregisters listeners
     *     so feature editing may proceed without engaging the split agent.
     */
    deactivate: function() {
        var deactivated = OpenLayers.Control.prototype.deactivate.call(this);
        if(deactivated) {
            if(this.source && this.source.events) {
                this.layer.events.un({
                    sketchcomplete: this.onSketchComplete,
                    afterfeaturemodified: this.afterFeatureModified,
                    scope: this
                });
            }
        }
        return deactivated;
    },
    
    /**
     * Method: onSketchComplete
     * Registered as a listener for the sketchcomplete event on the editable
     *     layer.
     *
     * Parameters:
     * event - {Object} The sketch complete event.
     *
     * Returns:
     * {Boolean} Stop the sketch from being added to the layer (it has been
     *     split).
     */
    onSketchComplete: function(event) {
        this.feature = null;
        return !this.considerSplit(event.feature);
    },
    
    /**
     * Method: afterFeatureModified
     * Registered as a listener for the afterfeaturemodified event on the
     *     editable layer.
     *
     * Parameters:
     * event - {Object} The after feature modified event.
     */
    afterFeatureModified: function(event) {
        if(event.modified) {
            var feature = event.feature;
            if(feature.geometry instanceof OpenLayers.Geometry.LineString ||
               feature.geometry instanceof OpenLayers.Geometry.MultiLineString) {
                this.feature = event.feature;
                this.considerSplit(event.feature);
            }
        }
    },
    
    /**
     * Method: removeByGeometry
     * Remove a feature from a list based on the given geometry.
     *
     * Parameters:
     * features - {Array(<OpenLayers.Feature.Vector>} A list of features.
     * geometry - {<OpenLayers.Geometry>} A geometry.
     */
    removeByGeometry: function(features, geometry) {
        for(var i=0, len=features.length; i<len; ++i) {
            if(features[i].geometry === geometry) {
                features.splice(i, 1);
                break;
            }
        }
    },
    
    /**
     * Method: isEligible
     * Test if a target feature is eligible for splitting.
     *
     * Parameters:
     * target - {<OpenLayers.Feature.Vector>} The target feature.
     *
     * Returns:
     * {Boolean} The target is eligible for splitting.
     */
    isEligible: function(target) {
        return (
            target.state !== OpenLayers.State.DELETE
        ) && (
            target.geometry instanceof OpenLayers.Geometry.LineString ||
            target.geometry instanceof OpenLayers.Geometry.MultiLineString
        ) && (
            this.feature !== target
        ) && (
            !this.targetFilter ||
            this.targetFilter.evaluate(target.attributes)
        );
    },
 
    /**
     * Method: considerSplit
     * Decide whether or not to split target features with the supplied
     *     feature.  If <mutual> is true, both the source and target features
     *     will be split if eligible.
     *
     * Parameters:
     * feature - {<OpenLayers.Feature.Vector}} The newly created or modified
     *     feature.
     *
     * Returns:
     * {Boolean} The supplied feature was split (and destroyed).
     */
    considerSplit: function(feature) {
        var sourceSplit = false;
        var targetSplit = false;
        if(!this.sourceFilter ||
           this.sourceFilter.evaluate(feature.attributes)) {
            var features = this.layer && this.layer.features || [];
            var target, results, proceed;
            var additions = [], removals = [];
            var mutual = (this.layer === this.source) && this.mutual;
            var options = {
                edge: this.edge,
                tolerance: this.tolerance,
                mutual: mutual
            };
            var sourceParts = [feature.geometry];
            var targetFeature, targetParts;
            var source, parts;
            for(var i=0, len=features.length; i<len; ++i) {
                targetFeature = features[i];
                if(this.isEligible(targetFeature)) {
                    targetParts = [targetFeature.geometry];
                    // work through source geoms - this array may change
                    for(var j=0; j<sourceParts.length; ++j) { 
                        source = sourceParts[j];
                        // work through target parts - this array may change
                        for(var k=0; k<targetParts.length; ++k) {
                            target = targetParts[k];
                            if(source.getBounds().intersectsBounds(target.getBounds())) {
                                results = source.split(target, options);
                                if(results) {
                                    proceed = this.events.triggerEvent(
                                        "beforesplit", {source: feature, target: targetFeature}
                                    );
                                    if(proceed !== false) {
                                        if(mutual) {
                                            parts = results[0];
                                            // handle parts that result from source splitting
                                            if(parts.length > 1) {
                                                // splice in new source parts
                                                parts.unshift(j, 1); // add args for splice below
                                                Array.prototype.splice.apply(sourceParts, parts);
                                                j += parts.length - 3;
                                            }
                                            results = results[1];
                                        }
                                        // handle parts that result from target splitting
                                        if(results.length > 1) {
                                            // splice in new target parts
                                            results.unshift(k, 1); // add args for splice below
                                            Array.prototype.splice.apply(targetParts, results);
                                            k += results.length - 3;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    if(targetParts && targetParts.length > 1) {
                        this.geomsToFeatures(targetFeature, targetParts);
                        this.events.triggerEvent("split", {
                            original: targetFeature,
                            features: targetParts
                        });
                        Array.prototype.push.apply(additions, targetParts);
                        removals.push(targetFeature);
                        targetSplit = true;
                    }
                }
            }
            if(sourceParts && sourceParts.length > 1) {
                this.geomsToFeatures(feature, sourceParts);
                this.even