]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/Control/Split.js
initial commit
[syp.git] / openlayers / lib / OpenLayers / Control / Split.js
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. */
4
5 /**
6  * @requires OpenLayers/Control.js
7  * @requires OpenLayers/Handler/Path.js
8  * @requires OpenLayers/Layer/Vector.js
9  */
10
11 /**
12  * Class: OpenLayers.Control.Split
13  * Acts as a split feature agent while editing vector features.
14  *
15  * Inherits from:
16  *  - <OpenLayers.Control>
17  */
18 OpenLayers.Control.Split = OpenLayers.Class(OpenLayers.Control, {
19
20     /**
21      * Constant: EVENT_TYPES
22      * {Array(String)} Supported application event types.  Register a listener
23      *     for a particular event with the following syntax:
24      * (code)
25      * control.events.register(type, obj, listener);
26      * (end)
27      *
28      * Listeners will be called with a reference to an event object.  The
29      *     properties of this event depends on exactly what happened.
30      *
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.
48      */
49     EVENT_TYPES: ["beforesplit", "split", "aftersplit"],
50     
51     /**
52      * APIProperty: layer
53      * {<OpenLayers.Layer.Vector>} The target layer with features to be split.
54      *     Set at construction or after construction with <setLayer>.
55      */
56     layer: null,
57     
58     /**
59      * Property: source
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
63      *     be created.
64      */
65     source: null,
66     
67     /**
68      * Property: sourceOptions
69      * {Options} If a temporary sketch layer is created, these layer options
70      *     will be applied.
71      */
72     sourceOptions: null,
73
74     /**
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.
79      */
80     tolerance: null,
81     
82     /**
83      * APIProperty: edge
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
87      *     to occur.
88      */
89     edge: true,
90     
91     /**
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.
98      */
99     deferDelete: false,
100     
101     /**
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.
106      */
107     mutual: true,
108     
109     /**
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
113      *     splitting.
114      */
115     targetFilter: null,
116     
117     /**
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
121      *     splitting.
122      */
123     sourceFilter: null,
124     
125     /**
126      * Property: handler
127      * {<OpenLayers.Handler.Path>} The temporary sketch handler created if
128      *     no source layer is provided.
129      */
130     handler: null,
131
132     /**
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.
140      *
141      * Parameters:
142      * options - {Object} An object containing all configuration properties for
143      *     the control.
144      *
145      * Valid options:
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
159      *     to occur.
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
165      *     splitting.
166      * sourceFilter - {OpenLayers.Filter} Optional filter that will be evaluated
167      *     to determine if a feature from the target layer is eligible for
168      *     splitting.
169      */
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
174         );
175         OpenLayers.Control.prototype.initialize.apply(this, [options]);
176         this.options = options || {}; // TODO: this could be done by the super
177         
178         // set the source layer if provided
179         if(this.options.source) {
180             this.setSource(this.options.source);
181         }
182     },
183     
184     /**
185      * APIMethod: setSource
186      * Set the source layer for edits layer.
187      *
188      * Parameters:
189      * layer - {OpenLayers.Layer.Vector}  The new source layer layer.  If
190      *     null, a temporary sketch layer will be created.
191      */
192     setSource: function(layer) {
193         if(this.active) {
194             this.deactivate();
195             if(this.handler) {
196                 this.handler.destroy();
197                 delete this.handler;
198             }
199             this.source = layer;
200             this.activate();
201         } else {
202             this.source = layer;
203         }
204     },
205     
206     /**
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
211      *     splitting.
212      */
213     activate: function() {
214         var activated = OpenLayers.Control.prototype.activate.call(this);
215         if(activated) {
216             if(!this.source) {
217                 if(!this.handler) {
218                     this.handler = new OpenLayers.Handler.Path(this,
219                         {done: function(geometry) {
220                             this.onSketchComplete({
221                                 feature: new OpenLayers.Feature.Vector(geometry)
222                             });
223                         }},
224                         {layerOptions: this.sourceOptions}
225                     );
226                 }
227                 this.handler.activate();
228             } else if(this.source.events) {
229                 this.source.events.on({
230                     sketchcomplete: this.onSketchComplete,
231                     afterfeaturemodified: this.afterFeatureModified,
232                     scope: this
233                 });
234             }
235         }
236         return activated;
237     },
238     
239     /**
240      * APIMethod: deactivate
241      * Deactivate the control.  Deactivating the control unregisters listeners
242      *     so feature editing may proceed without engaging the split agent.
243      */
244     deactivate: function() {
245         var deactivated = OpenLayers.Control.prototype.deactivate.call(this);
246         if(deactivated) {
247             if(this.source && this.source.events) {
248                 this.layer.events.un({
249                     sketchcomplete: this.onSketchComplete,
250                     afterfeaturemodified: this.afterFeatureModified,
251                     scope: this
252                 });
253             }
254         }
255         return deactivated;
256     },
257     
258     /**
259      * Method: onSketchComplete
260      * Registered as a listener for the sketchcomplete event on the editable
261      *     layer.
262      *
263      * Parameters:
264      * event - {Object} The sketch complete event.
265      *
266      * Returns:
267      * {Boolean} Stop the sketch from being added to the layer (it has been
268      *     split).
269      */
270     onSketchComplete: function(event) {
271         this.feature = null;
272         return !this.considerSplit(event.feature);
273     },
274     
275     /**
276      * Method: afterFeatureModified
277      * Registered as a listener for the afterfeaturemodified event on the
278      *     editable layer.
279      *
280      * Parameters:
281      * event - {Object} The after feature modified event.
282      */
283     afterFeatureModified: function(event) {
284         if(event.modified) {
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);
290             }
291         }
292     },
293     
294     /**
295      * Method: removeByGeometry
296      * Remove a feature from a list based on the given geometry.
297      *
298      * Parameters:
299      * features - {Array(<OpenLayers.Feature.Vector>} A list of features.
300      * geometry - {<OpenLayers.Geometry>} A geometry.
301      */
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);
306                 break;
307             }
308         }
309     },
310     
311     /**
312      * Method: isEligible
313      * Test if a target feature is eligible for splitting.
314      *
315      * Parameters:
316      * target - {<OpenLayers.Feature.Vector>} The target feature.
317      *
318      * Returns:
319      * {Boolean} The target is eligible for splitting.
320      */
321     isEligible: function(target) {
322         return (
323             target.state !== OpenLayers.State.DELETE
324         ) && (
325             target.geometry instanceof OpenLayers.Geometry.LineString ||
326             target.geometry instanceof OpenLayers.Geometry.MultiLineString
327         ) && (
328             this.feature !== target
329         ) && (
330             !this.targetFilter ||
331             this.targetFilter.evaluate(target.attributes)
332         );
333     },
334
335     /**
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.
340      *
341      * Parameters:
342      * feature - {<OpenLayers.Feature.Vector}} The newly created or modified
343      *     feature.
344      *
345      * Returns:
346      * {Boolean} The supplied feature was split (and destroyed).
347      */
348     considerSplit: function(feature) {
349         sourceSplit = false;
350         targetSplit = false;
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;
357             var options = {
358                 edge: this.edge,
359                 tolerance: this.tolerance,
360                 mutual: mutual
361             };
362             var sourceParts = [feature.geometry];
363             var targetFeature, targetParts;
364             var source, parts;
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);
377                                 if(results) {
378                                     proceed = this.events.triggerEvent(
379                                         "beforesplit", {source: feature, target: targetFeature}
380                                     );
381                                     if(proceed !== false) {
382                                         if(mutual) {
383                                             parts = results[0];
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;
390                                             }
391                                             results = results[1];
392                                         }
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;
399                                         }
400                                     }
401                                 }
402                             }
403                         }
404                     }
405                     if(targetParts && targetParts.length > 1) {
406                         this.geomsToFeatures(targetFeature, targetParts);
407                         this.events.triggerEvent("split", {
408                             original: targetFeature,
409                             features: targetParts
410                         });
411                         Array.prototype.push.apply(additions, targetParts);
412                         removals.push(targetFeature);
413                         targetSplit = true;
414                     }
415                 }
416             }
417             if(sourceParts && sourceParts.length > 1) {
418                 this.geomsToFeatures(feature, sourceParts);
419                 this.events.triggerEvent("split", {
420                     original: feature,
421                     features: sourceParts
422                 });
423                 Array.prototype.push.apply(additions, sourceParts);
424                 removals.push(feature);
425                 sourceSplit = true;
426             }
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) {
436                         feat = removals[i];
437                         if(feat.state === OpenLayers.State.INSERT) {
438                             destroys.push(feat);
439                         } else {
440                             feat.state = OpenLayers.State.DELETE;
441                             this.layer.drawFeature(feat);
442                         }
443                     }
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;
447                     }
448                 } else {
449                     this.layer.destroyFeatures(removals, {silent: true});
450                 }
451                 this.layer.addFeatures(additions, {silent: true});
452                 this.events.triggerEvent("aftersplit", {
453                     source: feature,
454                     features: additions
455                 });
456             }
457         }
458         return sourceSplit;
459     },
460     
461     /**
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.
466      *
467      * Parameters:
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.
471      */
472     geomsToFeatures: function(feature, geoms) {
473         var clone = feature.clone();
474         delete clone.geometry;
475         var newFeature;
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;
482         }
483     },
484     
485     /**
486      * Method: destroy
487      * Clean up the control.
488      */
489     destroy: function() {
490         if(this.active) {
491             this.deactivate(); // TODO: this should be handled by the super
492         }
493         OpenLayers.Control.prototype.destroy.call(this);
494     },
495
496     CLASS_NAME: "OpenLayers.Control.Split"
497 });