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
|
});
|