--- a/labs/openlayers/lib/OpenLayers/Control/Split.js +++ b/labs/openlayers/lib/OpenLayers/Control/Split.js @@ -1,1 +1,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.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 ): + * 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 + * {} The target layer with features to be split. + * Set at construction or after construction with . + */ + layer: null, + + /** + * Property: source + * {} 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 + * 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 + * {} 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 + * 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(} A list of features. + * geometry - {} A geometry. + */ + removeByGeometry: function(features, geometry) { + for(var i=0, len=features.length; i} 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 is true, both the source and target features + * will be split if eligible. + * + * Parameters: + * feature - { 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.events.triggerEvent("split", { + original: feature, + features: sourceParts + }); + Array.prototype.push.apply(additions, sourceParts); + removals.push(feature); + sourceSplit = true; + } + if(sourceSplit || targetSplit) { + // remove and add feature events are suppressed + // listen for split event on this control instead + if(this.deferDelete) { + // Set state instead of removing. Take care to avoid + // setting delete for features that have not yet been + // inserted - those should be destroyed immediately. + var feat, destroys = []; + for(var i=0, len=removals.length; i} The feature to be cloned. + * geoms - {Array()} List of goemetries. This will + * become a list of new features. + */ + geomsToFeatures: function(feature, geoms) { + var clone = feature.clone(); + delete clone.geometry; + var newFeature; + for(var i=0, len=geoms.length; i