| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498 |
- /* Copyright (c) 2006-2011 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>
- */
- 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 <OpenLayers.Control>):
- * 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
- * {<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 target 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) {
- // 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(<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) {
- 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 <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"
- });
|