More trip planner testing with colors
[busui.git] / labs / openlayers / lib / OpenLayers / Handler / RegularPolygon.js
blob:a/labs/openlayers/lib/OpenLayers/Handler/RegularPolygon.js -> blob:b/labs/openlayers/lib/OpenLayers/Handler/RegularPolygon.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/Handler/Drag.js
  */
   
  /**
  * Class: OpenLayers.Handler.RegularPolygon
  * Handler to draw a regular polygon on the map. Polygon is displayed on mouse
  * down, moves or is modified on mouse move, and is finished on mouse up.
  * The handler triggers callbacks for 'done' and 'cancel'. Create a new
  * instance with the <OpenLayers.Handler.RegularPolygon> constructor.
  *
  * Inherits from:
  * - <OpenLayers.Handler>
  */
  OpenLayers.Handler.RegularPolygon = OpenLayers.Class(OpenLayers.Handler.Drag, {
   
  /**
  * APIProperty: sides
  * {Integer} Number of sides for the regular polygon. Needs to be greater
  * than 2. Defaults to 4.
  */
  sides: 4,
   
  /**
  * APIProperty: radius
  * {Float} Optional radius in map units of the regular polygon. If this is
  * set to some non-zero value, a polygon with a fixed radius will be
  * drawn and dragged with mose movements. If this property is not
  * set, dragging changes the radius of the polygon. Set to null by
  * default.
  */
  radius: null,
   
  /**
  * APIProperty: snapAngle
  * {Float} If set to a non-zero value, the handler will snap the polygon
  * rotation to multiples of the snapAngle. Value is an angle measured
  * in degrees counterclockwise from the positive x-axis.
  */
  snapAngle: null,
   
  /**
  * APIProperty: snapToggle
  * {String} If set, snapToggle is checked on mouse events and will set
  * the snap mode to the opposite of what it currently is. To disallow
  * toggling between snap and non-snap mode, set freehandToggle to
  * null. Acceptable toggle values are 'shiftKey', 'ctrlKey', and
  * 'altKey'. Snap mode is only possible if this.snapAngle is set to a
  * non-zero value.
  */
  snapToggle: 'shiftKey',
   
  /**
  * Property: layerOptions
  * {Object} Any optional properties to be set on the sketch layer.
  */
  layerOptions: null,
   
  /**
  * APIProperty: persist
  * {Boolean} Leave the feature rendered until clear is called. Default
  * is false. If set to true, the feature remains rendered until
  * clear is called, typically by deactivating the handler or starting
  * another drawing.
  */
  persist: false,
   
  /**
  * APIProperty: irregular
  * {Boolean} Draw an irregular polygon instead of a regular polygon.
  * Default is false. If true, the initial mouse down will represent
  * one corner of the polygon bounds and with each mouse movement, the
  * polygon will be stretched so the opposite corner of its bounds
  * follows the mouse position. This property takes precedence over
  * the radius property. If set to true, the radius property will
  * be ignored.
  */
  irregular: false,
   
  /**
  * Property: angle
  * {Float} The angle from the origin (mouse down) to the current mouse
  * position, in radians. This is measured counterclockwise from the
  * positive x-axis.
  */
  angle: null,
   
  /**
  * Property: fixedRadius
  * {Boolean} The polygon has a fixed radius. True if a radius is set before
  * drawing begins. False otherwise.
  */
  fixedRadius: false,
   
  /**
  * Property: feature
  * {<OpenLayers.Feature.Vector>} The currently drawn polygon feature
  */
  feature: null,
   
  /**
  * Property: layer
  * {<OpenLayers.Layer.Vector>} The temporary drawing layer
  */
  layer: null,
   
  /**
  * Property: origin
  * {<OpenLayers.Geometry.Point>} Location of the first mouse down
  */
  origin: null,
   
  /**
  * Constructor: OpenLayers.Handler.RegularPolygon
  * Create a new regular polygon handler.
  *
  * Parameters:
  * control - {<OpenLayers.Control>} The control that owns this handler
  * callbacks - {Object} An object with a properties whose values are
  * functions. Various callbacks described below.
  * options - {Object} An object with properties to be set on the handler.
  * If the options.sides property is not specified, the number of sides
  * will default to 4.
  *
  * Named callbacks:
  * create - Called when a sketch is first created. Callback called with
  * the creation point geometry and sketch feature.
  * done - Called when the sketch drawing is finished. The callback will
  * recieve a single argument, the sketch geometry.
  * cancel - Called when the handler is deactivated while drawing. The
  * cancel callback will receive a geometry.
  */
  initialize: function(control, callbacks, options) {
  if(!(options && options.layerOptions && options.layerOptions.styleMap)) {
  this.style = OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'], {});
  }
   
  OpenLayers.Handler.prototype.initialize.apply(this,
  [control, callbacks, options]);
  this.options = (options) ? options : {};
  },
   
  /**
  * APIMethod: setOptions
  *
  * Parameters:
  * newOptions - {Object}
  */
  setOptions: function (newOptions) {
  OpenLayers.Util.extend(this.options, newOptions);
  OpenLayers.Util.extend(this, newOptions);
  },
   
  /**
  * APIMethod: activate
  * Turn on the handler.
  *
  * Return:
  * {Boolean} The handler was successfully activated
  */
  activate: function() {
  var activated = false;
  if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
  // create temporary vector layer for rendering geometry sketch
  var options = OpenLayers.Util.extend({
  displayInLayerSwitcher: false,
  // indicate that the temp vector layer will never be out of range
  // without this, resolution properties must be specified at the
  // map-level for this temporary layer to init its resolutions
  // correctly
  calculateInRange: OpenLayers.Function.True
  }, this.layerOptions);
  this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME, options);
  this.map.addLayer(this.layer);
  activated = true;
  }
  return activated;
  },
   
  /**
  * APIMethod: deactivate
  * Turn off the handler.
  *
  * Return:
  * {Boolean} The handler was successfully deactivated
  */
  deactivate: function() {
  var deactivated = false;
  if(OpenLayers.Handler.Drag.prototype.deactivate.apply(this, arguments)) {
  // call the cancel callback if mid-drawing
  if(this.dragging) {
  this.cancel();
  }
  // If a layer's map property is set to null, it means that that
  // layer isn't added to the map. Since we ourself added the layer
  // to the map in activate(), we can assume that if this.layer.map
  // is null it means that the layer has been destroyed (as a result
  // of map.destroy() for example.
  if (this.layer.map != null) {
  this.layer.destroy(false);
  if (this.feature) {
  this.feature.destroy();
  }
  }
  this.layer = null;
  this.feature = null;
  deactivated = true;
  }
  return deactivated;
  },
   
  /**
  * Method: down
  * Start drawing a new feature
  *
  * Parameters:
  * evt - {Event} The drag start event
  */
  down: function(evt) {
  this.fixedRadius = !!(this.radius);
  var maploc = this.map.getLonLatFromPixel(evt.xy);
  this.origin = new OpenLayers.Geometry.Point(maploc.lon, maploc.lat);
  // create the new polygon
  if(!this.fixedRadius || this.irregular) {
  // smallest radius should not be less one pixel in map units
  // VML doesn't behave well with smaller
  this.radius = this.map.getResolution();
  }
  if(this.persist) {
  this.clear();
  }
  this.feature = new OpenLayers.Feature.Vector();
  this.createGeometry();
  this.callback("create", [this.origin, this.feature]);
  this.layer.addFeatures([this.feature], {silent: true});
  this.layer.drawFeature(this.feature, this.style);
  },
   
  /**
  * Method: move
  * Respond to drag move events
  *
  * Parameters:
  * evt - {Evt} The move event
  */
  move: function(evt) {
  var maploc = this.map.getLonLatFromPixel(evt.xy);
  var point = new OpenLayers.Geometry.Point(maploc.lon, maploc.lat);
  if(this.irregular) {
  var ry = Math.sqrt(2) * Math.abs(point.y - this.origin.y) / 2;
  this.radius = Math.max(this.map.getResolution() / 2, ry);
  } else if(this.fixedRadius) {
  this.origin = point;
  } else {
  this.calculateAngle(point, evt);
  this.radius = Math.max(this.map.getResolution() / 2,
  point.distanceTo(this.origin));
  }
  this.modifyGeometry();
  if(this.irregular) {
  var dx = point.x - this.origin.x;
  var dy = point.y - this.origin.y;
  var ratio;
  if(dy == 0) {
  ratio = dx / (this.radius * Math.sqrt(2));
  } else {
  ratio = dx / dy;
  }
  this.feature.geometry.resize(1, this.origin, ratio);
  this.feature.geometry.move(dx / 2, dy / 2);
  }
  this.layer.drawFeature(this.feature, this.style);
  },
   
  /**
  * Method: up
  * Finish drawing the feature
  *
  * Parameters:
  * evt - {Event} The mouse up event
  */
  up: function(evt) {
  this.finalize();
  // the mouseup method of superclass doesn't call the
  // "done" callback if there's been no move between
  // down and up
  if (this.start == this.last) {
  this.callback("done", [evt.xy]);
  }
  },
   
  /**
  * Method: out
  * Finish drawing the feature.
  *
  * Parameters:
  * evt - {Event} The mouse out event
  */
  out: function(evt) {
  this.finalize();
  },
   
  /**
  * Method: createGeometry
  * Create the new polygon geometry. This is called at the start of the
  * drag and at any point during the drag if the number of sides
  * changes.
  */
  createGeometry: function() {
  this.angle = Math.PI * ((1/this.sides) - (1/2));
  if(this.snapAngle) {
  this.angle += this.snapAngle * (Math.PI / 180);
  }
  this.feature.geometry = OpenLayers.Geometry.Polygon.createRegularPolygon(
  this.origin, this.radius, this.sides, this.snapAngle
  );
  },
   
  /**
  * Method: modifyGeometry
  * Modify the polygon geometry in place.
  */
  modifyGeometry: function() {
  var angle, point;
  var ring = this.feature.geometry.components[0];
  // if the number of sides ever changes, create a new geometry
  if(ring.components.length != (this.sides + 1)) {
  this.createGeometry();
  ring = this.feature.geometry.components[0];
  }
  for(var i=0; i<this.sides; ++i) {
  point = ring.components[i];
  angle = this.angle + (i * 2 * Math.PI / this.sides);
  point.x = this.origin.x + (this.radius * Math.cos(angle));
  point.y = this.origin.y + (this.radius * Math.sin(angle));
  point.clearBounds();
  }
  },
   
  /**
  * Method: calculateAngle
  * Calculate the angle based on settings.
  *
  * Parameters:
  * point - {<OpenLayers.Geometry.Point>}
  * evt - {Event}
  */
  calculateAngle: function(point, evt) {
  var alpha = Math.atan2(point.y - this.origin.y,
  point.x - this.origin.x);
  if(this.snapAngle && (this.snapToggle && !evt[this.snapToggle])) {
  var snapAngleRad = (Math.PI / 180) * this.snapAngle;
  this.angle = Math.round(alpha / snapAngleRad) * snapAngleRad;
  } else {
  this.angle = alpha;
  }
  },
   
  /**
  * APIMethod: cancel
  * Finish the geometry and call the "cancel" callback.
  */
  cancel: function() {
  // the polygon geometry gets cloned in the callback method
  this.callback("cancel", null);