1 /* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2 * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3 * full text of the license. */
6 * @requires OpenLayers/Control.js
7 * @requires OpenLayers/Handler/Path.js
8 * @requires OpenLayers/Layer/Vector.js
12 * Class: OpenLayers.Control.Split
13 * Acts as a split feature agent while editing vector features.
16 * - <OpenLayers.Control>
18 OpenLayers.Control.Split = OpenLayers.Class(OpenLayers.Control, {
21 * Constant: EVENT_TYPES
22 * {Array(String)} Supported application event types. Register a listener
23 * for a particular event with the following syntax:
25 * control.events.register(type, obj, listener);
28 * Listeners will be called with a reference to an event object. The
29 * properties of this event depends on exactly what happened.
31 * Supported control event types (in addition to those from <OpenLayers.Control>):
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.
49 EVENT_TYPES: ["beforesplit", "split", "aftersplit"],
53 * {<OpenLayers.Layer.Vector>} The target layer with features to be split.
54 * Set at construction or after construction with <setLayer>.
60 * {<OpenLayers.Layer.Vector>} Optional source layer. Any newly created
61 * or modified features from this layer will be used to split features
62 * on the target layer. If not provided, a temporary sketch layer will
68 * Property: sourceOptions
69 * {Options} If a temporary sketch layer is created, these layer options
75 * APIProperty: tolerance
76 * {Number} Distance between the calculated intersection and a vertex on
77 * the source geometry below which the existing vertex will be used
78 * for the split. Default is null.
84 * {Boolean} Allow splits given intersection of edges only. Default is
85 * true. If false, a vertex on the source must be within the
86 * <tolerance> distance of the calculated intersection for a split
92 * APIProperty: deferDelete
93 * {Boolean} Instead of removing features from the layer, set feature
94 * states of split features to DELETE. This assumes a save strategy
95 * or other component is in charge of removing features from the
96 * layer. Default is false. If false, split features will be
97 * immediately deleted from the layer.
102 * APIProperty: mutual
103 * {Boolean} If source and target layers are the same, split source
104 * features and target features where they intersect. Default is
105 * true. If false, only target features will be split.
110 * APIProperty: targetFilter
111 * {OpenLayers.Filter} Optional filter that will be evaluated
112 * to determine if a feature from the target layer is eligible for
118 * APIProperty: sourceFilter
119 * {OpenLayers.Filter} Optional filter that will be evaluated
120 * to determine if a feature from the target layer is eligible for
127 * {<OpenLayers.Handler.Path>} The temporary sketch handler created if
128 * no source layer is provided.
133 * Constructor: OpenLayers.Control.Split
134 * Creates a new split control. A control is constructed with a target
135 * layer and an optional source layer. While the control is active,
136 * creating new features or modifying existing features on the source
137 * layer will result in splitting any eligible features on the target
138 * layer. If no source layer is provided, a temporary sketch layer will
139 * be created to create lines for splitting features on the target.
142 * options - {Object} An object containing all configuration properties for
146 * layer - {OpenLayers.Layer.Vector} The target layer. Features from this
147 * layer will be split by new or modified features on the source layer
148 * or temporary sketch layer.
149 * source - {OpenLayers.Layer.Vector} Optional source layer. If provided
150 * newly created features or modified features will be used to split
151 * features on the target layer. If not provided, a temporary sketch
152 * layer will be created for drawing lines.
153 * tolerance - {Number} Optional value for the distance between a source
154 * vertex and the calculated intersection below which the split will
155 * occur at the vertex.
156 * edge - {Boolean} Allow splits given intersection of edges only. Default
157 * is true. If false, a vertex on the source must be within the
158 * <tolerance> distance of the calculated intersection for a split
160 * mutual - {Boolean} If source and target are the same, split source
161 * features and target features where they intersect. Default is
162 * true. If false, only target features will be split.
163 * targetFilter - {OpenLayers.Filter} Optional filter that will be evaluated
164 * to determine if a feature from the target layer is eligible for
166 * sourceFilter - {OpenLayers.Filter} Optional filter that will be evaluated
167 * to determine if a feature from the target layer is eligible for
170 initialize: function(options) {
171 // concatenate events specific to measure with those from the base
172 Array.prototype.push.apply(
173 this.EVENT_TYPES, OpenLayers.Control.prototype.EVENT_TYPES
175 OpenLayers.Control.prototype.initialize.apply(this, [options]);
176 this.options = options || {}; // TODO: this could be done by the super
178 // set the source layer if provided
179 if(this.options.source) {
180 this.setSource(this.options.source);
185 * APIMethod: setSource
186 * Set the source layer for edits layer.
189 * layer - {OpenLayers.Layer.Vector} The new source layer layer. If
190 * null, a temporary sketch layer will be created.
192 setSource: function(layer) {
196 this.handler.destroy();
207 * APIMethod: activate
208 * Activate the control. Activating the control registers listeners for
209 * editing related events so that during feature creation and
210 * modification, features in the target will be considered for
213 activate: function() {
214 var activated = OpenLayers.Control.prototype.activate.call(this);
218 this.handler = new OpenLayers.Handler.Path(this,
219 {done: function(geometry) {
220 this.onSketchComplete({
221 feature: new OpenLayers.Feature.Vector(geometry)
224 {layerOptions: this.sourceOptions}
227 this.handler.activate();
228 } else if(this.source.events) {
229 this.source.events.on({
230 sketchcomplete: this.onSketchComplete,
231 afterfeaturemodified: this.afterFeatureModified,
240 * APIMethod: deactivate
241 * Deactivate the control. Deactivating the control unregisters listeners
242 * so feature editing may proceed without engaging the split agent.
244 deactivate: function() {
245 var deactivated = OpenLayers.Control.prototype.deactivate.call(this);
247 if(this.source && this.source.events) {
248 this.layer.events.un({
249 sketchcomplete: this.onSketchComplete,
250 afterfeaturemodified: this.afterFeatureModified,
259 * Method: onSketchComplete
260 * Registered as a listener for the sketchcomplete event on the editable
264 * event - {Object} The sketch complete event.
267 * {Boolean} Stop the sketch from being added to the layer (it has been
270 onSketchComplete: function(event) {
272 return !this.considerSplit(event.feature);
276 * Method: afterFeatureModified
277 * Registered as a listener for the afterfeaturemodified event on the
281 * event - {Object} The after feature modified event.
283 afterFeatureModified: function(event) {
285 var feature = event.feature;
286 if(feature.geometry instanceof OpenLayers.Geometry.LineString ||
287 feature.geometry instanceof OpenLayers.Geometry.MultiLineString) {
288 this.feature = event.feature;
289 this.considerSplit(event.feature);
295 * Method: removeByGeometry
296 * Remove a feature from a list based on the given geometry.
299 * features - {Array(<OpenLayers.Feature.Vector>} A list of features.
300 * geometry - {<OpenLayers.Geometry>} A geometry.
302 removeByGeometry: function(features, geometry) {
303 for(var i=0, len=features.length; i<len; ++i) {
304 if(features[i].geometry === geometry) {
305 features.splice(i, 1);
313 * Test if a target feature is eligible for splitting.
316 * target - {<OpenLayers.Feature.Vector>} The target feature.
319 * {Boolean} The target is eligible for splitting.
321 isEligible: function(target) {
323 target.state !== OpenLayers.State.DELETE
325 target.geometry instanceof OpenLayers.Geometry.LineString ||
326 target.geometry instanceof OpenLayers.Geometry.MultiLineString
328 this.feature !== target
330 !this.targetFilter ||
331 this.targetFilter.evaluate(target.attributes)
336 * Method: considerSplit
337 * Decide whether or not to split target features with the supplied
338 * feature. If <mutual> is true, both the source and target features
339 * will be split if eligible.
342 * feature - {<OpenLayers.Feature.Vector}} The newly created or modified
346 * {Boolean} The supplied feature was split (and destroyed).
348 considerSplit: function(feature) {
351 if(!this.sourceFilter ||
352 this.sourceFilter.evaluate(feature.attributes)) {
353 var features = this.layer && this.layer.features || [];
354 var target, results, result, proceed;
355 var additions = [], removals = [];
356 var mutual = (this.layer === this.source) && this.mutual;
359 tolerance: this.tolerance,
362 var sourceParts = [feature.geometry];
363 var targetFeature, targetParts;
365 for(var i=0, len=features.length; i<len; ++i) {
366 targetFeature = features[i];
367 if(this.isEligible(targetFeature)) {
368 targetParts = [targetFeature.geometry];
369 // work through source geoms - this array may change
370 for(var j=0; j<sourceParts.length; ++j) {
371 source = sourceParts[j];
372 // work through target parts - this array may change
373 for(var k=0; k<targetParts.length; ++k) {
374 target = targetParts[k];
375 if(source.getBounds().intersectsBounds(target.getBounds())) {
376 results = source.split(target, options);
378 proceed = this.events.triggerEvent(
379 "beforesplit", {source: feature, target: targetFeature}
381 if(proceed !== false) {
384 // handle parts that result from source splitting
385 if(parts.length > 1) {
386 // splice in new source parts
387 parts.unshift(j, 1); // add args for splice below
388 Array.prototype.splice.apply(sourceParts, parts);
389 j += parts.length - 3;
391 results = results[1];
393 // handle parts that result from target splitting
394 if(results.length > 1) {
395 // splice in new target parts
396 results.unshift(k, 1); // add args for splice below
397 Array.prototype.splice.apply(targetParts, results);
398 k += results.length - 3;
405 if(targetParts && targetParts.length > 1) {
406 this.geomsToFeatures(targetFeature, targetParts);
407 this.events.triggerEvent("split", {
408 original: targetFeature,
409 features: targetParts
411 Array.prototype.push.apply(additions, targetParts);
412 removals.push(targetFeature);
417 if(sourceParts && sourceParts.length > 1) {
418 this.geomsToFeatures(feature, sourceParts);
419 this.events.triggerEvent("split", {
421 features: sourceParts
423 Array.prototype.push.apply(additions, sourceParts);
424 removals.push(feature);
427 if(sourceSplit || targetSplit) {
428 // remove and add feature events are suppressed
429 // listen for split event on this control instead
430 if(this.deferDelete) {
431 // Set state instead of removing. Take care to avoid
432 // setting delete for features that have not yet been
433 // inserted - those should be destroyed immediately.
434 var feat, destroys = [];
435 for(var i=0, len=removals.length; i<len; ++i) {
437 if(feat.state === OpenLayers.State.INSERT) {
440 feat.state = OpenLayers.State.DELETE;
441 this.layer.drawFeature(feat);
444 this.layer.destroyFeatures(destroys, {silent: true});
445 for(var i=0, len=additions.length; i<len; ++i) {
446 additions[i].state = OpenLayers.State.INSERT;
449 this.layer.destroyFeatures(removals, {silent: true});
451 this.layer.addFeatures(additions, {silent: true});
452 this.events.triggerEvent("aftersplit", {
462 * Method: geomsToFeatures
463 * Create new features given a template feature and a list of geometries.
464 * The list of geometries is modified in place. The result will be
465 * a list of new features.
468 * feature - {<OpenLayers.Feature.Vector>} The feature to be cloned.
469 * geoms - {Array(<OpenLayers.Geometry>)} List of goemetries. This will
470 * become a list of new features.
472 geomsToFeatures: function(feature, geoms) {
473 var clone = feature.clone();
474 delete clone.geometry;
476 for(var i=0, len=geoms.length; i<len; ++i) {
477 // turn results list from geoms to features
478 newFeature = clone.clone();
479 newFeature.geometry = geoms[i];
480 newFeature.state = OpenLayers.State.INSERT;
481 geoms[i] = newFeature;
487 * Clean up the control.
489 destroy: function() {
491 this.deactivate(); // TODO: this should be handled by the super
493 OpenLayers.Control.prototype.destroy.call(this);
496 CLASS_NAME: "OpenLayers.Control.Split"