Add a control that acts as a split agent. The control can be configured to listen for edits on any source layer or can create a temporary sketch layer for creating a split source. Source features will be used to split targets on a target layer. The deferDelete option tells the control to modify feature state instead of destroying split features (for use with a save strategy and a protocol that persists changes). r=ahocevar (closes #1972)
git-svn-id: http://svn.openlayers.org/trunk/openlayers@8998 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
This commit is contained in:
497
lib/OpenLayers/Control/Split.js
Normal file
497
lib/OpenLayers/Control/Split.js
Normal file
@@ -0,0 +1,497 @@
|
||||
/* Copyright (c) 2006-2008 MetaCarta, Inc., 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"],
|
||||
|
||||
/**
|
||||
* Property: 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,
|
||||
|
||||
/**
|
||||
* Property: 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,
|
||||
|
||||
/**
|
||||
* Property: 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,
|
||||
|
||||
/**
|
||||
* Property: 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,
|
||||
|
||||
/**
|
||||
* Property: 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,
|
||||
|
||||
/**
|
||||
* Property: targetFilter
|
||||
* {OpenLayers.Filter} Optional filter that will be evaluated
|
||||
* to determine if a feature from the target layer is eligible for
|
||||
* splitting.
|
||||
*/
|
||||
targetFilter: null,
|
||||
|
||||
/**
|
||||
* Property: 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) {
|
||||
sourceSplit = false;
|
||||
targetSplit = false;
|
||||
if(!this.sourceFilter ||
|
||||
this.sourceFilter.evaluate(feature.attributes)) {
|
||||
var features = this.layer && this.layer.features || [];
|
||||
var target, results, result, 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"
|
||||
});
|
||||
Reference in New Issue
Block a user