diff options
Diffstat (limited to 'misc/openlayers/lib/OpenLayers/Control/Split.js')
-rw-r--r-- | misc/openlayers/lib/OpenLayers/Control/Split.js | 494 |
1 files changed, 494 insertions, 0 deletions
diff --git a/misc/openlayers/lib/OpenLayers/Control/Split.js b/misc/openlayers/lib/OpenLayers/Control/Split.js new file mode 100644 index 0000000..de19eb7 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Control/Split.js @@ -0,0 +1,494 @@ +/* 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> + */ +OpenLayers.Control.Split = OpenLayers.Class(OpenLayers.Control, { + + /** + * APIProperty: events + * {<OpenLayers.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 <OpenLayers.Control.events>): + * 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 + * {<OpenLayers.Layer.Vector>} The target layer with features to be split. + * Set at construction or after construction with <setLayer>. + */ + layer: null, + + /** + * Property: source + * {<OpenLayers.Layer.Vector>} 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 + * <tolerance> 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 source layer is eligible for + * splitting. + */ + sourceFilter: null, + + /** + * Property: handler + * {<OpenLayers.Handler.Path>} 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 + * <tolerance> 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) { + 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.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(<OpenLayers.Feature.Vector>)} A list of features. + * geometry - {<OpenLayers.Geometry>} A geometry. + */ + removeByGeometry: function(features, geometry) { + for(var i=0, len=features.length; i<len; ++i) { + if(features[i].geometry === geometry) { + features.splice(i, 1); + break; + } + } + }, + + /** + * Method: isEligible + * Test if a target feature is eligible for splitting. + * + * Parameters: + * target - {<OpenLayers.Feature.Vector>} 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 <mutual> is true, both the source and target features + * will be split if eligible. + * + * Parameters: + * feature - {<OpenLayers.Feature.Vector>} 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<len; ++i) { + targetFeature = features[i]; + if(this.isEligible(targetFeature)) { + targetParts = [targetFeature.geometry]; + // work through source geoms - this array may change + for(var j=0; j<sourceParts.length; ++j) { + source = sourceParts[j]; + // work through target parts - this array may change + for(var k=0; k<targetParts.length; ++k) { + target = targetParts[k]; + if(source.getBounds().intersectsBounds(target.getBounds())) { + results = source.split(target, options); + if(results) { + proceed = this.events.triggerEvent( + "beforesplit", {source: feature, target: targetFeature} + ); + if(proceed !== false) { + if(mutual) { + parts = results[0]; + // handle parts that result from source splitting + if(parts.length > 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<len; ++i) { + feat = removals[i]; + if(feat.state === OpenLayers.State.INSERT) { + destroys.push(feat); + } else { + feat.state = OpenLayers.State.DELETE; + this.layer.drawFeature(feat); + } + } + this.layer.destroyFeatures(destroys, {silent: true}); + for(var i=0, len=additions.length; i<len; ++i) { + additions[i].state = OpenLayers.State.INSERT; + } + } else { + this.layer.destroyFeatures(removals, {silent: true}); + } + this.layer.addFeatures(additions, {silent: true}); + this.events.triggerEvent("aftersplit", { + source: feature, + features: additions + }); + } + } + return sourceSplit; + }, + + /** + * Method: geomsToFeatures + * Create new features given a template feature and a list of geometries. + * The list of geometries is modified in place. The result will be + * a list of new features. + * + * Parameters: + * feature - {<OpenLayers.Feature.Vector>} The feature to be cloned. + * geoms - {Array(<OpenLayers.Geometry>)} 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<len; ++i) { + // turn results list from geoms to features + newFeature = clone.clone(); + newFeature.geometry = geoms[i]; + newFeature.state = OpenLayers.State.INSERT; + geoms[i] = newFeature; + } + }, + + /** + * Method: destroy + * Clean up the control. + */ + destroy: function() { + if(this.active) { + this.deactivate(); // TODO: this should be handled by the super + } + OpenLayers.Control.prototype.destroy.call(this); + }, + + CLASS_NAME: "OpenLayers.Control.Split" +}); |