Files
openlayers/lib/OpenLayers/Control/Split.js
tschaub 2b5ade2dde Adding back events documentation.
When the list of event types became unconstrained in 501b42228a, we lost the documentation for events that are triggered.  This change adds the list of events triggered to the API docs for events properties.
2012-02-16 10:36:34 -07:00

495 lines
19 KiB
JavaScript

/* Copyright (c) 2006-2012 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, {
/**
* 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.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 (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"
});