More trip planner testing with colors
[busui.git] / labs / openlayers / lib / OpenLayers / Control / Split.js
blob:a/labs/openlayers/lib/OpenLayers/Control/Split.js -> blob:b/labs/openlayers/lib/OpenLayers/Control/Split.js
  /* Copyright (c) 2006-2010 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) {</