1 /* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2 * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3 * full text of the license. */
7 * @requires OpenLayers/Handler/Drag.js
11 * Class: OpenLayers.Handler.RegularPolygon
12 * Handler to draw a regular polygon on the map. Polygon is displayed on mouse
13 * down, moves or is modified on mouse move, and is finished on mouse up.
14 * The handler triggers callbacks for 'done' and 'cancel'. Create a new
15 * instance with the <OpenLayers.Handler.RegularPolygon> constructor.
18 * - <OpenLayers.Handler>
20 OpenLayers.Handler.RegularPolygon = OpenLayers.Class(OpenLayers.Handler.Drag, {
24 * {Integer} Number of sides for the regular polygon. Needs to be greater
25 * than 2. Defaults to 4.
31 * {Float} Optional radius in map units of the regular polygon. If this is
32 * set to some non-zero value, a polygon with a fixed radius will be
33 * drawn and dragged with mose movements. If this property is not
34 * set, dragging changes the radius of the polygon. Set to null by
40 * APIProperty: snapAngle
41 * {Float} If set to a non-zero value, the handler will snap the polygon
42 * rotation to multiples of the snapAngle. Value is an angle measured
43 * in degrees counterclockwise from the positive x-axis.
48 * APIProperty: snapToggle
49 * {String} If set, snapToggle is checked on mouse events and will set
50 * the snap mode to the opposite of what it currently is. To disallow
51 * toggling between snap and non-snap mode, set freehandToggle to
52 * null. Acceptable toggle values are 'shiftKey', 'ctrlKey', and
53 * 'altKey'. Snap mode is only possible if this.snapAngle is set to a
56 snapToggle: 'shiftKey',
59 * APIProperty: persist
60 * {Boolean} Leave the feature rendered until clear is called. Default
61 * is false. If set to true, the feature remains rendered until
62 * clear is called, typically by deactivating the handler or starting
68 * APIProperty: irregular
69 * {Boolean} Draw an irregular polygon instead of a regular polygon.
70 * Default is false. If true, the initial mouse down will represent
71 * one corner of the polygon bounds and with each mouse movement, the
72 * polygon will be stretched so the opposite corner of its bounds
73 * follows the mouse position. This property takes precedence over
74 * the radius property. If set to true, the radius property will
81 * {Float} The angle from the origin (mouse down) to the current mouse
82 * position, in radians. This is measured counterclockwise from the
88 * Property: fixedRadius
89 * {Boolean} The polygon has a fixed radius. True if a radius is set before
90 * drawing begins. False otherwise.
96 * {<OpenLayers.Feature.Vector>} The currently drawn polygon feature
102 * {<OpenLayers.Layer.Vector>} The temporary drawing layer
108 * {<OpenLayers.Geometry.Point>} Location of the first mouse down
113 * Constructor: OpenLayers.Handler.RegularPolygon
114 * Create a new regular polygon handler.
117 * control - {<OpenLayers.Control>} The control that owns this handler
118 * callbacks - {Object} An object with a properties whose values are
119 * functions. Various callbacks described below.
120 * options - {Object} An object with properties to be set on the handler.
121 * If the options.sides property is not specified, the number of sides
125 * create - Called when a sketch is first created. Callback called with
126 * the creation point geometry and sketch feature.
127 * done - Called when the sketch drawing is finished. The callback will
128 * recieve a single argument, the sketch geometry.
129 * cancel - Called when the handler is deactivated while drawing. The
130 * cancel callback will receive a geometry.
132 initialize: function(control, callbacks, options) {
133 this.style = OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'], {});
135 OpenLayers.Handler.prototype.initialize.apply(this,
136 [control, callbacks, options]);
137 this.options = (options) ? options : new Object();
141 * APIMethod: setOptions
144 * newOptions - {Object}
146 setOptions: function (newOptions) {
147 OpenLayers.Util.extend(this.options, newOptions);
148 OpenLayers.Util.extend(this, newOptions);
152 * APIMethod: activate
153 * Turn on the handler.
156 * {Boolean} The handler was successfully activated
158 activate: function() {
159 var activated = false;
160 if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
161 // create temporary vector layer for rendering geometry sketch
163 displayInLayerSwitcher: false,
164 // indicate that the temp vector layer will never be out of range
165 // without this, resolution properties must be specified at the
166 // map-level for this temporary layer to init its resolutions
168 calculateInRange: function() { return true; }
170 this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME, options);
171 this.map.addLayer(this.layer);
178 * APIMethod: deactivate
179 * Turn off the handler.
182 * {Boolean} The handler was successfully deactivated
184 deactivate: function() {
185 var deactivated = false;
186 if(OpenLayers.Handler.Drag.prototype.deactivate.apply(this, arguments)) {
187 // call the cancel callback if mid-drawing
191 // If a layer's map property is set to null, it means that that
192 // layer isn't added to the map. Since we ourself added the layer
193 // to the map in activate(), we can assume that if this.layer.map
194 // is null it means that the layer has been destroyed (as a result
195 // of map.destroy() for example.
196 if (this.layer.map != null) {
197 this.layer.destroy(false);
199 this.feature.destroy();
211 * Start drawing a new feature
214 * evt - {Event} The drag start event
216 down: function(evt) {
217 this.fixedRadius = !!(this.radius);
218 var maploc = this.map.getLonLatFromPixel(evt.xy);
219 this.origin = new OpenLayers.Geometry.Point(maploc.lon, maploc.lat);
220 // create the new polygon
221 if(!this.fixedRadius || this.irregular) {
222 // smallest radius should not be less one pixel in map units
223 // VML doesn't behave well with smaller
224 this.radius = this.map.getResolution();
229 this.feature = new OpenLayers.Feature.Vector();
230 this.createGeometry();
231 this.callback("create", [this.origin, this.feature]);
232 this.layer.addFeatures([this.feature], {silent: true});
233 this.layer.drawFeature(this.feature, this.style);
238 * Respond to drag move events
241 * evt - {Evt} The move event
243 move: function(evt) {
244 var maploc = this.map.getLonLatFromPixel(evt.xy);
245 var point = new OpenLayers.Geometry.Point(maploc.lon, maploc.lat);
247 var ry = Math.sqrt(2) * Math.abs(point.y - this.origin.y) / 2;
248 this.radius = Math.max(this.map.getResolution() / 2, ry);
249 } else if(this.fixedRadius) {
252 this.calculateAngle(point, evt);
253 this.radius = Math.max(this.map.getResolution() / 2,
254 point.distanceTo(this.origin));
256 this.modifyGeometry();
258 var dx = point.x - this.origin.x;
259 var dy = point.y - this.origin.y;
262 ratio = dx / (this.radius * Math.sqrt(2));
266 this.feature.geometry.resize(1, this.origin, ratio);
267 this.feature.geometry.move(dx / 2, dy / 2);
269 this.layer.drawFeature(this.feature, this.style);
274 * Finish drawing the feature
277 * evt - {Event} The mouse up event
281 // the mouseup method of superclass doesn't call the
282 // "done" callback if there's been no move between
284 if (this.start == this.last) {
285 this.callback("done", [evt.xy]);
291 * Finish drawing the feature.
294 * evt - {Event} The mouse out event
301 * Method: createGeometry
302 * Create the new polygon geometry. This is called at the start of the
303 * drag and at any point during the drag if the number of sides
306 createGeometry: function() {
307 this.angle = Math.PI * ((1/this.sides) - (1/2));
309 this.angle += this.snapAngle * (Math.PI / 180);
311 this.feature.geometry = OpenLayers.Geometry.Polygon.createRegularPolygon(
312 this.origin, this.radius, this.sides, this.snapAngle
317 * Method: modifyGeometry
318 * Modify the polygon geometry in place.
320 modifyGeometry: function() {
321 var angle, dx, dy, point;
322 var ring = this.feature.geometry.components[0];
323 // if the number of sides ever changes, create a new geometry
324 if(ring.components.length != (this.sides + 1)) {
325 this.createGeometry();
326 ring = this.feature.geometry.components[0];
328 for(var i=0; i<this.sides; ++i) {
329 point = ring.components[i];
330 angle = this.angle + (i * 2 * Math.PI / this.sides);
331 point.x = this.origin.x + (this.radius * Math.cos(angle));
332 point.y = this.origin.y + (this.radius * Math.sin(angle));
338 * Method: calculateAngle
339 * Calculate the angle based on settings.
342 * point - {<OpenLayers.Geometry.Point>}
345 calculateAngle: function(point, evt) {
346 var alpha = Math.atan2(point.y - this.origin.y,
347 point.x - this.origin.x);
348 if(this.snapAngle && (this.snapToggle && !evt[this.snapToggle])) {
349 var snapAngleRad = (Math.PI / 180) * this.snapAngle;
350 this.angle = Math.round(alpha / snapAngleRad) * snapAngleRad;
358 * Finish the geometry and call the "cancel" callback.
361 // the polygon geometry gets cloned in the callback method
362 this.callback("cancel", null);
368 * Finish the geometry and call the "done" callback.
370 finalize: function() {
372 this.radius = this.options.radius;
377 * Clear any rendered features on the temporary layer. This is called
378 * when the handler is deactivated, canceled, or done (unless persist
382 this.layer.renderer.clear();
383 this.layer.destroyFeatures();
388 * Trigger the control's named callback with the given arguments
391 * name - {String} The key for the callback that is one of the properties
392 * of the handler's callbacks object.
393 * args - {Array} An array of arguments with which to call the callback
394 * (defined by the control).
396 callback: function (name, args) {
397 // override the callback method to always send the polygon geometry
398 if (this.callbacks[name]) {
399 this.callbacks[name].apply(this.control,
400 [this.feature.geometry.clone()]);
402 // since sketch features are added to the temporary layer
403 // they must be cleared here if done or cancel
404 if(!this.persist && (name == "done" || name == "cancel")) {
409 CLASS_NAME: "OpenLayers.Handler.RegularPolygon"