diff options
author | Chris Schlaeger <chris@linux.com> | 2014-08-12 21:56:44 +0200 |
---|---|---|
committer | Chris Schlaeger <chris@linux.com> | 2014-08-12 21:56:44 +0200 |
commit | ea346a785dc1b3f7c156f6fc33da634e1f1a627b (patch) | |
tree | af67530553d20b6e82ad60fd79593e9c4abf5565 /misc/openlayers/lib/OpenLayers/Handler | |
parent | 59741cd535c47f25971bf8c32b25da25ceadc6d5 (diff) | |
download | postrunner-ea346a785dc1b3f7c156f6fc33da634e1f1a627b.zip |
Adding jquery, flot and openlayers to be included with the GEM.v0.0.4
Diffstat (limited to 'misc/openlayers/lib/OpenLayers/Handler')
-rw-r--r-- | misc/openlayers/lib/OpenLayers/Handler/Box.js | 244 | ||||
-rw-r--r-- | misc/openlayers/lib/OpenLayers/Handler/Click.js | 505 | ||||
-rw-r--r-- | misc/openlayers/lib/OpenLayers/Handler/Drag.js | 547 | ||||
-rw-r--r-- | misc/openlayers/lib/OpenLayers/Handler/Feature.js | 434 | ||||
-rw-r--r-- | misc/openlayers/lib/OpenLayers/Handler/Hover.js | 180 | ||||
-rw-r--r-- | misc/openlayers/lib/OpenLayers/Handler/Keyboard.js | 117 | ||||
-rw-r--r-- | misc/openlayers/lib/OpenLayers/Handler/MouseWheel.js | 264 | ||||
-rw-r--r-- | misc/openlayers/lib/OpenLayers/Handler/Path.js | 543 | ||||
-rw-r--r-- | misc/openlayers/lib/OpenLayers/Handler/Pinch.js | 239 | ||||
-rw-r--r-- | misc/openlayers/lib/OpenLayers/Handler/Point.js | 556 | ||||
-rw-r--r-- | misc/openlayers/lib/OpenLayers/Handler/Polygon.js | 305 | ||||
-rw-r--r-- | misc/openlayers/lib/OpenLayers/Handler/RegularPolygon.js | 429 |
12 files changed, 4363 insertions, 0 deletions
diff --git a/misc/openlayers/lib/OpenLayers/Handler/Box.js b/misc/openlayers/lib/OpenLayers/Handler/Box.js new file mode 100644 index 0000000..9d3289a --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Handler/Box.js @@ -0,0 +1,244 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Handler.js + * @requires OpenLayers/Handler/Drag.js + */ + +/** + * Class: OpenLayers.Handler.Box + * Handler for dragging a rectangle across the map. Box is displayed + * on mouse down, moves on mouse move, and is finished on mouse up. + * + * Inherits from: + * - <OpenLayers.Handler> + */ +OpenLayers.Handler.Box = OpenLayers.Class(OpenLayers.Handler, { + + /** + * Property: dragHandler + * {<OpenLayers.Handler.Drag>} + */ + dragHandler: null, + + /** + * APIProperty: boxDivClassName + * {String} The CSS class to use for drawing the box. Default is + * olHandlerBoxZoomBox + */ + boxDivClassName: 'olHandlerBoxZoomBox', + + /** + * Property: boxOffsets + * {Object} Caches box offsets from css. This is used by the getBoxOffsets + * method. + */ + boxOffsets: null, + + /** + * Constructor: OpenLayers.Handler.Box + * + * Parameters: + * control - {<OpenLayers.Control>} + * callbacks - {Object} An object with a properties whose values are + * functions. Various callbacks described below. + * options - {Object} + * + * Named callbacks: + * start - Called when the box drag operation starts. + * done - Called when the box drag operation is finished. + * The callback should expect to receive a single argument, the box + * bounds or a pixel. If the box dragging didn't span more than a 5 + * pixel distance, a pixel will be returned instead of a bounds object. + */ + initialize: function(control, callbacks, options) { + OpenLayers.Handler.prototype.initialize.apply(this, arguments); + this.dragHandler = new OpenLayers.Handler.Drag( + this, + { + down: this.startBox, + move: this.moveBox, + out: this.removeBox, + up: this.endBox + }, + {keyMask: this.keyMask} + ); + }, + + /** + * Method: destroy + */ + destroy: function() { + OpenLayers.Handler.prototype.destroy.apply(this, arguments); + if (this.dragHandler) { + this.dragHandler.destroy(); + this.dragHandler = null; + } + }, + + /** + * Method: setMap + */ + setMap: function (map) { + OpenLayers.Handler.prototype.setMap.apply(this, arguments); + if (this.dragHandler) { + this.dragHandler.setMap(map); + } + }, + + /** + * Method: startBox + * + * Parameters: + * xy - {<OpenLayers.Pixel>} + */ + startBox: function (xy) { + this.callback("start", []); + this.zoomBox = OpenLayers.Util.createDiv('zoomBox', { + x: -9999, y: -9999 + }); + this.zoomBox.className = this.boxDivClassName; + this.zoomBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1; + + this.map.viewPortDiv.appendChild(this.zoomBox); + + OpenLayers.Element.addClass( + this.map.viewPortDiv, "olDrawBox" + ); + }, + + /** + * Method: moveBox + */ + moveBox: function (xy) { + var startX = this.dragHandler.start.x; + var startY = this.dragHandler.start.y; + var deltaX = Math.abs(startX - xy.x); + var deltaY = Math.abs(startY - xy.y); + + var offset = this.getBoxOffsets(); + this.zoomBox.style.width = (deltaX + offset.width + 1) + "px"; + this.zoomBox.style.height = (deltaY + offset.height + 1) + "px"; + this.zoomBox.style.left = (xy.x < startX ? + startX - deltaX - offset.left : startX - offset.left) + "px"; + this.zoomBox.style.top = (xy.y < startY ? + startY - deltaY - offset.top : startY - offset.top) + "px"; + }, + + /** + * Method: endBox + */ + endBox: function(end) { + var result; + if (Math.abs(this.dragHandler.start.x - end.x) > 5 || + Math.abs(this.dragHandler.start.y - end.y) > 5) { + var start = this.dragHandler.start; + var top = Math.min(start.y, end.y); + var bottom = Math.max(start.y, end.y); + var left = Math.min(start.x, end.x); + var right = Math.max(start.x, end.x); + result = new OpenLayers.Bounds(left, bottom, right, top); + } else { + result = this.dragHandler.start.clone(); // i.e. OL.Pixel + } + this.removeBox(); + + this.callback("done", [result]); + }, + + /** + * Method: removeBox + * Remove the zoombox from the screen and nullify our reference to it. + */ + removeBox: function() { + this.map.viewPortDiv.removeChild(this.zoomBox); + this.zoomBox = null; + this.boxOffsets = null; + OpenLayers.Element.removeClass( + this.map.viewPortDiv, "olDrawBox" + ); + + }, + + /** + * Method: activate + */ + activate: function () { + if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) { + this.dragHandler.activate(); + return true; + } else { + return false; + } + }, + + /** + * Method: deactivate + */ + deactivate: function () { + if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) { + if (this.dragHandler.deactivate()) { + if (this.zoomBox) { + this.removeBox(); + } + } + return true; + } else { + return false; + } + }, + + /** + * Method: getBoxOffsets + * Determines border offsets for a box, according to the box model. + * + * Returns: + * {Object} an object with the following offsets: + * - left + * - right + * - top + * - bottom + * - width + * - height + */ + getBoxOffsets: function() { + if (!this.boxOffsets) { + // Determine the box model. If the testDiv's clientWidth is 3, then + // the borders are outside and we are dealing with the w3c box + // model. Otherwise, the browser uses the traditional box model and + // the borders are inside the box bounds, leaving us with a + // clientWidth of 1. + var testDiv = document.createElement("div"); + //testDiv.style.visibility = "hidden"; + testDiv.style.position = "absolute"; + testDiv.style.border = "1px solid black"; + testDiv.style.width = "3px"; + document.body.appendChild(testDiv); + var w3cBoxModel = testDiv.clientWidth == 3; + document.body.removeChild(testDiv); + + var left = parseInt(OpenLayers.Element.getStyle(this.zoomBox, + "border-left-width")); + var right = parseInt(OpenLayers.Element.getStyle( + this.zoomBox, "border-right-width")); + var top = parseInt(OpenLayers.Element.getStyle(this.zoomBox, + "border-top-width")); + var bottom = parseInt(OpenLayers.Element.getStyle( + this.zoomBox, "border-bottom-width")); + this.boxOffsets = { + left: left, + right: right, + top: top, + bottom: bottom, + width: w3cBoxModel === false ? left + right : 0, + height: w3cBoxModel === false ? top + bottom : 0 + }; + } + return this.boxOffsets; + }, + + CLASS_NAME: "OpenLayers.Handler.Box" +}); diff --git a/misc/openlayers/lib/OpenLayers/Handler/Click.js b/misc/openlayers/lib/OpenLayers/Handler/Click.js new file mode 100644 index 0000000..94a8444 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Handler/Click.js @@ -0,0 +1,505 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Handler.js + */ + +/** + * Class: OpenLayers.Handler.Click + * A handler for mouse clicks. The intention of this handler is to give + * controls more flexibility with handling clicks. Browsers trigger + * click events twice for a double-click. In addition, the mousedown, + * mousemove, mouseup sequence fires a click event. With this handler, + * controls can decide whether to ignore clicks associated with a double + * click. By setting a <pixelTolerance>, controls can also ignore clicks + * that include a drag. Create a new instance with the + * <OpenLayers.Handler.Click> constructor. + * + * Inherits from: + * - <OpenLayers.Handler> + */ +OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, { + /** + * APIProperty: delay + * {Number} Number of milliseconds between clicks before the event is + * considered a double-click. + */ + delay: 300, + + /** + * APIProperty: single + * {Boolean} Handle single clicks. Default is true. If false, clicks + * will not be reported. If true, single-clicks will be reported. + */ + single: true, + + /** + * APIProperty: double + * {Boolean} Handle double-clicks. Default is false. + */ + 'double': false, + + /** + * APIProperty: pixelTolerance + * {Number} Maximum number of pixels between mouseup and mousedown for an + * event to be considered a click. Default is 0. If set to an + * integer value, clicks with a drag greater than the value will be + * ignored. This property can only be set when the handler is + * constructed. + */ + pixelTolerance: 0, + + /** + * APIProperty: dblclickTolerance + * {Number} Maximum distance in pixels between clicks for a sequence of + * events to be considered a double click. Default is 13. If the + * distance between two clicks is greater than this value, a double- + * click will not be fired. + */ + dblclickTolerance: 13, + + /** + * APIProperty: stopSingle + * {Boolean} Stop other listeners from being notified of clicks. Default + * is false. If true, any listeners registered before this one for + * click or rightclick events will not be notified. + */ + stopSingle: false, + + /** + * APIProperty: stopDouble + * {Boolean} Stop other listeners from being notified of double-clicks. + * Default is false. If true, any click listeners registered before + * this one will not be notified of *any* double-click events. + * + * The one caveat with stopDouble is that given a map with two click + * handlers, one with stopDouble true and the other with stopSingle + * true, the stopSingle handler should be activated last to get + * uniform cross-browser performance. Since IE triggers one click + * with a dblclick and FF triggers two, if a stopSingle handler is + * activated first, all it gets in IE is a single click when the + * second handler stops propagation on the dblclick. + */ + stopDouble: false, + + /** + * Property: timerId + * {Number} The id of the timeout waiting to clear the <delayedCall>. + */ + timerId: null, + + /** + * Property: down + * {Object} Object that store relevant information about the last + * mousedown or touchstart. Its 'xy' OpenLayers.Pixel property gives + * the average location of the mouse/touch event. Its 'touches' + * property records clientX/clientY of each touches. + */ + down: null, + + /** + * Property: last + * {Object} Object that store relevant information about the last + * mousemove or touchmove. Its 'xy' OpenLayers.Pixel property gives + * the average location of the mouse/touch event. Its 'touches' + * property records clientX/clientY of each touches. + */ + last: null, + + /** + * Property: first + * {Object} When waiting for double clicks, this object will store + * information about the first click in a two click sequence. + */ + first: null, + + /** + * Property: rightclickTimerId + * {Number} The id of the right mouse timeout waiting to clear the + * <delayedEvent>. + */ + rightclickTimerId: null, + + /** + * Constructor: OpenLayers.Handler.Click + * Create a new click handler. + * + * Parameters: + * control - {<OpenLayers.Control>} The control that is making use of + * this handler. If a handler is being used without a control, the + * handler's setMap method must be overridden to deal properly with + * the map. + * callbacks - {Object} An object with keys corresponding to callbacks + * that will be called by the handler. The callbacks should + * expect to recieve a single argument, the click event. + * Callbacks for 'click' and 'dblclick' are supported. + * options - {Object} Optional object whose properties will be set on the + * handler. + */ + + /** + * Method: touchstart + * Handle touchstart. + * + * Returns: + * {Boolean} Continue propagating this event. + */ + touchstart: function(evt) { + this.startTouch(); + this.down = this.getEventInfo(evt); + this.last = this.getEventInfo(evt); + return true; + }, + + /** + * Method: touchmove + * Store position of last move, because touchend event can have + * an empty "touches" property. + * + * Returns: + * {Boolean} Continue propagating this event. + */ + touchmove: function(evt) { + this.last = this.getEventInfo(evt); + return true; + }, + + /** + * Method: touchend + * Correctly set event xy property, and add lastTouches to have + * touches property from last touchstart or touchmove + * + * Returns: + * {Boolean} Continue propagating this event. + */ + touchend: function(evt) { + // touchstart may not have been allowed to propagate + if (this.down) { + evt.xy = this.last.xy; + evt.lastTouches = this.last.touches; + this.handleSingle(evt); + this.down = null; + } + return true; + }, + + /** + * Method: mousedown + * Handle mousedown. + * + * Returns: + * {Boolean} Continue propagating this event. + */ + mousedown: function(evt) { + this.down = this.getEventInfo(evt); + this.last = this.getEventInfo(evt); + return true; + }, + + /** + * Method: mouseup + * Handle mouseup. Installed to support collection of right mouse events. + * + * Returns: + * {Boolean} Continue propagating this event. + */ + mouseup: function (evt) { + var propagate = true; + + // Collect right mouse clicks from the mouseup + // IE - ignores the second right click in mousedown so using + // mouseup instead + if (this.checkModifiers(evt) && this.control.handleRightClicks && + OpenLayers.Event.isRightClick(evt)) { + propagate = this.rightclick(evt); + } + + return propagate; + }, + + /** + * Method: rightclick + * Handle rightclick. For a dblrightclick, we get two clicks so we need + * to always register for dblrightclick to properly handle single + * clicks. + * + * Returns: + * {Boolean} Continue propagating this event. + */ + rightclick: function(evt) { + if(this.passesTolerance(evt)) { + if(this.rightclickTimerId != null) { + //Second click received before timeout this must be + // a double click + this.clearTimer(); + this.callback('dblrightclick', [evt]); + return !this.stopDouble; + } else { + //Set the rightclickTimerId, send evt only if double is + // true else trigger single + var clickEvent = this['double'] ? + OpenLayers.Util.extend({}, evt) : + this.callback('rightclick', [evt]); + + var delayedRightCall = OpenLayers.Function.bind( + this.delayedRightCall, + this, + clickEvent + ); + this.rightclickTimerId = window.setTimeout( + delayedRightCall, this.delay + ); + } + } + return !this.stopSingle; + }, + + /** + * Method: delayedRightCall + * Sets <rightclickTimerId> to null. And optionally triggers the + * rightclick callback if evt is set. + */ + delayedRightCall: function(evt) { + this.rightclickTimerId = null; + if (evt) { + this.callback('rightclick', [evt]); + } + }, + + /** + * Method: click + * Handle click events from the browser. This is registered as a listener + * for click events and should not be called from other events in this + * handler. + * + * Returns: + * {Boolean} Continue propagating this event. + */ + click: function(evt) { + if (!this.last) { + this.last = this.getEventInfo(evt); + } + this.handleSingle(evt); + return !this.stopSingle; + }, + + /** + * Method: dblclick + * Handle dblclick. For a dblclick, we get two clicks in some browsers + * (FF) and one in others (IE). So we need to always register for + * dblclick to properly handle single clicks. This method is registered + * as a listener for the dblclick browser event. It should *not* be + * called by other methods in this handler. + * + * Returns: + * {Boolean} Continue propagating this event. + */ + dblclick: function(evt) { + this.handleDouble(evt); + return !this.stopDouble; + }, + + /** + * Method: handleDouble + * Handle double-click sequence. + */ + handleDouble: function(evt) { + if (this.passesDblclickTolerance(evt)) { + if (this["double"]) { + this.callback("dblclick", [evt]); + } + // to prevent a dblclick from firing the click callback in IE + this.clearTimer(); + } + }, + + /** + * Method: handleSingle + * Handle single click sequence. + */ + handleSingle: function(evt) { + if (this.passesTolerance(evt)) { + if (this.timerId != null) { + // already received a click + if (this.last.touches && this.last.touches.length === 1) { + // touch device, no dblclick event - this may be a double + if (this["double"]) { + // on Android don't let the browser zoom on the page + OpenLayers.Event.preventDefault(evt); + } + this.handleDouble(evt); + } + // if we're not in a touch environment we clear the click timer + // if we've got a second touch, we'll get two touchend events + if (!this.last.touches || this.last.touches.length !== 2) { + this.clearTimer(); + } + } else { + // remember the first click info so we can compare to the second + this.first = this.getEventInfo(evt); + // set the timer, send evt only if single is true + //use a clone of the event object because it will no longer + //be a valid event object in IE in the timer callback + var clickEvent = this.single ? + OpenLayers.Util.extend({}, evt) : null; + this.queuePotentialClick(clickEvent); + } + } + }, + + /** + * Method: queuePotentialClick + * This method is separated out largely to make testing easier (so we + * don't have to override window.setTimeout) + */ + queuePotentialClick: function(evt) { + this.timerId = window.setTimeout( + OpenLayers.Function.bind(this.delayedCall, this, evt), + this.delay + ); + }, + + /** + * Method: passesTolerance + * Determine whether the event is within the optional pixel tolerance. Note + * that the pixel tolerance check only works if mousedown events get to + * the listeners registered here. If they are stopped by other elements, + * the <pixelTolerance> will have no effect here (this method will always + * return true). + * + * Returns: + * {Boolean} The click is within the pixel tolerance (if specified). + */ + passesTolerance: function(evt) { + var passes = true; + if (this.pixelTolerance != null && this.down && this.down.xy) { + passes = this.pixelTolerance >= this.down.xy.distanceTo(evt.xy); + // for touch environments, we also enforce that all touches + // start and end within the given tolerance to be considered a click + if (passes && this.touch && + this.down.touches.length === this.last.touches.length) { + // the touchend event doesn't come with touches, so we check + // down and last + for (var i=0, ii=this.down.touches.length; i<ii; ++i) { + if (this.getTouchDistance( + this.down.touches[i], + this.last.touches[i] + ) > this.pixelTolerance) { + passes = false; + break; + } + } + } + } + return passes; + }, + + /** + * Method: getTouchDistance + * + * Returns: + * {Boolean} The pixel displacement between two touches. + */ + getTouchDistance: function(from, to) { + return Math.sqrt( + Math.pow(from.clientX - to.clientX, 2) + + Math.pow(from.clientY - to.clientY, 2) + ); + }, + + /** + * Method: passesDblclickTolerance + * Determine whether the event is within the optional double-cick pixel + * tolerance. + * + * Returns: + * {Boolean} The click is within the double-click pixel tolerance. + */ + passesDblclickTolerance: function(evt) { + var passes = true; + if (this.down && this.first) { + passes = this.down.xy.distanceTo(this.first.xy) <= this.dblclickTolerance; + } + return passes; + }, + + /** + * Method: clearTimer + * Clear the timer and set <timerId> to null. + */ + clearTimer: function() { + if (this.timerId != null) { + window.clearTimeout(this.timerId); + this.timerId = null; + } + if (this.rightclickTimerId != null) { + window.clearTimeout(this.rightclickTimerId); + this.rightclickTimerId = null; + } + }, + + /** + * Method: delayedCall + * Sets <timerId> to null. And optionally triggers the click callback if + * evt is set. + */ + delayedCall: function(evt) { + this.timerId = null; + if (evt) { + this.callback("click", [evt]); + } + }, + + /** + * Method: getEventInfo + * This method allows us to store event information without storing the + * actual event. In touch devices (at least), the same event is + * modified between touchstart, touchmove, and touchend. + * + * Returns: + * {Object} An object with event related info. + */ + getEventInfo: function(evt) { + var touches; + if (evt.touches) { + var len = evt.touches.length; + touches = new Array(len); + var touch; + for (var i=0; i<len; i++) { + touch = evt.touches[i]; + touches[i] = { + clientX: touch.olClientX, + clientY: touch.olClientY + }; + } + } + return { + xy: evt.xy, + touches: touches + }; + }, + + /** + * APIMethod: deactivate + * Deactivate the handler. + * + * Returns: + * {Boolean} The handler was successfully deactivated. + */ + deactivate: function() { + var deactivated = false; + if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) { + this.clearTimer(); + this.down = null; + this.first = null; + this.last = null; + deactivated = true; + } + return deactivated; + }, + + CLASS_NAME: "OpenLayers.Handler.Click" +}); diff --git a/misc/openlayers/lib/OpenLayers/Handler/Drag.js b/misc/openlayers/lib/OpenLayers/Handler/Drag.js new file mode 100644 index 0000000..8c3cb55 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Handler/Drag.js @@ -0,0 +1,547 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Handler.js + */ + +/** + * Class: OpenLayers.Handler.Drag + * The drag handler is used to deal with sequences of browser events related + * to dragging. The handler is used by controls that want to know when + * a drag sequence begins, when a drag is happening, and when it has + * finished. + * + * Controls that use the drag handler typically construct it with callbacks + * for 'down', 'move', and 'done'. Callbacks for these keys are called + * when the drag begins, with each move, and when the drag is done. In + * addition, controls can have callbacks keyed to 'up' and 'out' if they + * care to differentiate between the types of events that correspond with + * the end of a drag sequence. If no drag actually occurs (no mouse move) + * the 'down' and 'up' callbacks will be called, but not the 'done' + * callback. + * + * Create a new drag handler with the <OpenLayers.Handler.Drag> constructor. + * + * Inherits from: + * - <OpenLayers.Handler> + */ +OpenLayers.Handler.Drag = OpenLayers.Class(OpenLayers.Handler, { + + /** + * Property: started + * {Boolean} When a mousedown or touchstart event is received, we want to + * record it, but not set 'dragging' until the mouse moves after starting. + */ + started: false, + + /** + * Property: stopDown + * {Boolean} Stop propagation of mousedown events from getting to listeners + * on the same element. Default is true. + */ + stopDown: true, + + /** + * Property: dragging + * {Boolean} + */ + dragging: false, + + /** + * Property: last + * {<OpenLayers.Pixel>} The last pixel location of the drag. + */ + last: null, + + /** + * Property: start + * {<OpenLayers.Pixel>} The first pixel location of the drag. + */ + start: null, + + /** + * Property: lastMoveEvt + * {Object} The last mousemove event that occurred. Used to + * position the map correctly when our "delay drag" + * timeout expired. + */ + lastMoveEvt: null, + + /** + * Property: oldOnselectstart + * {Function} + */ + oldOnselectstart: null, + + /** + * Property: interval + * {Integer} In order to increase performance, an interval (in + * milliseconds) can be set to reduce the number of drag events + * called. If set, a new drag event will not be set until the + * interval has passed. + * Defaults to 0, meaning no interval. + */ + interval: 0, + + /** + * Property: timeoutId + * {String} The id of the timeout used for the mousedown interval. + * This is "private", and should be left alone. + */ + timeoutId: null, + + /** + * APIProperty: documentDrag + * {Boolean} If set to true, the handler will also handle mouse moves when + * the cursor has moved out of the map viewport. Default is false. + */ + documentDrag: false, + + /** + * Property: documentEvents + * {Boolean} Are we currently observing document events? + */ + documentEvents: null, + + /** + * Constructor: OpenLayers.Handler.Drag + * Returns OpenLayers.Handler.Drag + * + * Parameters: + * control - {<OpenLayers.Control>} The control that is making use of + * this handler. If a handler is being used without a control, the + * handlers setMap method must be overridden to deal properly with + * the map. + * callbacks - {Object} An object containing a single function to be + * called when the drag operation is finished. The callback should + * expect to recieve a single argument, the pixel location of the event. + * Callbacks for 'move' and 'done' are supported. You can also speficy + * callbacks for 'down', 'up', and 'out' to respond to those events. + * options - {Object} + */ + initialize: function(control, callbacks, options) { + OpenLayers.Handler.prototype.initialize.apply(this, arguments); + + if (this.documentDrag === true) { + var me = this; + this._docMove = function(evt) { + me.mousemove({ + xy: {x: evt.clientX, y: evt.clientY}, + element: document + }); + }; + this._docUp = function(evt) { + me.mouseup({xy: {x: evt.clientX, y: evt.clientY}}); + }; + } + }, + + + /** + * Method: dragstart + * This private method is factorized from mousedown and touchstart methods + * + * Parameters: + * evt - {Event} The event + * + * Returns: + * {Boolean} Let the event propagate. + */ + dragstart: function (evt) { + var propagate = true; + this.dragging = false; + if (this.checkModifiers(evt) && + (OpenLayers.Event.isLeftClick(evt) || + OpenLayers.Event.isSingleTouch(evt))) { + this.started = true; + this.start = evt.xy; + this.last = evt.xy; + OpenLayers.Element.addClass( + this.map.viewPortDiv, "olDragDown" + ); + this.down(evt); + this.callback("down", [evt.xy]); + + // prevent document dragging + OpenLayers.Event.preventDefault(evt); + + if(!this.oldOnselectstart) { + this.oldOnselectstart = document.onselectstart ? + document.onselectstart : OpenLayers.Function.True; + } + document.onselectstart = OpenLayers.Function.False; + + propagate = !this.stopDown; + } else { + this.started = false; + this.start = null; + this.last = null; + } + return propagate; + }, + + /** + * Method: dragmove + * This private method is factorized from mousemove and touchmove methods + * + * Parameters: + * evt - {Event} The event + * + * Returns: + * {Boolean} Let the event propagate. + */ + dragmove: function (evt) { + this.lastMoveEvt = evt; + if (this.started && !this.timeoutId && (evt.xy.x != this.last.x || + evt.xy.y != this.last.y)) { + if(this.documentDrag === true && this.documentEvents) { + if(evt.element === document) { + this.adjustXY(evt); + // do setEvent manually because the documentEvents are not + // registered with the map + this.setEvent(evt); + } else { + this.removeDocumentEvents(); + } + } + if (this.interval > 0) { + this.timeoutId = setTimeout( + OpenLayers.Function.bind(this.removeTimeout, this), + this.interval); + } + this.dragging = true; + + this.move(evt); + this.callback("move", [evt.xy]); + if(!this.oldOnselectstart) { + this.oldOnselectstart = document.onselectstart; + document.onselectstart = OpenLayers.Function.False; + } + this.last = evt.xy; + } + return true; + }, + + /** + * Method: dragend + * This private method is factorized from mouseup and touchend methods + * + * Parameters: + * evt - {Event} The event + * + * Returns: + * {Boolean} Let the event propagate. + */ + dragend: function (evt) { + if (this.started) { + if(this.documentDrag === true && this.documentEvents) { + this.adjustXY(evt); + this.removeDocumentEvents(); + } + var dragged = (this.start != this.last); + this.started = false; + this.dragging = false; + OpenLayers.Element.removeClass( + this.map.viewPortDiv, "olDragDown" + ); + this.up(evt); + this.callback("up", [evt.xy]); + if(dragged) { + this.callback("done", [evt.xy]); + } + document.onselectstart = this.oldOnselectstart; + } + return true; + }, + + /** + * The four methods below (down, move, up, and out) are used by subclasses + * to do their own processing related to these mouse events. + */ + + /** + * Method: down + * This method is called during the handling of the mouse down event. + * Subclasses can do their own processing here. + * + * Parameters: + * evt - {Event} The mouse down event + */ + down: function(evt) { + }, + + /** + * Method: move + * This method is called during the handling of the mouse move event. + * Subclasses can do their own processing here. + * + * Parameters: + * evt - {Event} The mouse move event + * + */ + move: function(evt) { + }, + + /** + * Method: up + * This method is called during the handling of the mouse up event. + * Subclasses can do their own processing here. + * + * Parameters: + * evt - {Event} The mouse up event + */ + up: function(evt) { + }, + + /** + * Method: out + * This method is called during the handling of the mouse out event. + * Subclasses can do their own processing here. + * + * Parameters: + * evt - {Event} The mouse out event + */ + out: function(evt) { + }, + + /** + * The methods below are part of the magic of event handling. Because + * they are named like browser events, they are registered as listeners + * for the events they represent. + */ + + /** + * Method: mousedown + * Handle mousedown events + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} Let the event propagate. + */ + mousedown: function(evt) { + return this.dragstart(evt); + }, + + /** + * Method: touchstart + * Handle touchstart events + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} Let the event propagate. + */ + touchstart: function(evt) { + this.startTouch(); + return this.dragstart(evt); + }, + + /** + * Method: mousemove + * Handle mousemove events + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} Let the event propagate. + */ + mousemove: function(evt) { + return this.dragmove(evt); + }, + + /** + * Method: touchmove + * Handle touchmove events + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} Let the event propagate. + */ + touchmove: function(evt) { + return this.dragmove(evt); + }, + + /** + * Method: removeTimeout + * Private. Called by mousemove() to remove the drag timeout. + */ + removeTimeout: function() { + this.timeoutId = null; + // if timeout expires while we're still dragging (mouseup + // hasn't occurred) then call mousemove to move to the + // correct position + if(this.dragging) { + this.mousemove(this.lastMoveEvt); + } + }, + + /** + * Method: mouseup + * Handle mouseup events + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} Let the event propagate. + */ + mouseup: function(evt) { + return this.dragend(evt); + }, + + /** + * Method: touchend + * Handle touchend events + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} Let the event propagate. + */ + touchend: function(evt) { + // override evt.xy with last position since touchend does not have + // any touch position + evt.xy = this.last; + return this.dragend(evt); + }, + + /** + * Method: mouseout + * Handle mouseout events + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} Let the event propagate. + */ + mouseout: function (evt) { + if (this.started && OpenLayers.Util.mouseLeft(evt, this.map.viewPortDiv)) { + if(this.documentDrag === true) { + this.addDocumentEvents(); + } else { + var dragged = (this.start != this.last); + this.started = false; + this.dragging = false; + OpenLayers.Element.removeClass( + this.map.viewPortDiv, "olDragDown" + ); + this.out(evt); + this.callback("out", []); + if(dragged) { + this.callback("done", [evt.xy]); + } + if(document.onselectstart) { + document.onselectstart = this.oldOnselectstart; + } + } + } + return true; + }, + + /** + * Method: click + * The drag handler captures the click event. If something else registers + * for clicks on the same element, its listener will not be called + * after a drag. + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} Let the event propagate. + */ + click: function (evt) { + // let the click event propagate only if the mouse moved + return (this.start == this.last); + }, + + /** + * Method: activate + * Activate the handler. + * + * Returns: + * {Boolean} The handler was successfully activated. + */ + activate: function() { + var activated = false; + if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) { + this.dragging = false; + activated = true; + } + return activated; + }, + + /** + * Method: deactivate + * Deactivate the handler. + * + * Returns: + * {Boolean} The handler was successfully deactivated. + */ + deactivate: function() { + var deactivated = false; + if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) { + this.started = false; + this.dragging = false; + this.start = null; + this.last = null; + deactivated = true; + OpenLayers.Element.removeClass( + this.map.viewPortDiv, "olDragDown" + ); + } + return deactivated; + }, + + /** + * Method: adjustXY + * Converts event coordinates that are relative to the document body to + * ones that are relative to the map viewport. The latter is the default in + * OpenLayers. + * + * Parameters: + * evt - {Object} + */ + adjustXY: function(evt) { + var pos = OpenLayers.Util.pagePosition(this.map.viewPortDiv); + evt.xy.x -= pos[0]; + evt.xy.y -= pos[1]; + }, + + /** + * Method: addDocumentEvents + * Start observing document events when documentDrag is true and the mouse + * cursor leaves the map viewport while dragging. + */ + addDocumentEvents: function() { + OpenLayers.Element.addClass(document.body, "olDragDown"); + this.documentEvents = true; + OpenLayers.Event.observe(document, "mousemove", this._docMove); + OpenLayers.Event.observe(document, "mouseup", this._docUp); + }, + + /** + * Method: removeDocumentEvents + * Stops observing document events when documentDrag is true and the mouse + * cursor re-enters the map viewport while dragging. + */ + removeDocumentEvents: function() { + OpenLayers.Element.removeClass(document.body, "olDragDown"); + this.documentEvents = false; + OpenLayers.Event.stopObserving(document, "mousemove", this._docMove); + OpenLayers.Event.stopObserving(document, "mouseup", this._docUp); + }, + + CLASS_NAME: "OpenLayers.Handler.Drag" +}); diff --git a/misc/openlayers/lib/OpenLayers/Handler/Feature.js b/misc/openlayers/lib/OpenLayers/Handler/Feature.js new file mode 100644 index 0000000..39b86f2 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Handler/Feature.js @@ -0,0 +1,434 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Handler.js + */ + +/** + * Class: OpenLayers.Handler.Feature + * Handler to respond to mouse events related to a drawn feature. Callbacks + * with the following keys will be notified of the following events + * associated with features: click, clickout, over, out, and dblclick. + * + * This handler stops event propagation for mousedown and mouseup if those + * browser events target features that can be selected. + * + * Inherits from: + * - <OpenLayers.Handler> + */ +OpenLayers.Handler.Feature = OpenLayers.Class(OpenLayers.Handler, { + + /** + * Property: EVENTMAP + * {Object} A object mapping the browser events to objects with callback + * keys for in and out. + */ + EVENTMAP: { + 'click': {'in': 'click', 'out': 'clickout'}, + 'mousemove': {'in': 'over', 'out': 'out'}, + 'dblclick': {'in': 'dblclick', 'out': null}, + 'mousedown': {'in': null, 'out': null}, + 'mouseup': {'in': null, 'out': null}, + 'touchstart': {'in': 'click', 'out': 'clickout'} + }, + + /** + * Property: feature + * {<OpenLayers.Feature.Vector>} The last feature that was hovered. + */ + feature: null, + + /** + * Property: lastFeature + * {<OpenLayers.Feature.Vector>} The last feature that was handled. + */ + lastFeature: null, + + /** + * Property: down + * {<OpenLayers.Pixel>} The location of the last mousedown. + */ + down: null, + + /** + * Property: up + * {<OpenLayers.Pixel>} The location of the last mouseup. + */ + up: null, + + /** + * Property: clickTolerance + * {Number} The number of pixels the mouse can move between mousedown + * and mouseup for the event to still be considered a click. + * Dragging the map should not trigger the click and clickout callbacks + * unless the map is moved by less than this tolerance. Defaults to 4. + */ + clickTolerance: 4, + + /** + * Property: geometryTypes + * To restrict dragging to a limited set of geometry types, send a list + * of strings corresponding to the geometry class names. + * + * @type Array(String) + */ + geometryTypes: null, + + /** + * Property: stopClick + * {Boolean} If stopClick is set to true, handled clicks do not + * propagate to other click listeners. Otherwise, handled clicks + * do propagate. Unhandled clicks always propagate, whatever the + * value of stopClick. Defaults to true. + */ + stopClick: true, + + /** + * Property: stopDown + * {Boolean} If stopDown is set to true, handled mousedowns do not + * propagate to other mousedown listeners. Otherwise, handled + * mousedowns do propagate. Unhandled mousedowns always propagate, + * whatever the value of stopDown. Defaults to true. + */ + stopDown: true, + + /** + * Property: stopUp + * {Boolean} If stopUp is set to true, handled mouseups do not + * propagate to other mouseup listeners. Otherwise, handled mouseups + * do propagate. Unhandled mouseups always propagate, whatever the + * value of stopUp. Defaults to false. + */ + stopUp: false, + + /** + * Constructor: OpenLayers.Handler.Feature + * + * Parameters: + * control - {<OpenLayers.Control>} + * layer - {<OpenLayers.Layer.Vector>} + * callbacks - {Object} An object with a 'over' property whos value is + * a function to be called when the mouse is over a feature. The + * callback should expect to recieve a single argument, the feature. + * options - {Object} + */ + initialize: function(control, layer, callbacks, options) { + OpenLayers.Handler.prototype.initialize.apply(this, [control, callbacks, options]); + this.layer = layer; + }, + + /** + * Method: touchstart + * Handle touchstart events + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} Let the event propagate. + */ + touchstart: function(evt) { + this.startTouch(); + return OpenLayers.Event.isMultiTouch(evt) ? + true : this.mousedown(evt); + }, + + /** + * Method: touchmove + * Handle touchmove events. We just prevent the browser default behavior, + * for Android Webkit not to select text when moving the finger after + * selecting a feature. + * + * Parameters: + * evt - {Event} + */ + touchmove: function(evt) { + OpenLayers.Event.preventDefault(evt); + }, + + /** + * Method: mousedown + * Handle mouse down. Stop propagation if a feature is targeted by this + * event (stops map dragging during feature selection). + * + * Parameters: + * evt - {Event} + */ + mousedown: function(evt) { + // Feature selection is only done with a left click. Other handlers may stop the + // propagation of left-click mousedown events but not right-click mousedown events. + // This mismatch causes problems when comparing the location of the down and up + // events in the click function so it is important ignore right-clicks. + if (OpenLayers.Event.isLeftClick(evt) || OpenLayers.Event.isSingleTouch(evt)) { + this.down = evt.xy; + } + return this.handle(evt) ? !this.stopDown : true; + }, + + /** + * Method: mouseup + * Handle mouse up. Stop propagation if a feature is targeted by this + * event. + * + * Parameters: + * evt - {Event} + */ + mouseup: function(evt) { + this.up = evt.xy; + return this.handle(evt) ? !this.stopUp : true; + }, + + /** + * Method: click + * Handle click. Call the "click" callback if click on a feature, + * or the "clickout" callback if click outside any feature. + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} + */ + click: function(evt) { + return this.handle(evt) ? !this.stopClick : true; + }, + + /** + * Method: mousemove + * Handle mouse moves. Call the "over" callback if moving in to a feature, + * or the "out" callback if moving out of a feature. + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} + */ + mousemove: function(evt) { + if (!this.callbacks['over'] && !this.callbacks['out']) { + return true; + } + this.handle(evt); + return true; + }, + + /** + * Method: dblclick + * Handle dblclick. Call the "dblclick" callback if dblclick on a feature. + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} + */ + dblclick: function(evt) { + return !this.handle(evt); + }, + + /** + * Method: geometryTypeMatches + * Return true if the geometry type of the passed feature matches + * one of the geometry types in the geometryTypes array. + * + * Parameters: + * feature - {<OpenLayers.Vector.Feature>} + * + * Returns: + * {Boolean} + */ + geometryTypeMatches: function(feature) { + return this.geometryTypes == null || + OpenLayers.Util.indexOf(this.geometryTypes, + feature.geometry.CLASS_NAME) > -1; + }, + + /** + * Method: handle + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} The event occurred over a relevant feature. + */ + handle: function(evt) { + if(this.feature && !this.feature.layer) { + // feature has been destroyed + this.feature = null; + } + var type = evt.type; + var handled = false; + var previouslyIn = !!(this.feature); // previously in a feature + var click = (type == "click" || type == "dblclick" || type == "touchstart"); + this.feature = this.layer.getFeatureFromEvent(evt); + if(this.feature && !this.feature.layer) { + // feature has been destroyed + this.feature = null; + } + if(this.lastFeature && !this.lastFeature.layer) { + // last feature has been destroyed + this.lastFeature = null; + } + if(this.feature) { + if(type === "touchstart") { + // stop the event to prevent Android Webkit from + // "flashing" the map div + OpenLayers.Event.preventDefault(evt); + } + var inNew = (this.feature != this.lastFeature); + if(this.geometryTypeMatches(this.feature)) { + // in to a feature + if(previouslyIn && inNew) { + // out of last feature and in to another + if(this.lastFeature) { + this.triggerCallback(type, 'out', [this.lastFeature]); + } + this.triggerCallback(type, 'in', [this.feature]); + } else if(!previouslyIn || click) { + // in feature for the first time + this.triggerCallback(type, 'in', [this.feature]); + } + this.lastFeature = this.feature; + handled = true; + } else { + // not in to a feature + if(this.lastFeature && (previouslyIn && inNew || click)) { + // out of last feature for the first time + this.triggerCallback(type, 'out', [this.lastFeature]); + } + // next time the mouse goes in a feature whose geometry type + // doesn't match we don't want to call the 'out' callback + // again, so let's set this.feature to null so that + // previouslyIn will evaluate to false the next time + // we enter handle. Yes, a bit hackish... + this.feature = null; + } + } else if(this.lastFeature && (previouslyIn || click)) { + this.triggerCallback(type, 'out', [this.lastFeature]); + } + return handled; + }, + + /** + * Method: triggerCallback + * Call the callback keyed in the event map with the supplied arguments. + * For click and clickout, the <clickTolerance> is checked first. + * + * Parameters: + * type - {String} + */ + triggerCallback: function(type, mode, args) { + var key = this.EVENTMAP[type][mode]; + if(key) { + if(type == 'click' && this.up && this.down) { + // for click/clickout, only trigger callback if tolerance is met + var dpx = Math.sqrt( + Math.pow(this.up.x - this.down.x, 2) + + Math.pow(this.up.y - this.down.y, 2) + ); + if(dpx <= this.clickTolerance) { + this.callback(key, args); + } + // we're done with this set of events now: clear the cached + // positions so we can't trip over them later (this can occur + // if one of the up/down events gets eaten before it gets to us + // but we still get the click) + this.up = this.down = null; + } else { + this.callback(key, args); + } + } + }, + + /** + * Method: activate + * Turn on the handler. Returns false if the handler was already active. + * + * Returns: + * {Boolean} + */ + activate: function() { + var activated = false; + if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) { + this.moveLayerToTop(); + this.map.events.on({ + "removelayer": this.handleMapEvents, + "changelayer": this.handleMapEvents, + scope: this + }); + activated = true; + } + return activated; + }, + + /** + * Method: deactivate + * Turn off the handler. Returns false if the handler was already active. + * + * Returns: + * {Boolean} + */ + deactivate: function() { + var deactivated = false; + if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) { + this.moveLayerBack(); + this.feature = null; + this.lastFeature = null; + this.down = null; + this.up = null; + this.map.events.un({ + "removelayer": this.handleMapEvents, + "changelayer": this.handleMapEvents, + scope: this + }); + deactivated = true; + } + return deactivated; + }, + + /** + * Method: handleMapEvents + * + * Parameters: + * evt - {Object} + */ + handleMapEvents: function(evt) { + if (evt.type == "removelayer" || evt.property == "order") { + this.moveLayerToTop(); + } + }, + + /** + * Method: moveLayerToTop + * Moves the layer for this handler to the top, so mouse events can reach + * it. + */ + moveLayerToTop: function() { + var index = Math.max(this.map.Z_INDEX_BASE['Feature'] - 1, + this.layer.getZIndex()) + 1; + this.layer.setZIndex(index); + + }, + + /** + * Method: moveLayerBack + * Moves the layer back to the position determined by the map's layers + * array. + */ + moveLayerBack: function() { + var index = this.layer.getZIndex() - 1; + if (index >= this.map.Z_INDEX_BASE['Feature']) { + this.layer.setZIndex(index); + } else { + this.map.setLayerZIndex(this.layer, + this.map.getLayerIndex(this.layer)); + } + }, + + CLASS_NAME: "OpenLayers.Handler.Feature" +}); diff --git a/misc/openlayers/lib/OpenLayers/Handler/Hover.js b/misc/openlayers/lib/OpenLayers/Handler/Hover.js new file mode 100644 index 0000000..18b81f4 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Handler/Hover.js @@ -0,0 +1,180 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Handler.js + */ + +/** + * Class: OpenLayers.Handler.Hover + * The hover handler is to be used to emulate mouseovers on objects + * on the map that aren't DOM elements. For example one can use + * this handler to send WMS/GetFeatureInfo requests as the user + * moves the mouve over the map. + * + * Inherits from: + * - <OpenLayers.Handler> + */ +OpenLayers.Handler.Hover = OpenLayers.Class(OpenLayers.Handler, { + + /** + * APIProperty: delay + * {Integer} - Number of milliseconds between mousemoves before + * the event is considered a hover. Default is 500. + */ + delay: 500, + + /** + * APIProperty: pixelTolerance + * {Integer} - Maximum number of pixels between mousemoves for + * an event to be considered a hover. Default is null. + */ + pixelTolerance: null, + + /** + * APIProperty: stopMove + * {Boolean} - Stop other listeners from being notified on mousemoves. + * Default is false. + */ + stopMove: false, + + /** + * Property: px + * {<OpenLayers.Pixel>} - The location of the last mousemove, expressed + * in pixels. + */ + px: null, + + /** + * Property: timerId + * {Number} - The id of the timer. + */ + timerId: null, + + /** + * Constructor: OpenLayers.Handler.Hover + * Construct a hover handler. + * + * Parameters: + * control - {<OpenLayers.Control>} The control that initialized this + * handler. The control is assumed to have a valid map property; that + * map is used in the handler's own setMap method. + * callbacks - {Object} An object with keys corresponding to callbacks + * that will be called by the handler. The callbacks should + * expect to receive a single argument, the event. Callbacks for + * 'move', the mouse is moving, and 'pause', the mouse is pausing, + * are supported. + * options - {Object} An optional object whose properties will be set on + * the handler. + */ + + /** + * Method: mousemove + * Called when the mouse moves on the map. + * + * Parameters: + * evt - {<OpenLayers.Event>} + * + * Returns: + * {Boolean} Continue propagating this event. + */ + mousemove: function(evt) { + if(this.passesTolerance(evt.xy)) { + this.clearTimer(); + this.callback('move', [evt]); + this.px = evt.xy; + // clone the evt so original properties can be accessed even + // if the browser deletes them during the delay + evt = OpenLayers.Util.extend({}, evt); + this.timerId = window.setTimeout( + OpenLayers.Function.bind(this.delayedCall, this, evt), + this.delay + ); + } + return !this.stopMove; + }, + + /** + * Method: mouseout + * Called when the mouse goes out of the map. + * + * Parameters: + * evt - {<OpenLayers.Event>} + * + * Returns: + * {Boolean} Continue propagating this event. + */ + mouseout: function(evt) { + if (OpenLayers.Util.mouseLeft(evt, this.map.viewPortDiv)) { + this.clearTimer(); + this.callback('move', [evt]); + } + return true; + }, + + /** + * Method: passesTolerance + * Determine whether the mouse move is within the optional pixel tolerance. + * + * Parameters: + * px - {<OpenLayers.Pixel>} + * + * Returns: + * {Boolean} The mouse move is within the pixel tolerance. + */ + passesTolerance: function(px) { + var passes = true; + if(this.pixelTolerance && this.px) { + var dpx = Math.sqrt( + Math.pow(this.px.x - px.x, 2) + + Math.pow(this.px.y - px.y, 2) + ); + if(dpx < this.pixelTolerance) { + passes = false; + } + } + return passes; + }, + + /** + * Method: clearTimer + * Clear the timer and set <timerId> to null. + */ + clearTimer: function() { + if(this.timerId != null) { + window.clearTimeout(this.timerId); + this.timerId = null; + } + }, + + /** + * Method: delayedCall + * Triggers pause callback. + * + * Parameters: + * evt - {<OpenLayers.Event>} + */ + delayedCall: function(evt) { + this.callback('pause', [evt]); + }, + + /** + * APIMethod: deactivate + * Deactivate the handler. + * + * Returns: + * {Boolean} The handler was successfully deactivated. + */ + deactivate: function() { + var deactivated = false; + if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) { + this.clearTimer(); + deactivated = true; + } + return deactivated; + }, + + CLASS_NAME: "OpenLayers.Handler.Hover" +}); diff --git a/misc/openlayers/lib/OpenLayers/Handler/Keyboard.js b/misc/openlayers/lib/OpenLayers/Handler/Keyboard.js new file mode 100644 index 0000000..de7a464 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Handler/Keyboard.js @@ -0,0 +1,117 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Handler.js + * @requires OpenLayers/Events.js + */ + +/** + * Class: OpenLayers.handler.Keyboard + * A handler for keyboard events. Create a new instance with the + * <OpenLayers.Handler.Keyboard> constructor. + * + * Inherits from: + * - <OpenLayers.Handler> + */ +OpenLayers.Handler.Keyboard = OpenLayers.Class(OpenLayers.Handler, { + + /* http://www.quirksmode.org/js/keys.html explains key x-browser + key handling quirks in pretty nice detail */ + + /** + * Constant: KEY_EVENTS + * keydown, keypress, keyup + */ + KEY_EVENTS: ["keydown", "keyup"], + + /** + * Property: eventListener + * {Function} + */ + eventListener: null, + + /** + * Property: observeElement + * {DOMElement|String} The DOM element on which we listen for + * key events. Default to the document. + */ + observeElement: null, + + /** + * Constructor: OpenLayers.Handler.Keyboard + * Returns a new keyboard handler. + * + * Parameters: + * control - {<OpenLayers.Control>} The control that is making use of + * this handler. If a handler is being used without a control, the + * handlers setMap method must be overridden to deal properly with + * the map. + * callbacks - {Object} An object containing a single function to be + * called when the drag operation is finished. The callback should + * expect to recieve a single argument, the pixel location of the event. + * Callbacks for 'keydown', 'keypress', and 'keyup' are supported. + * options - {Object} Optional object whose properties will be set on the + * handler. + */ + initialize: function(control, callbacks, options) { + OpenLayers.Handler.prototype.initialize.apply(this, arguments); + // cache the bound event listener method so it can be unobserved later + this.eventListener = OpenLayers.Function.bindAsEventListener( + this.handleKeyEvent, this + ); + }, + + /** + * Method: destroy + */ + destroy: function() { + this.deactivate(); + this.eventListener = null; + OpenLayers.Handler.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: activate + */ + activate: function() { + if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) { + this.observeElement = this.observeElement || document; + for (var i=0, len=this.KEY_EVENTS.length; i<len; i++) { + OpenLayers.Event.observe( + this.observeElement, this.KEY_EVENTS[i], this.eventListener); + } + return true; + } else { + return false; + } + }, + + /** + * Method: deactivate + */ + deactivate: function() { + var deactivated = false; + if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) { + for (var i=0, len=this.KEY_EVENTS.length; i<len; i++) { + OpenLayers.Event.stopObserving( + this.observeElement, this.KEY_EVENTS[i], this.eventListener); + } + deactivated = true; + } + return deactivated; + }, + + /** + * Method: handleKeyEvent + */ + handleKeyEvent: function (evt) { + if (this.checkModifiers(evt)) { + this.callback(evt.type, [evt]); + } + }, + + CLASS_NAME: "OpenLayers.Handler.Keyboard" +}); diff --git a/misc/openlayers/lib/OpenLayers/Handler/MouseWheel.js b/misc/openlayers/lib/OpenLayers/Handler/MouseWheel.js new file mode 100644 index 0000000..c69dff3 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Handler/MouseWheel.js @@ -0,0 +1,264 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Handler.js + */ + +/** + * Class: OpenLayers.Handler.MouseWheel + * Handler for wheel up/down events. + * + * Inherits from: + * - <OpenLayers.Handler> + */ +OpenLayers.Handler.MouseWheel = OpenLayers.Class(OpenLayers.Handler, { + /** + * Property: wheelListener + * {function} + */ + wheelListener: null, + + /** + * Property: interval + * {Integer} In order to increase server performance, an interval (in + * milliseconds) can be set to reduce the number of up/down events + * called. If set, a new up/down event will not be set until the + * interval has passed. + * Defaults to 0, meaning no interval. + */ + interval: 0, + + /** + * Property: maxDelta + * {Integer} Maximum delta to collect before breaking from the current + * interval. In cumulative mode, this also limits the maximum delta + * returned from the handler. Default is Number.POSITIVE_INFINITY. + */ + maxDelta: Number.POSITIVE_INFINITY, + + /** + * Property: delta + * {Integer} When interval is set, delta collects the mousewheel z-deltas + * of the events that occur within the interval. + * See also the cumulative option + */ + delta: 0, + + /** + * Property: cumulative + * {Boolean} When interval is set: true to collect all the mousewheel + * z-deltas, false to only record the delta direction (positive or + * negative) + */ + cumulative: true, + + /** + * Constructor: OpenLayers.Handler.MouseWheel + * + * Parameters: + * control - {<OpenLayers.Control>} + * callbacks - {Object} An object containing a single function to be + * called when the drag operation is finished. + * The callback should expect to recieve a single + * argument, the point geometry. + * options - {Object} + */ + initialize: function(control, callbacks, options) { + OpenLayers.Handler.prototype.initialize.apply(this, arguments); + this.wheelListener = OpenLayers.Function.bindAsEventListener( + this.onWheelEvent, this + ); + }, + + /** + * Method: destroy + */ + destroy: function() { + OpenLayers.Handler.prototype.destroy.apply(this, arguments); + this.wheelListener = null; + }, + + /** + * Mouse ScrollWheel code thanks to http://adomas.org/javascript-mouse-wheel/ + */ + + /** + * Method: onWheelEvent + * Catch the wheel event and handle it xbrowserly + * + * Parameters: + * e - {Event} + */ + onWheelEvent: function(e){ + + // make sure we have a map and check keyboard modifiers + if (!this.map || !this.checkModifiers(e)) { + return; + } + + // Ride up the element's DOM hierarchy to determine if it or any of + // its ancestors was: + // * specifically marked as scrollable (CSS overflow property) + // * one of our layer divs or a div marked as scrollable + // ('olScrollable' CSS class) + // * the map div + // + var overScrollableDiv = false; + var allowScroll = false; + var overMapDiv = false; + + var elem = OpenLayers.Event.element(e); + while((elem != null) && !overMapDiv && !overScrollableDiv) { + + if (!overScrollableDiv) { + try { + var overflow; + if (elem.currentStyle) { + overflow = elem.currentStyle["overflow"]; + } else { + var style = + document.defaultView.getComputedStyle(elem, null); + overflow = style.getPropertyValue("overflow"); + } + overScrollableDiv = ( overflow && + (overflow == "auto") || (overflow == "scroll") ); + } catch(err) { + //sometimes when scrolling in a popup, this causes + // obscure browser error + } + } + + if (!allowScroll) { + allowScroll = OpenLayers.Element.hasClass(elem, 'olScrollable'); + if (!allowScroll) { + for (var i = 0, len = this.map.layers.length; i < len; i++) { + // Are we in the layer div? Note that we have two cases + // here: one is to catch EventPane layers, which have a + // pane above the layer (layer.pane) + var layer = this.map.layers[i]; + if (elem == layer.div || elem == layer.pane) { + allowScroll = true; + break; + } + } + } + } + overMapDiv = (elem == this.map.div); + + elem = elem.parentNode; + } + + // Logic below is the following: + // + // If we are over a scrollable div or not over the map div: + // * do nothing (let the browser handle scrolling) + // + // otherwise + // + // If we are over the layer div or a 'olScrollable' div: + // * zoom/in out + // then + // * kill event (so as not to also scroll the page after zooming) + // + // otherwise + // + // Kill the event (dont scroll the page if we wheel over the + // layerswitcher or the pan/zoom control) + // + if (!overScrollableDiv && overMapDiv) { + if (allowScroll) { + var delta = 0; + + if (e.wheelDelta) { + delta = e.wheelDelta; + if (delta % 160 === 0) { + // opera have steps of 160 instead of 120 + delta = delta * 0.75; + } + delta = delta / 120; + } else if (e.detail) { + // detail in Firefox on OS X is 1/3 of Windows + // so force delta 1 / -1 + delta = - (e.detail / Math.abs(e.detail)); + } + this.delta += delta; + + window.clearTimeout(this._timeoutId); + if(this.interval && Math.abs(this.delta) < this.maxDelta) { + // store e because window.event might change during delay + var evt = OpenLayers.Util.extend({}, e); + this._timeoutId = window.setTimeout( + OpenLayers.Function.bind(function(){ + this.wheelZoom(evt); + }, this), + this.interval + ); + } else { + this.wheelZoom(e); + } + } + OpenLayers.Event.stop(e); + } + }, + + /** + * Method: wheelZoom + * Given the wheel event, we carry out the appropriate zooming in or out, + * based on the 'wheelDelta' or 'detail' property of the event. + * + * Parameters: + * e - {Event} + */ + wheelZoom: function(e) { + var delta = this.delta; + this.delta = 0; + + if (delta) { + e.xy = this.map.events.getMousePosition(e); + if (delta < 0) { + this.callback("down", + [e, this.cumulative ? Math.max(-this.maxDelta, delta) : -1]); + } else { + this.callback("up", + [e, this.cumulative ? Math.min(this.maxDelta, delta) : 1]); + } + } + }, + + /** + * Method: activate + */ + activate: function (evt) { + if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) { + //register mousewheel events specifically on the window and document + var wheelListener = this.wheelListener; + OpenLayers.Event.observe(window, "DOMMouseScroll", wheelListener); + OpenLayers.Event.observe(window, "mousewheel", wheelListener); + OpenLayers.Event.observe(document, "mousewheel", wheelListener); + return true; + } else { + return false; + } + }, + + /** + * Method: deactivate + */ + deactivate: function (evt) { + if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) { + // unregister mousewheel events specifically on the window and document + var wheelListener = this.wheelListener; + OpenLayers.Event.stopObserving(window, "DOMMouseScroll", wheelListener); + OpenLayers.Event.stopObserving(window, "mousewheel", wheelListener); + OpenLayers.Event.stopObserving(document, "mousewheel", wheelListener); + return true; + } else { + return false; + } + }, + + CLASS_NAME: "OpenLayers.Handler.MouseWheel" +}); diff --git a/misc/openlayers/lib/OpenLayers/Handler/Path.js b/misc/openlayers/lib/OpenLayers/Handler/Path.js new file mode 100644 index 0000000..28512a1 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Handler/Path.js @@ -0,0 +1,543 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Handler/Point.js + * @requires OpenLayers/Geometry/Point.js + * @requires OpenLayers/Geometry/LineString.js + */ + +/** + * Class: OpenLayers.Handler.Path + * Handler to draw a path on the map. Path is displayed on mouse down, + * moves on mouse move, and is finished on mouse up. + * + * Inherits from: + * - <OpenLayers.Handler.Point> + */ +OpenLayers.Handler.Path = OpenLayers.Class(OpenLayers.Handler.Point, { + + /** + * Property: line + * {<OpenLayers.Feature.Vector>} + */ + line: null, + + /** + * APIProperty: maxVertices + * {Number} The maximum number of vertices which can be drawn by this + * handler. When the number of vertices reaches maxVertices, the + * geometry is automatically finalized. Default is null. + */ + maxVertices: null, + + /** + * Property: doubleTouchTolerance + * {Number} Maximum number of pixels between two touches for + * the gesture to be considered a "finalize feature" action. + * Default is 20. + */ + doubleTouchTolerance: 20, + + /** + * Property: freehand + * {Boolean} In freehand mode, the handler starts the path on mouse down, + * adds a point for every mouse move, and finishes the path on mouse up. + * Outside of freehand mode, a point is added to the path on every mouse + * click and double-click finishes the path. + */ + freehand: false, + + /** + * Property: freehandToggle + * {String} If set, freehandToggle is checked on mouse events and will set + * the freehand mode to the opposite of this.freehand. To disallow + * toggling between freehand and non-freehand mode, set freehandToggle to + * null. Acceptable toggle values are 'shiftKey', 'ctrlKey', and 'altKey'. + */ + freehandToggle: 'shiftKey', + + /** + * Property: timerId + * {Integer} The timer used to test the double touch. + */ + timerId: null, + + /** + * Property: redoStack + * {Array} Stack containing points removed with <undo>. + */ + redoStack: null, + + /** + * Constructor: OpenLayers.Handler.Path + * Create a new path hander + * + * 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 optional object with properties to be set on the + * handler + * + * Named callbacks: + * create - Called when a sketch is first created. Callback called with + * the creation point geometry and sketch feature. + * modify - Called with each move of a vertex with the vertex (point) + * geometry and the sketch feature. + * point - Called as each point is added. Receives the new point geometry. + * done - Called when the point drawing is finished. The callback will + * recieve a single argument, the linestring geometry. + * cancel - Called when the handler is deactivated while drawing. The + * cancel callback will receive a geometry. + */ + + /** + * Method: createFeature + * Add temporary geometries + * + * Parameters: + * pixel - {<OpenLayers.Pixel>} The initial pixel location for the new + * feature. + */ + createFeature: function(pixel) { + var lonlat = this.layer.getLonLatFromViewPortPx(pixel); + var geometry = new OpenLayers.Geometry.Point( + lonlat.lon, lonlat.lat + ); + this.point = new OpenLayers.Feature.Vector(geometry); + this.line = new OpenLayers.Feature.Vector( + new OpenLayers.Geometry.LineString([this.point.geometry]) + ); + this.callback("create", [this.point.geometry, this.getSketch()]); + this.point.geometry.clearBounds(); + this.layer.addFeatures([this.line, this.point], {silent: true}); + }, + + /** + * Method: destroyFeature + * Destroy temporary geometries + * + * Parameters: + * force - {Boolean} Destroy even if persist is true. + */ + destroyFeature: function(force) { + OpenLayers.Handler.Point.prototype.destroyFeature.call( + this, force); + this.line = null; + }, + + /** + * Method: destroyPersistedFeature + * Destroy the persisted feature. + */ + destroyPersistedFeature: function() { + var layer = this.layer; + if(layer && layer.features.length > 2) { + this.layer.features[0].destroy(); + } + }, + + /** + * Method: removePoint + * Destroy the temporary point. + */ + removePoint: function() { + if(this.point) { + this.layer.removeFeatures([this.point]); + } + }, + + /** + * Method: addPoint + * Add point to geometry. Send the point index to override + * the behavior of LinearRing that disregards adding duplicate points. + * + * Parameters: + * pixel - {<OpenLayers.Pixel>} The pixel location for the new point. + */ + addPoint: function(pixel) { + this.layer.removeFeatures([this.point]); + var lonlat = this.layer.getLonLatFromViewPortPx(pixel); + this.point = new OpenLayers.Feature.Vector( + new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat) + ); + this.line.geometry.addComponent( + this.point.geometry, this.line.geometry.components.length + ); + this.layer.addFeatures([this.point]); + this.callback("point", [this.point.geometry, this.getGeometry()]); + this.callback("modify", [this.point.geometry, this.getSketch()]); + this.drawFeature(); + delete this.redoStack; + }, + + /** + * Method: insertXY + * Insert a point in the current sketch given x & y coordinates. The new + * point is inserted immediately before the most recently drawn point. + * + * Parameters: + * x - {Number} The x-coordinate of the point. + * y - {Number} The y-coordinate of the point. + */ + insertXY: function(x, y) { + this.line.geometry.addComponent( + new OpenLayers.Geometry.Point(x, y), + this.getCurrentPointIndex() + ); + this.drawFeature(); + delete this.redoStack; + }, + + /** + * Method: insertDeltaXY + * Insert a point given offsets from the previously inserted point. + * + * Parameters: + * dx - {Number} The x-coordinate offset of the point. + * dy - {Number} The y-coordinate offset of the point. + */ + insertDeltaXY: function(dx, dy) { + var previousIndex = this.getCurrentPointIndex() - 1; + var p0 = this.line.geometry.components[previousIndex]; + if (p0 && !isNaN(p0.x) && !isNaN(p0.y)) { + this.insertXY(p0.x + dx, p0.y + dy); + } + }, + + /** + * Method: insertDirectionLength + * Insert a point in the current sketch given a direction and a length. + * + * Parameters: + * direction - {Number} Degrees clockwise from the positive x-axis. + * length - {Number} Distance from the previously drawn point. + */ + insertDirectionLength: function(direction, length) { + direction *= Math.PI / 180; + var dx = length * Math.cos(direction); + var dy = length * Math.sin(direction); + this.insertDeltaXY(dx, dy); + }, + + /** + * Method: insertDeflectionLength + * Insert a point in the current sketch given a deflection and a length. + * The deflection should be degrees clockwise from the previously + * digitized segment. + * + * Parameters: + * deflection - {Number} Degrees clockwise from the previous segment. + * length - {Number} Distance from the previously drawn point. + */ + insertDeflectionLength: function(deflection, length) { + var previousIndex = this.getCurrentPointIndex() - 1; + if (previousIndex > 0) { + var p1 = this.line.geometry.components[previousIndex]; + var p0 = this.line.geometry.components[previousIndex-1]; + var theta = Math.atan2(p1.y - p0.y, p1.x - p0.x); + this.insertDirectionLength( + (theta * 180 / Math.PI) + deflection, length + ); + } + }, + + /** + * Method: getCurrentPointIndex + * + * Returns: + * {Number} The index of the most recently drawn point. + */ + getCurrentPointIndex: function() { + return this.line.geometry.components.length - 1; + }, + + + /** + * Method: undo + * Remove the most recently added point in the sketch geometry. + * + * Returns: + * {Boolean} A point was removed. + */ + undo: function() { + var geometry = this.line.geometry; + var components = geometry.components; + var index = this.getCurrentPointIndex() - 1; + var target = components[index]; + var undone = geometry.removeComponent(target); + if (undone) { + // On touch devices, set the current ("mouse location") point to + // match the last digitized point. + if (this.touch && index > 0) { + components = geometry.components; // safety + var lastpt = components[index - 1]; + var curptidx = this.getCurrentPointIndex(); + var curpt = components[curptidx]; + curpt.x = lastpt.x; + curpt.y = lastpt.y; + } + if (!this.redoStack) { + this.redoStack = []; + } + this.redoStack.push(target); + this.drawFeature(); + } + return undone; + }, + + /** + * Method: redo + * Reinsert the most recently removed point resulting from an <undo> call. + * The undo stack is deleted whenever a point is added by other means. + * + * Returns: + * {Boolean} A point was added. + */ + redo: function() { + var target = this.redoStack && this.redoStack.pop(); + if (target) { + this.line.geometry.addComponent(target, this.getCurrentPointIndex()); + this.drawFeature(); + } + return !!target; + }, + + /** + * Method: freehandMode + * Determine whether to behave in freehand mode or not. + * + * Returns: + * {Boolean} + */ + freehandMode: function(evt) { + return (this.freehandToggle && evt[this.freehandToggle]) ? + !this.freehand : this.freehand; + }, + + /** + * Method: modifyFeature + * Modify the existing geometry given the new point + * + * Parameters: + * pixel - {<OpenLayers.Pixel>} The updated pixel location for the latest + * point. + * drawing - {Boolean} Indicate if we're currently drawing. + */ + modifyFeature: function(pixel, drawing) { + if(!this.line) { + this.createFeature(pixel); + } + var lonlat = this.layer.getLonLatFromViewPortPx(pixel); + this.point.geometry.x = lonlat.lon; + this.point.geometry.y = lonlat.lat; + this.callback("modify", [this.point.geometry, this.getSketch(), drawing]); + this.point.geometry.clearBounds(); + this.drawFeature(); + }, + + /** + * Method: drawFeature + * Render geometries on the temporary layer. + */ + drawFeature: function() { + this.layer.drawFeature(this.line, this.style); + this.layer.drawFeature(this.point, this.style); + }, + + /** + * Method: getSketch + * Return the sketch feature. + * + * Returns: + * {<OpenLayers.Feature.Vector>} + */ + getSketch: function() { + return this.line; + }, + + /** + * Method: getGeometry + * Return the sketch geometry. If <multi> is true, this will return + * a multi-part geometry. + * + * Returns: + * {<OpenLayers.Geometry.LineString>} + */ + getGeometry: function() { + var geometry = this.line && this.line.geometry; + if(geometry && this.multi) { + geometry = new OpenLayers.Geometry.MultiLineString([geometry]); + } + return geometry; + }, + + /** + * method: touchstart + * handle touchstart. + * + * parameters: + * evt - {event} the browser event + * + * returns: + * {boolean} allow event propagation + */ + touchstart: function(evt) { + if (this.timerId && + this.passesTolerance(this.lastTouchPx, evt.xy, + this.doubleTouchTolerance)) { + // double-tap, finalize the geometry + this.finishGeometry(); + window.clearTimeout(this.timerId); + this.timerId = null; + return false; + } else { + if (this.timerId) { + window.clearTimeout(this.timerId); + this.timerId = null; + } + this.timerId = window.setTimeout( + OpenLayers.Function.bind(function() { + this.timerId = null; + }, this), 300); + return OpenLayers.Handler.Point.prototype.touchstart.call(this, evt); + } + }, + + /** + * Method: down + * Handle mousedown and touchstart. Add a new point to the geometry and + * render it. Return determines whether to propagate the event on the map. + * + * Parameters: + * evt - {Event} The browser event + * + * Returns: + * {Boolean} Allow event propagation + */ + down: function(evt) { + var stopDown = this.stopDown; + if(this.freehandMode(evt)) { + stopDown = true; + if (this.touch) { + this.modifyFeature(evt.xy, !!this.lastUp); + OpenLayers.Event.stop(evt); + } + } + if (!this.touch && (!this.lastDown || + !this.passesTolerance(this.lastDown, evt.xy, + this.pixelTolerance))) { + this.modifyFeature(evt.xy, !!this.lastUp); + } + this.mouseDown = true; + this.lastDown = evt.xy; + this.stoppedDown = stopDown; + return !stopDown; + }, + + /** + * Method: move + * Handle mousemove and touchmove. Adjust the geometry and redraw. + * Return determines whether to propagate the event on the map. + * + * Parameters: + * evt - {Event} The browser event + * + * Returns: + * {Boolean} Allow event propagation + */ + move: function (evt) { + if(this.stoppedDown && this.freehandMode(evt)) { + if(this.persist) { + this.destroyPersistedFeature(); + } + if(this.maxVertices && this.line && + this.line.geometry.components.length === this.maxVertices) { + this.removePoint(); + this.finalize(); + } else { + this.addPoint(evt.xy); + } + return false; + } + if (!this.touch && (!this.mouseDown || this.stoppedDown)) { + this.modifyFeature(evt.xy, !!this.lastUp); + } + return true; + }, + + /** + * Method: up + * Handle mouseup and touchend. Send the latest point in the geometry to + * the control. Return determines whether to propagate the event on the map. + * + * Parameters: + * evt - {Event} The browser event + * + * Returns: + * {Boolean} Allow event propagation + */ + up: function (evt) { + if (this.mouseDown && (!this.lastUp || !this.lastUp.equals(evt.xy))) { + if(this.stoppedDown && this.freehandMode(evt)) { + if (this.persist) { + this.destroyPersistedFeature(); + } + this.removePoint(); + this.finalize(); + } else { + if (this.passesTolerance(this.lastDown, evt.xy, + this.pixelTolerance)) { + if (this.touch) { + this.modifyFeature(evt.xy); + } + if(this.lastUp == null && this.persist) { + this.destroyPersistedFeature(); + } + this.addPoint(evt.xy); + this.lastUp = evt.xy; + if(this.line.geometry.components.length === this.maxVertices + 1) { + this.finishGeometry(); + } + } + } + } + this.stoppedDown = this.stopDown; + this.mouseDown = false; + return !this.stopUp; + }, + + /** + * APIMethod: finishGeometry + * Finish the geometry and send it back to the control. + */ + finishGeometry: function() { + var index = this.line.geometry.components.length - 1; + this.line.geometry.removeComponent(this.line.geometry.components[index]); + this.removePoint(); + this.finalize(); + }, + + /** + * Method: dblclick + * Handle double-clicks. + * + * Parameters: + * evt - {Event} The browser event + * + * Returns: + * {Boolean} Allow event propagation + */ + dblclick: function(evt) { + if(!this.freehandMode(evt)) { + this.finishGeometry(); + } + return false; + }, + + CLASS_NAME: "OpenLayers.Handler.Path" +}); diff --git a/misc/openlayers/lib/OpenLayers/Handler/Pinch.js b/misc/openlayers/lib/OpenLayers/Handler/Pinch.js new file mode 100644 index 0000000..cd3d086 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Handler/Pinch.js @@ -0,0 +1,239 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Handler.js + */ + +/** + * Class: OpenLayers.Handler.Pinch + * The pinch handler is used to deal with sequences of browser events related + * to pinch gestures. The handler is used by controls that want to know + * when a pinch sequence begins, when a pinch is happening, and when it has + * finished. + * + * Controls that use the pinch handler typically construct it with callbacks + * for 'start', 'move', and 'done'. Callbacks for these keys are + * called when the pinch begins, with each change, and when the pinch is + * done. + * + * Create a new pinch handler with the <OpenLayers.Handler.Pinch> constructor. + * + * Inherits from: + * - <OpenLayers.Handler> + */ +OpenLayers.Handler.Pinch = OpenLayers.Class(OpenLayers.Handler, { + + /** + * Property: started + * {Boolean} When a touchstart event is received, we want to record it, + * but not set 'pinching' until the touchmove get started after + * starting. + */ + started: false, + + /** + * Property: stopDown + * {Boolean} Stop propagation of touchstart events from getting to + * listeners on the same element. Default is false. + */ + stopDown: false, + + /** + * Property: pinching + * {Boolean} + */ + pinching: false, + + /** + * Property: last + * {Object} Object that store informations related to pinch last touch. + */ + last: null, + + /** + * Property: start + * {Object} Object that store informations related to pinch touchstart. + */ + start: null, + + /** + * Constructor: OpenLayers.Handler.Pinch + * Returns OpenLayers.Handler.Pinch + * + * Parameters: + * control - {<OpenLayers.Control>} The control that is making use of + * this handler. If a handler is being used without a control, the + * handlers setMap method must be overridden to deal properly with + * the map. + * callbacks - {Object} An object containing functions to be called when + * the pinch operation start, change, or is finished. The callbacks + * should expect to receive an object argument, which contains + * information about scale, distance, and position of touch points. + * options - {Object} + */ + + /** + * Method: touchstart + * Handle touchstart events + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} Let the event propagate. + */ + touchstart: function(evt) { + var propagate = true; + this.pinching = false; + if (OpenLayers.Event.isMultiTouch(evt)) { + this.started = true; + this.last = this.start = { + distance: this.getDistance(evt.touches), + delta: 0, + scale: 1 + }; + this.callback("start", [evt, this.start]); + propagate = !this.stopDown; + } else if (this.started) { + // Some webkit versions send fake single-touch events during + // multitouch, which cause the drag handler to trigger + return false; + } else { + this.started = false; + this.start = null; + this.last = null; + } + // prevent document dragging + OpenLayers.Event.preventDefault(evt); + return propagate; + }, + + /** + * Method: touchmove + * Handle touchmove events + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} Let the event propagate. + */ + touchmove: function(evt) { + if (this.started && OpenLayers.Event.isMultiTouch(evt)) { + this.pinching = true; + var current = this.getPinchData(evt); + this.callback("move", [evt, current]); + this.last = current; + // prevent document dragging + OpenLayers.Event.stop(evt); + } else if (this.started) { + // Some webkit versions send fake single-touch events during + // multitouch, which cause the drag handler to trigger + return false; + } + return true; + }, + + /** + * Method: touchend + * Handle touchend events + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} Let the event propagate. + */ + touchend: function(evt) { + if (this.started && !OpenLayers.Event.isMultiTouch(evt)) { + this.started = false; + this.pinching = false; + this.callback("done", [evt, this.start, this.last]); + this.start = null; + this.last = null; + return false; + } + return true; + }, + + /** + * Method: activate + * Activate the handler. + * + * Returns: + * {Boolean} The handler was successfully activated. + */ + activate: function() { + var activated = false; + if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) { + this.pinching = false; + activated = true; + } + return activated; + }, + + /** + * Method: deactivate + * Deactivate the handler. + * + * Returns: + * {Boolean} The handler was successfully deactivated. + */ + deactivate: function() { + var deactivated = false; + if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) { + this.started = false; + this.pinching = false; + this.start = null; + this.last = null; + deactivated = true; + } + return deactivated; + }, + + /** + * Method: getDistance + * Get the distance in pixels between two touches. + * + * Parameters: + * touches - {Array(Object)} + * + * Returns: + * {Number} The distance in pixels. + */ + getDistance: function(touches) { + var t0 = touches[0]; + var t1 = touches[1]; + return Math.sqrt( + Math.pow(t0.olClientX - t1.olClientX, 2) + + Math.pow(t0.olClientY - t1.olClientY, 2) + ); + }, + + + /** + * Method: getPinchData + * Get informations about the pinch event. + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Object} Object that contains data about the current pinch. + */ + getPinchData: function(evt) { + var distance = this.getDistance(evt.touches); + var scale = distance / this.start.distance; + return { + distance: distance, + delta: this.last.distance - distance, + scale: scale + }; + }, + + CLASS_NAME: "OpenLayers.Handler.Pinch" +}); + diff --git a/misc/openlayers/lib/OpenLayers/Handler/Point.js b/misc/openlayers/lib/OpenLayers/Handler/Point.js new file mode 100644 index 0000000..b4bb17c --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Handler/Point.js @@ -0,0 +1,556 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Handler.js + * @requires OpenLayers/Geometry/Point.js + */ + +/** + * Class: OpenLayers.Handler.Point + * Handler to draw a point on the map. Point is displayed on activation, + * moves on mouse move, and is finished on mouse up. The handler triggers + * callbacks for 'done', 'cancel', and 'modify'. The modify callback is + * called with each change in the sketch and will receive the latest point + * drawn. Create a new instance with the <OpenLayers.Handler.Point> + * constructor. + * + * Inherits from: + * - <OpenLayers.Handler> + */ +OpenLayers.Handler.Point = OpenLayers.Class(OpenLayers.Handler, { + + /** + * Property: point + * {<OpenLayers.Feature.Vector>} The currently drawn point + */ + point: null, + + /** + * Property: layer + * {<OpenLayers.Layer.Vector>} The temporary drawing layer + */ + layer: null, + + /** + * APIProperty: multi + * {Boolean} Cast features to multi-part geometries before passing to the + * layer. Default is false. + */ + multi: false, + + /** + * APIProperty: citeCompliant + * {Boolean} If set to true, coordinates of features drawn in a map extent + * crossing the date line won't exceed the world bounds. Default is false. + */ + citeCompliant: false, + + /** + * Property: mouseDown + * {Boolean} The mouse is down + */ + mouseDown: false, + + /** + * Property: stoppedDown + * {Boolean} Indicate whether the last mousedown stopped the event + * propagation. + */ + stoppedDown: null, + + /** + * Property: lastDown + * {<OpenLayers.Pixel>} Location of the last mouse down + */ + lastDown: null, + + /** + * Property: lastUp + * {<OpenLayers.Pixel>} + */ + lastUp: null, + + /** + * APIProperty: persist + * {Boolean} Leave the feature rendered until destroyFeature is called. + * Default is false. If set to true, the feature remains rendered until + * destroyFeature is called, typically by deactivating the handler or + * starting another drawing. + */ + persist: false, + + /** + * APIProperty: stopDown + * {Boolean} Stop event propagation on mousedown. Must be false to + * allow "pan while drawing". Defaults to false. + */ + stopDown: false, + + /** + * APIPropery: stopUp + * {Boolean} Stop event propagation on mouse. Must be false to + * allow "pan while dragging". Defaults to fase. + */ + stopUp: false, + + /** + * Property: layerOptions + * {Object} Any optional properties to be set on the sketch layer. + */ + layerOptions: null, + + /** + * APIProperty: pixelTolerance + * {Number} Maximum number of pixels between down and up (mousedown + * and mouseup, or touchstart and touchend) for the handler to + * add a new point. If set to an integer value, if the + * displacement between down and up is great to this value + * no point will be added. Default value is 5. + */ + pixelTolerance: 5, + + /** + * Property: lastTouchPx + * {<OpenLayers.Pixel>} The last pixel used to know the distance between + * two touches (for double touch). + */ + lastTouchPx: null, + + /** + * Constructor: OpenLayers.Handler.Point + * Create a new point 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 optional object with properties to be set on the + * handler + * + * Named callbacks: + * create - Called when a sketch is first created. Callback called with + * the creation point geometry and sketch feature. + * modify - Called with each move of a vertex with the vertex (point) + * geometry and the sketch feature. + * done - Called when the point drawing is finished. The callback will + * recieve a single argument, the point 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, arguments); + }, + + /** + * APIMethod: activate + * turn on the handler + */ + activate: function() { + if(!OpenLayers.Handler.prototype.activate.apply(this, arguments)) { + return false; + } + // create temporary vector layer for rendering geometry sketch + // TBD: this could be moved to initialize/destroy - setting visibility here + 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, + wrapDateLine: this.citeCompliant + }, this.layerOptions); + this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME, options); + this.map.addLayer(this.layer); + return true; + }, + + /** + * Method: createFeature + * Add temporary features + * + * Parameters: + * pixel - {<OpenLayers.Pixel>} A pixel location on the map. + */ + createFeature: function(pixel) { + var lonlat = this.layer.getLonLatFromViewPortPx(pixel); + var geometry = new OpenLayers.Geometry.Point( + lonlat.lon, lonlat.lat + ); + this.point = new OpenLayers.Feature.Vector(geometry); + this.callback("create", [this.point.geometry, this.point]); + this.point.geometry.clearBounds(); + this.layer.addFeatures([this.point], {silent: true}); + }, + + /** + * APIMethod: deactivate + * turn off the handler + */ + deactivate: function() { + if(!OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) { + return false; + } + 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.destroyFeature(true); + this.layer.destroy(false); + } + this.layer = null; + return true; + }, + + /** + * Method: destroyFeature + * Destroy the temporary geometries + * + * Parameters: + * force - {Boolean} Destroy even if persist is true. + */ + destroyFeature: function(force) { + if(this.layer && (force || !this.persist)) { + this.layer.destroyFeatures(); + } + this.point = null; + }, + + /** + * Method: destroyPersistedFeature + * Destroy the persisted feature. + */ + destroyPersistedFeature: function() { + var layer = this.layer; + if(layer && layer.features.length > 1) { + this.layer.features[0].destroy(); + } + }, + + /** + * Method: finalize + * Finish the geometry and call the "done" callback. + * + * Parameters: + * cancel - {Boolean} Call cancel instead of done callback. Default + * is false. + */ + finalize: function(cancel) { + var key = cancel ? "cancel" : "done"; + this.mouseDown = false; + this.lastDown = null; + this.lastUp = null; + this.lastTouchPx = null; + this.callback(key, [this.geometryClone()]); + this.destroyFeature(cancel); + }, + + /** + * APIMethod: cancel + * Finish the geometry and call the "cancel" callback. + */ + cancel: function() { + this.finalize(true); + }, + + /** + * Method: click + * Handle clicks. Clicks are stopped from propagating to other listeners + * on map.events or other dom elements. + * + * Parameters: + * evt - {Event} The browser event + * + * Returns: + * {Boolean} Allow event propagation + */ + click: function(evt) { + OpenLayers.Event.stop(evt); + return false; + }, + + /** + * Method: dblclick + * Handle double-clicks. Double-clicks are stopped from propagating to other + * listeners on map.events or other dom elements. + * + * Parameters: + * evt - {Event} The browser event + * + * Returns: + * {Boolean} Allow event propagation + */ + dblclick: function(evt) { + OpenLayers.Event.stop(evt); + return false; + }, + + /** + * Method: modifyFeature + * Modify the existing geometry given a pixel location. + * + * Parameters: + * pixel - {<OpenLayers.Pixel>} A pixel location on the map. + */ + modifyFeature: function(pixel) { + if(!this.point) { + this.createFeature(pixel); + } + var lonlat = this.layer.getLonLatFromViewPortPx(pixel); + this.point.geometry.x = lonlat.lon; + this.point.geometry.y = lonlat.lat; + this.callback("modify", [this.point.geometry, this.point, false]); + this.point.geometry.clearBounds(); + this.drawFeature(); + }, + + /** + * Method: drawFeature + * Render features on the temporary layer. + */ + drawFeature: function() { + this.layer.drawFeature(this.point, this.style); + }, + + /** + * Method: getGeometry + * Return the sketch geometry. If <multi> is true, this will return + * a multi-part geometry. + * + * Returns: + * {<OpenLayers.Geometry.Point>} + */ + getGeometry: function() { + var geometry = this.point && this.point.geometry; + if(geometry && this.multi) { + geometry = new OpenLayers.Geometry.MultiPoint([geometry]); + } + return geometry; + }, + + /** + * Method: geometryClone + * Return a clone of the relevant geometry. + * + * Returns: + * {<OpenLayers.Geometry>} + */ + geometryClone: function() { + var geom = this.getGeometry(); + return geom && geom.clone(); + }, + + /** + * Method: mousedown + * Handle mousedown. + * + * Parameters: + * evt - {Event} The browser event + * + * Returns: + * {Boolean} Allow event propagation + */ + mousedown: function(evt) { + return this.down(evt); + }, + + /** + * Method: touchstart + * Handle touchstart. + * + * Parameters: + * evt - {Event} The browser event + * + * Returns: + * {Boolean} Allow event propagation + */ + touchstart: function(evt) { + this.startTouch(); + this.lastTouchPx = evt.xy; + return this.down(evt); + }, + + /** + * Method: mousemove + * Handle mousemove. + * + * Parameters: + * evt - {Event} The browser event + * + * Returns: + * {Boolean} Allow event propagation + */ + mousemove: function(evt) { + return this.move(evt); + }, + + /** + * Method: touchmove + * Handle touchmove. + * + * Parameters: + * evt - {Event} The browser event + * + * Returns: + * {Boolean} Allow event propagation + */ + touchmove: function(evt) { + this.lastTouchPx = evt.xy; + return this.move(evt); + }, + + /** + * Method: mouseup + * Handle mouseup. + * + * Parameters: + * evt - {Event} The browser event + * + * Returns: + * {Boolean} Allow event propagation + */ + mouseup: function(evt) { + return this.up(evt); + }, + + /** + * Method: touchend + * Handle touchend. + * + * Parameters: + * evt - {Event} The browser event + * + * Returns: + * {Boolean} Allow event propagation + */ + touchend: function(evt) { + evt.xy = this.lastTouchPx; + return this.up(evt); + }, + + /** + * Method: down + * Handle mousedown and touchstart. Adjust the geometry and redraw. + * Return determines whether to propagate the event on the map. + * + * Parameters: + * evt - {Event} The browser event + * + * Returns: + * {Boolean} Allow event propagation + */ + down: function(evt) { + this.mouseDown = true; + this.lastDown = evt.xy; + if(!this.touch) { // no point displayed until up on touch devices + this.modifyFeature(evt.xy); + } + this.stoppedDown = this.stopDown; + return !this.stopDown; + }, + + /** + * Method: move + * Handle mousemove and touchmove. Adjust the geometry and redraw. + * Return determines whether to propagate the event on the map. + * + * Parameters: + * evt - {Event} The browser event + * + * Returns: + * {Boolean} Allow event propagation + */ + move: function (evt) { + if(!this.touch // no point displayed until up on touch devices + && (!this.mouseDown || this.stoppedDown)) { + this.modifyFeature(evt.xy); + } + return true; + }, + + /** + * Method: up + * Handle mouseup and touchend. Send the latest point in the geometry to the control. + * Return determines whether to propagate the event on the map. + * + * Parameters: + * evt - {Event} The browser event + * + * Returns: + * {Boolean} Allow event propagation + */ + up: function (evt) { + this.mouseDown = false; + this.stoppedDown = this.stopDown; + + // check keyboard modifiers + if(!this.checkModifiers(evt)) { + return true; + } + // ignore double-clicks + if (this.lastUp && this.lastUp.equals(evt.xy)) { + return true; + } + if (this.lastDown && this.passesTolerance(this.lastDown, evt.xy, + this.pixelTolerance)) { + if (this.touch) { + this.modifyFeature(evt.xy); + } + if(this.persist) { + this.destroyPersistedFeature(); + } + this.lastUp = evt.xy; + this.finalize(); + return !this.stopUp; + } else { + return true; + } + }, + + /** + * Method: mouseout + * Handle mouse out. For better user experience reset mouseDown + * and stoppedDown when the mouse leaves the map viewport. + * + * Parameters: + * evt - {Event} The browser event + */ + mouseout: function(evt) { + if(OpenLayers.Util.mouseLeft(evt, this.map.viewPortDiv)) { + this.stoppedDown = this.stopDown; + this.mouseDown = false; + } + }, + + /** + * Method: passesTolerance + * Determine whether the event is within the optional pixel tolerance. + * + * Returns: + * {Boolean} The event is within the pixel tolerance (if specified). + */ + passesTolerance: function(pixel1, pixel2, tolerance) { + var passes = true; + + if (tolerance != null && pixel1 && pixel2) { + var dist = pixel1.distanceTo(pixel2); + if (dist > tolerance) { + passes = false; + } + } + return passes; + }, + + CLASS_NAME: "OpenLayers.Handler.Point" +}); diff --git a/misc/openlayers/lib/OpenLayers/Handler/Polygon.js b/misc/openlayers/lib/OpenLayers/Handler/Polygon.js new file mode 100644 index 0000000..4f6dfd2 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Handler/Polygon.js @@ -0,0 +1,305 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Handler/Path.js + * @requires OpenLayers/Geometry/Polygon.js + */ + +/** + * Class: OpenLayers.Handler.Polygon + * Handler to draw a polygon on the map. Polygon is displayed on mouse down, + * moves on mouse move, and is finished on mouse up. + * + * Inherits from: + * - <OpenLayers.Handler.Path> + * - <OpenLayers.Handler> + */ +OpenLayers.Handler.Polygon = OpenLayers.Class(OpenLayers.Handler.Path, { + + /** + * APIProperty: holeModifier + * {String} Key modifier to trigger hole digitizing. Acceptable values are + * "altKey", "shiftKey", or "ctrlKey". If not set, no hole digitizing + * will take place. Default is null. + */ + holeModifier: null, + + /** + * Property: drawingHole + * {Boolean} Currently drawing an interior ring. + */ + drawingHole: false, + + /** + * Property: polygon + * {<OpenLayers.Feature.Vector>} + */ + polygon: null, + + /** + * Constructor: OpenLayers.Handler.Polygon + * Create a 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 optional object with properties to be set on the + * handler + * + * Named callbacks: + * create - Called when a sketch is first created. Callback called with + * the creation point geometry and sketch feature. + * modify - Called with each move of a vertex with the vertex (point) + * geometry and the sketch feature. + * point - Called as each point is added. Receives the new point geometry. + * done - Called when the point drawing is finished. The callback will + * recieve a single argument, the polygon geometry. + * cancel - Called when the handler is deactivated while drawing. The + * cancel callback will receive a geometry. + */ + + /** + * Method: createFeature + * Add temporary geometries + * + * Parameters: + * pixel - {<OpenLayers.Pixel>} The initial pixel location for the new + * feature. + */ + createFeature: function(pixel) { + var lonlat = this.layer.getLonLatFromViewPortPx(pixel); + var geometry = new OpenLayers.Geometry.Point( + lonlat.lon, lonlat.lat + ); + this.point = new OpenLayers.Feature.Vector(geometry); + this.line = new OpenLayers.Feature.Vector( + new OpenLayers.Geometry.LinearRing([this.point.geometry]) + ); + this.polygon = new OpenLayers.Feature.Vector( + new OpenLayers.Geometry.Polygon([this.line.geometry]) + ); + this.callback("create", [this.point.geometry, this.getSketch()]); + this.point.geometry.clearBounds(); + this.layer.addFeatures([this.polygon, this.point], {silent: true}); + }, + + /** + * Method: addPoint + * Add point to geometry. + * + * Parameters: + * pixel - {<OpenLayers.Pixel>} The pixel location for the new point. + */ + addPoint: function(pixel) { + if(!this.drawingHole && this.holeModifier && + this.evt && this.evt[this.holeModifier]) { + var geometry = this.point.geometry; + var features = this.control.layer.features; + var candidate, polygon; + // look for intersections, last drawn gets priority + for (var i=features.length-1; i>=0; --i) { + candidate = features[i].geometry; + if ((candidate instanceof OpenLayers.Geometry.Polygon || + candidate instanceof OpenLayers.Geometry.MultiPolygon) && + candidate.intersects(geometry)) { + polygon = features[i]; + this.control.layer.removeFeatures([polygon], {silent: true}); + this.control.layer.events.registerPriority( + "sketchcomplete", this, this.finalizeInteriorRing + ); + this.control.layer.events.registerPriority( + "sketchmodified", this, this.enforceTopology + ); + polygon.geometry.addComponent(this.line.geometry); + this.polygon = polygon; + this.drawingHole = true; + break; + } + } + } + OpenLayers.Handler.Path.prototype.addPoint.apply(this, arguments); + }, + + /** + * Method: getCurrentPointIndex + * + * Returns: + * {Number} The index of the most recently drawn point. + */ + getCurrentPointIndex: function() { + return this.line.geometry.components.length - 2; + }, + + /** + * Method: enforceTopology + * Simple topology enforcement for drawing interior rings. Ensures vertices + * of interior rings are contained by exterior ring. Other topology + * rules are enforced in <finalizeInteriorRing> to allow drawing of + * rings that intersect only during the sketch (e.g. a "C" shaped ring + * that nearly encloses another ring). + */ + enforceTopology: function(event) { + var point = event.vertex; + var components = this.line.geometry.components; + // ensure that vertices of interior ring are contained by exterior ring + if (!this.polygon.geometry.intersects(point)) { + var last = components[components.length-3]; + point.x = last.x; + point.y = last.y; + } + }, + + /** + * Method: finishGeometry + * Finish the geometry and send it back to the control. + */ + finishGeometry: function() { + var index = this.line.geometry.components.length - 2; + this.line.geometry.removeComponent(this.line.geometry.components[index]); + this.removePoint(); + this.finalize(); + }, + + /** + * Method: finalizeInteriorRing + * Enforces that new ring has some area and doesn't contain vertices of any + * other rings. + */ + finalizeInteriorRing: function() { + var ring = this.line.geometry; + // ensure that ring has some area + var modified = (ring.getArea() !== 0); + if (modified) { + // ensure that new ring doesn't intersect any other rings + var rings = this.polygon.geometry.components; + for (var i=rings.length-2; i>=0; --i) { + if (ring.intersects(rings[i])) { + modified = false; + break; + } + } + if (modified) { + // ensure that new ring doesn't contain any other rings + var target; + outer: for (var i=rings.length-2; i>0; --i) { + var points = rings[i].components; + for (var j=0, jj=points.length; j<jj; ++j) { + if (ring.containsPoint(points[j])) { + modified = false; + break outer; + } + } + } + } + } + if (modified) { + if (this.polygon.state !== OpenLayers.State.INSERT) { + this.polygon.state = OpenLayers.State.UPDATE; + } + } else { + this.polygon.geometry.removeComponent(ring); + } + this.restoreFeature(); + return false; + }, + + /** + * APIMethod: cancel + * Finish the geometry and call the "cancel" callback. + */ + cancel: function() { + if (this.drawingHole) { + this.polygon.geometry.removeComponent(this.line.geometry); + this.restoreFeature(true); + } + return OpenLayers.Handler.Path.prototype.cancel.apply(this, arguments); + }, + + /** + * Method: restoreFeature + * Move the feature from the sketch layer to the target layer. + * + * Properties: + * cancel - {Boolean} Cancel drawing. If falsey, the "sketchcomplete" event + * will be fired. + */ + restoreFeature: function(cancel) { + this.control.layer.events.unregister( + "sketchcomplete", this, this.finalizeInteriorRing + ); + this.control.layer.events.unregister( + "sketchmodified", this, this.enforceTopology + ); + this.layer.removeFeatures([this.polygon], {silent: true}); + this.control.layer.addFeatures([this.polygon], {silent: true}); + this.drawingHole = false; + if (!cancel) { + // Re-trigger "sketchcomplete" so other listeners can do their + // business. While this is somewhat sloppy (if a listener is + // registered with registerPriority - not common - between the start + // and end of a single ring drawing - very uncommon - it will be + // called twice). + // TODO: In 3.0, collapse sketch handlers into geometry specific + // drawing controls. + this.control.layer.events.triggerEvent( + "sketchcomplete", {feature : this.polygon} + ); + } + }, + + /** + * Method: destroyFeature + * Destroy temporary geometries + * + * Parameters: + * force - {Boolean} Destroy even if persist is true. + */ + destroyFeature: function(force) { + OpenLayers.Handler.Path.prototype.destroyFeature.call( + this, force); + this.polygon = null; + }, + + /** + * Method: drawFeature + * Render geometries on the temporary layer. + */ + drawFeature: function() { + this.layer.drawFeature(this.polygon, this.style); + this.layer.drawFeature(this.point, this.style); + }, + + /** + * Method: getSketch + * Return the sketch feature. + * + * Returns: + * {<OpenLayers.Feature.Vector>} + */ + getSketch: function() { + return this.polygon; + }, + + /** + * Method: getGeometry + * Return the sketch geometry. If <multi> is true, this will return + * a multi-part geometry. + * + * Returns: + * {<OpenLayers.Geometry.Polygon>} + */ + getGeometry: function() { + var geometry = this.polygon && this.polygon.geometry; + if(geometry && this.multi) { + geometry = new OpenLayers.Geometry.MultiPolygon([geometry]); + } + return geometry; + }, + + CLASS_NAME: "OpenLayers.Handler.Polygon" +}); diff --git a/misc/openlayers/lib/OpenLayers/Handler/RegularPolygon.js b/misc/openlayers/lib/OpenLayers/Handler/RegularPolygon.js new file mode 100644 index 0000000..bf4e2db --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Handler/RegularPolygon.js @@ -0,0 +1,429 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository 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.Drag> + */ +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, + + /** + * APIProperty: citeCompliant + * {Boolean} If set to true, coordinates of features drawn in a map extent + * crossing the date line won't exceed the world bounds. Default is false. + */ + citeCompliant: 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.Drag.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. + * + * Returns: + * {Boolean} The handler was successfully activated + */ + activate: function() { + var activated = false; + if(OpenLayers.Handler.Drag.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, + wrapDateLine: this.citeCompliant + }, 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. + * + * Returns: + * {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.layer.getLonLatFromViewPortPx(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.layer.getLonLatFromViewPortPx(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); + this.finalize(); + }, + + /** + * Method: finalize + * Finish the geometry and call the "done" callback. + */ + finalize: function() { + this.origin = null; + this.radius = this.options.radius; + }, + + /** + * APIMethod: clear + * Clear any rendered features on the temporary layer. This is called + * when the handler is deactivated, canceled, or done (unless persist + * is true). + */ + clear: function() { + if (this.layer) { + this.layer.renderer.clear(); + this.layer.destroyFeatures(); + } + }, + + /** + * Method: callback + * Trigger the control's named callback with the given arguments + * + * Parameters: + * name - {String} The key for the callback that is one of the properties + * of the handler's callbacks object. + * args - {Array} An array of arguments with which to call the callback + * (defined by the control). + */ + callback: function (name, args) { + // override the callback method to always send the polygon geometry + if (this.callbacks[name]) { + this.callbacks[name].apply(this.control, + [this.feature.geometry.clone()]); + } + // since sketch features are added to the temporary layer + // they must be cleared here if done or cancel + if(!this.persist && (name == "done" || name == "cancel")) { + this.clear(); + } + }, + + CLASS_NAME: "OpenLayers.Handler.RegularPolygon" +}); |