summaryrefslogtreecommitdiff
path: root/misc/openlayers/lib/OpenLayers/Control/Split.js
diff options
context:
space:
mode:
Diffstat (limited to 'misc/openlayers/lib/OpenLayers/Control/Split.js')
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/Split.js494
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"
+});