From 35dae2bdb0a1dcc4eb6b46e82e66ef30fd07cb79 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Mon, 10 Sep 2007 20:24:27 +0000 Subject: [PATCH] Adding a RegularPolygon handler for drawing squares, triangles, circles, etc. Demo in the regular-polygon.html example. Also adding a createRegularPolygon class method to the Polygon geometry class. Thanks to crschmidt for all the tests and help getting this in (closes #828). git-svn-id: http://svn.openlayers.org/trunk/openlayers@4205 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf --- examples/regular-polygons.html | 162 +++++++++++ lib/OpenLayers.js | 1 + lib/OpenLayers/Geometry/Polygon.js | 28 ++ lib/OpenLayers/Handler/RegularPolygon.js | 356 +++++++++++++++++++++++ tests/Geometry/test_Polygon.html | 41 +++ tests/Handler/test_RegularPolygon.html | 135 +++++++++ tests/list-tests.html | 1 + 7 files changed, 724 insertions(+) create mode 100644 examples/regular-polygons.html create mode 100644 lib/OpenLayers/Handler/RegularPolygon.js create mode 100644 tests/Handler/test_RegularPolygon.html diff --git a/examples/regular-polygons.html b/examples/regular-polygons.html new file mode 100644 index 0000000000..c7bed84832 --- /dev/null +++ b/examples/regular-polygons.html @@ -0,0 +1,162 @@ + + + OpenLayers Regular Polygon Example + + + + + +

OpenLayers Regular Polygon Example

+
+
+ + + + + + + + + + + + + + + + + + + + + +
Draw OptionValue
+ shape + + +
+ snap angle + + +
+ size + + +
+
+

+ Regular polygons can be drawn by pointing a DrawFeature control to the + RegularPolygon handler class. The options above demonstrate how the + handler can be configured. Note if you are in angle snapping mode (if + the snap angle is non-null) and you hold down the Shift key, you + will toggle to non-snapping mode. +

+ + diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js index 9bc0de858f..344f5f95fe 100644 --- a/lib/OpenLayers.js +++ b/lib/OpenLayers.js @@ -119,6 +119,7 @@ "OpenLayers/Handler/Polygon.js", "OpenLayers/Handler/Feature.js", "OpenLayers/Handler/Drag.js", + "OpenLayers/Handler/RegularPolygon.js", "OpenLayers/Handler/Box.js", "OpenLayers/Handler/MouseWheel.js", "OpenLayers/Handler/Keyboard.js", diff --git a/lib/OpenLayers/Geometry/Polygon.js b/lib/OpenLayers/Geometry/Polygon.js index cc54d6be6c..3599c8a353 100644 --- a/lib/OpenLayers/Geometry/Polygon.js +++ b/lib/OpenLayers/Geometry/Polygon.js @@ -59,3 +59,31 @@ OpenLayers.Geometry.Polygon = OpenLayers.Class( CLASS_NAME: "OpenLayers.Geometry.Polygon" }); + +/** + * APIMethod: createRegularPolygon + * Create a regular polygon around a radius. Useful for creating circles + * and the like. + * + * Parameters: + * origin - {} center of polygon. + * radius - {Float} distance to vertex, in map units. + * sides - {Integer} Number of sides. 20 approximates a circle. + * rotation - {Float} original angle of rotation, in degrees. + */ +OpenLayers.Geometry.Polygon.createRegularPolygon = function(origin, radius, sides, rotation) { + var angle = Math.PI * ((1/sides) - (1/2)); + if(rotation) { + angle += (rotation / 180) * Math.PI; + } + var rotateAngle, x, y; + var points = []; + for(var i=0; i constructor. + * + * Inherits from: + * - + */ +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', + + /** + * 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, + + /** + * 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 + * {} The currently drawn polygon feature + */ + feature: null, + + /** + * Property: layer + * {} The temporary drawing layer + */ + layer: null, + + /** + * Property: origin + * {} Location of the first mouse down + */ + origin: null, + + /** + * Constructor: OpenLayers.Handler.RegularPolygon + * Create a new regular polygon handler. + * + * Parameters: + * control - {} The control that owns this handler + * callbacks - {Array} An object with a 'done' property whos value is a + * function to be called when the polygon drawing is finished. + * The callback should expect to recieve a single argument, + * the polygon geometry. If the callbacks object contains a + * 'cancel' property, this function will be called when the + * handler is deactivated while drawing. The cancel should + * expect to receive a geometry. + * 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. + */ + initialize: function(control, callbacks, options) { + this.style = OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'], {}); + + OpenLayers.Handler.prototype.initialize.apply(this, + [control, callbacks, options]); + this.options = (options) ? options : new Object(); + }, + + /** + * 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 = {displayInLayerSwitcher: false}; + 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(); + } + this.map.removeLayer(this.layer, false); + this.layer.destroy(); + if (this.feature) { + this.feature.destroy(); + } + deactivated = true; + } + return deactivated; + }, + + /** + * Method: downFeature + * 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) { + // 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.layer.addFeatures([this.feature]); + 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.fixedRadius) { + this.origin = point; + } else { + this.calculateAngle(point, evt); + this.radius = Math.max(this.map.getResolution() / 2, + point.distanceTo(this.origin)); + } + this.modifyGeometry(); + 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(); + }, + + /** + * 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, dx, dy, 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(); + } + for(var i=0; i poly.getArea(), "area with radius 15 > poly with radius 6"); + + var sides = 4; + var poly4 = OpenLayers.Geometry.Polygon.createRegularPolygon(new OpenLayers.Geometry.Point(10,0), 15, sides); + var polyBounds = poly4.getBounds(); + t.eq(polyBounds.toBBOX(), "-0.606602,-10.606602,20.606602,10.606602", sides + " sided figure generates correct bbox."); + t.eq(Math.round(polyBounds.getCenterLonLat().lon), 10, "longitude of center of bounds is same as origin"); + t.eq(poly4.components.length, 1, "Poly has one linear ring"); + t.eq(poly4.components[0].components.length, sides + 1, "ring has correct count of components"); + t.eq(poly4.components[0].components[0].id, poly4.components[0].components[sides].id, "ring starts and ends with same geom"); + t.ok(poly4.getArea() > poly3.getArea(), "square with radius 15 > triangle with radius 15"); + } + function test_Polygon_equals(t) { t.plan(3); diff --git a/tests/Handler/test_RegularPolygon.html b/tests/Handler/test_RegularPolygon.html new file mode 100644 index 0000000000..d0717a1007 --- /dev/null +++ b/tests/Handler/test_RegularPolygon.html @@ -0,0 +1,135 @@ + + + + + + +
+ + diff --git a/tests/list-tests.html b/tests/list-tests.html index ce54945b82..a390826ef2 100644 --- a/tests/list-tests.html +++ b/tests/list-tests.html @@ -80,5 +80,6 @@
  • Handler/test_Point.html
  • Handler/test_Path.html
  • Handler/test_Polygon.html
  • +
  • Handler/test_RegularPolygon.html
  • test_Map.html