/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository 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, { /** * APIProperty: events * {} Events instance for listeners and triggering * control specific events. * * Register a listener for a particular event with the following syntax: * (code) * control.events.register(type, obj, listener); * (end) * * Supported 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. */ /** * 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 * {} Optional filter that will be evaluated * to determine if a feature from the target layer is eligible for * splitting. */ targetFilter: null, /** * APIProperty: sourceFilter * {} Optional filter that will be evaluated * to determine if a feature from the source 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 - {} The target layer. Features from this * layer will be split by new or modified features on the source layer * or temporary sketch layer. * source - {} 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 - {} Optional filter that will be evaluated * to determine if a feature from the target layer is eligible for * splitting. * sourceFilter - {} Optional filter that will be evaluated * to determine if a feature from the target layer is eligible for * splitting. */ initialize: function(options) { 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 - {} 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.source.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 (typeof feature.geometry.split === "function") { 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) { if (!target.geometry) { return false; } else { return ( target.state !== OpenLayers.State.DELETE ) && ( typeof target.geometry.split === "function" ) && ( 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 - {} 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 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