diff --git a/build/loader_hosted_examples.js b/build/loader_hosted_examples.js index 3ec22aa5e2..9afe49715f 100644 --- a/build/loader_hosted_examples.js +++ b/build/loader_hosted_examples.js @@ -6,8 +6,8 @@ * This loader is used for the hosted examples. It is used in place of the * development loader (examples/loader.js). * - * ol.css and ol.js are built with Plovr/Closure, based build/ol.json. - * (`make build` should build them). They are located in the ../build/ + * ol.css and ol.js are built with Plovr/Closure, based on build/ol.json. + * (`build.py build` builds them). They are located in the ../build/ * directory, relatively to this script. * * The script should be named loader.js. So it needs to be renamed to diff --git a/css/ol.css b/css/ol.css index eca80d969c..79fec30bae 100644 --- a/css/ol.css +++ b/css/ol.css @@ -5,9 +5,7 @@ color: #eeeeee; bottom: 0; right: 0; - background: #130085; /* @alternate */ background: rgba(0,60,136,0.3); - filter: alpha(opacity=30); font-family: 'Lucida Grande',Verdana,Geneva,Lucida,Arial,Helvetica,sans-serif; padding: 2px 4px; } @@ -60,14 +58,10 @@ height: 22px; width: 22px; line-height: 19px; - background: #130085; /* @alternate */ background: rgba(0,60,136,0.5); - filter: alpha(opacity=80); } .ol-zoom a:hover { - background: #130085; /* @alternate */ background: rgba(0,60,136,0.7); - filter: alpha(opacity=100); } @media only screen and (max-width:600px) { .ol-zoom a:hover { diff --git a/examples/side-by-side.js b/examples/side-by-side.js index b5d108b0df..661d1ebb79 100644 --- a/examples/side-by-side.js +++ b/examples/side-by-side.js @@ -131,7 +131,7 @@ keyboardInteraction.addCallback('l', function() { view.setCenter(LONDON); }); keyboardInteraction.addCallback('L', function() { - var start = goog.now(); + var start = +new Date(); var duration = 5000; var bounce = ol.animation.bounce({ resolution: 2 * view.getResolution(), @@ -166,7 +166,7 @@ keyboardInteraction.addCallback('m', function() { view.setCenter(MOSCOW); }); keyboardInteraction.addCallback('M', function() { - var start = goog.now(); + var start = +new Date(); var duration = 5000; var bounce = ol.animation.bounce({ resolution: 2 * view.getResolution(), diff --git a/examples/standalone/full-screen-standalone.html b/examples/standalone/full-screen-standalone.html deleted file mode 100644 index afb0f97f38..0000000000 --- a/examples/standalone/full-screen-standalone.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - ol3 full-screen demo - - - -
- - - diff --git a/examples/standalone/overlay-and-popup-standalone.html b/examples/standalone/overlay-and-popup-standalone.html deleted file mode 100644 index e1293b1c54..0000000000 --- a/examples/standalone/overlay-and-popup-standalone.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - ol3 overlay-and-popup demo - - - -
- - Vienna - - -
- - - diff --git a/examples/standalone/side-by-side-standalone.html b/examples/standalone/side-by-side-standalone.html deleted file mode 100644 index e28c0425a7..0000000000 --- a/examples/standalone/side-by-side-standalone.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - ol3 side-by-side demo - - - -

ol3 side-by-side demo

- - - - - - - - - - - - - -
DOMWebGL
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Pan:drag, arrow keys
Zoom:double-click, Shift+double-click, mouse wheel, +/- keys; Shift+drag
Rotate:Alt+drag, r to reset
Brightness/contrast:b/B/c/C keys (WebGL only)
Hue/saturation:h/H/s/S keys (WebGL only)
Opacity:o/O keys
Visibility:v/V keys
Reset0 key
-

Notes: The two maps share the same center, resolution, rotation and layers.

- - - diff --git a/examples/wms-custom-proj.js b/examples/wms-custom-proj.js index fea7b563dd..20b2b90072 100644 --- a/examples/wms-custom-proj.js +++ b/examples/wms-custom-proj.js @@ -29,9 +29,8 @@ ol.Projection.addProjection(epsg21781); // We give the single image source a set of resolutions. This prevents the // source from requesting images of arbitrary resolutions. var projectionExtent = epsg21781.getExtent(); -var maxResolution = Math.max( - projectionExtent.maxX - projectionExtent.minX, - projectionExtent.maxY - projectionExtent.minY) / 256; +var maxResolution = Math.max(projectionExtent.getWidth(), + projectionExtent.getHeight()) / 256; var resolutions = new Array(10); for (var i = 0; i < 10; ++i) { resolutions[i] = maxResolution / Math.pow(2.0, i); diff --git a/externs/tilejson.js b/externs/tilejson.js index 5927c479ee..6e982cc325 100644 --- a/externs/tilejson.js +++ b/externs/tilejson.js @@ -66,7 +66,7 @@ TileJSON.prototype.tiles; /** - * @type {!Array.} + * @type {!Array.|undefined} */ TileJSON.prototype.grids; diff --git a/src/objectliterals.exports b/src/objectliterals.exports index 0e24207a11..4af5ad024f 100644 --- a/src/objectliterals.exports +++ b/src/objectliterals.exports @@ -12,6 +12,8 @@ @exportObjectLiteralProperty ol.MapOptions.renderers Array.|undefined @exportObjectLiteralProperty ol.MapOptions.shiftDragZoom boolean|undefined @exportObjectLiteralProperty ol.MapOptions.target Element|string +@exportObjectLiteralProperty ol.MapOptions.touchPan boolean|undefined +@exportObjectLiteralProperty ol.MapOptions.touchRotateZoom boolean|undefined @exportObjectLiteralProperty ol.MapOptions.view ol.IView|undefined @exportObjectLiteralProperty ol.MapOptions.zoomControl boolean|undefined @exportObjectLiteralProperty ol.MapOptions.zoomDelta number|undefined diff --git a/src/ol/animation.exports b/src/ol/animation.exports index 98207b2e1a..4a8819704d 100644 --- a/src/ol/animation.exports +++ b/src/ol/animation.exports @@ -2,3 +2,4 @@ @exportProperty ol.animation.bounce @exportProperty ol.animation.pan @exportProperty ol.animation.rotate +@exportProperty ol.animation.zoom diff --git a/src/ol/easing.exports b/src/ol/easing.exports new file mode 100644 index 0000000000..62418771ad --- /dev/null +++ b/src/ol/easing.exports @@ -0,0 +1,5 @@ +@exportSymbol ol.easing +@exportProperty ol.easing.linear +@exportProperty ol.easing.upAndDown +@exportProperty ol.easing.elastic +@exportProperty ol.easing.bounce diff --git a/src/ol/extent.exports b/src/ol/extent.exports index 23e1785d4f..b52ee8007a 100644 --- a/src/ol/extent.exports +++ b/src/ol/extent.exports @@ -1,2 +1,3 @@ @exportSymbol ol.Extent - +@exportProperty ol.Extent.prototype.getHeight +@exportProperty ol.Extent.prototype.getWidth diff --git a/src/imageurlfunction.js b/src/ol/imageurlfunction.js similarity index 96% rename from src/imageurlfunction.js rename to src/ol/imageurlfunction.js index bb328819eb..ebaec5c427 100644 --- a/src/imageurlfunction.js +++ b/src/ol/imageurlfunction.js @@ -1,6 +1,7 @@ goog.provide('ol.ImageUrlFunction'); goog.provide('ol.ImageUrlFunctionType'); +goog.require('goog.uri.utils'); goog.require('ol.Extent'); goog.require('ol.Size'); diff --git a/src/ol/interaction/touchinteraction.js b/src/ol/interaction/touchinteraction.js new file mode 100644 index 0000000000..c71d5997de --- /dev/null +++ b/src/ol/interaction/touchinteraction.js @@ -0,0 +1,119 @@ + +goog.provide('ol.interaction.Touch'); + +goog.require('goog.functions'); +goog.require('ol.MapBrowserEvent'); +goog.require('ol.MapBrowserEvent.EventType'); +goog.require('ol.Pixel'); +goog.require('ol.interaction.Interaction'); + + + +/** + * @constructor + * @extends {ol.interaction.Interaction} + */ +ol.interaction.Touch = function() { + + goog.base(this); + + /** + * @type {boolean} + * @private + */ + this.handled_ = false; + + /** + * @type {Object} + * @private + */ + this.trackedTouches_ = {}; + + /** + * @type {Array.} + */ + this.targetTouches = []; + +}; +goog.inherits(ol.interaction.Touch, ol.interaction.Interaction); + + +/** + * @param {Array.} touches TouchEvents. + * @return {ol.Pixel} Centroid pixel. + */ +ol.interaction.Touch.centroid = function(touches) { + var length = touches.length; + var clientX = 0; + var clientY = 0; + for (var i = 0; i < length; i++) { + clientX += touches[i].clientX; + clientY += touches[i].clientY; + } + return new ol.Pixel(clientX / length, clientY / length); +}; + + +/** + * @param {ol.MapBrowserEvent} mapBrowserEvent Event. + * @private + */ +ol.interaction.Touch.prototype.updateTrackedTouches_ = + function(mapBrowserEvent) { + var event = mapBrowserEvent.browserEvent.getBrowserEvent(); + if (goog.isDef(event.targetTouches)) { + // W3C touch events + this.targetTouches = event.targetTouches; + } else { + // IE pointer event + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.TOUCHEND) { + delete this.trackedTouches_[event.pointerId]; + } else { + this.trackedTouches_[event.pointerId] = event; + } + this.targetTouches = goog.object.getValues(this.trackedTouches_); + } +}; + + +/** + * @param {ol.MapBrowserEvent} mapBrowserEvent Event. + * @protected + */ +ol.interaction.Touch.prototype.handleTouchMove = goog.nullFunction; + + +/** + * @param {ol.MapBrowserEvent} mapBrowserEvent Event. + * @protected + * @return {boolean} Capture dragging. + */ +ol.interaction.Touch.prototype.handleTouchEnd = goog.functions.FALSE; + + +/** + * @param {ol.MapBrowserEvent} mapBrowserEvent Event. + * @protected + * @return {boolean} Capture dragging. + */ +ol.interaction.Touch.prototype.handleTouchStart = goog.functions.FALSE; + + +/** + * @inheritDoc + */ +ol.interaction.Touch.prototype.handleMapBrowserEvent = + function(mapBrowserEvent) { + var browserEvent = mapBrowserEvent.browserEvent.getBrowserEvent(); + this.updateTrackedTouches_(mapBrowserEvent); + if (this.handled_) { + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.TOUCHMOVE) { + this.handleTouchMove(mapBrowserEvent); + } else if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.TOUCHEND) { + this.handled_ = this.handleTouchEnd(mapBrowserEvent); + } + } + if (mapBrowserEvent.type == ol.MapBrowserEvent.EventType.TOUCHSTART) { + this.handled_ = this.handleTouchStart(mapBrowserEvent); + } +}; diff --git a/src/ol/interaction/touchpaninteraction.js b/src/ol/interaction/touchpaninteraction.js new file mode 100644 index 0000000000..0c21c78c4b --- /dev/null +++ b/src/ol/interaction/touchpaninteraction.js @@ -0,0 +1,116 @@ +// FIXME works for View2D only +// FIXME opt_kinetic param +goog.provide('ol.interaction.TouchPan'); + +goog.require('goog.asserts'); +goog.require('ol.Coordinate'); +goog.require('ol.Kinetic'); +goog.require('ol.Pixel'); +goog.require('ol.PreRenderFunction'); +goog.require('ol.View'); +goog.require('ol.ViewHint'); +goog.require('ol.interaction.Touch'); + + + +/** + * @constructor + * @extends {ol.interaction.Touch} + */ +ol.interaction.TouchPan = function() { + + goog.base(this); + + /** + * @private + * @type {ol.Kinetic} + */ + this.kinetic_ = new ol.Kinetic(-0.005, 0.05, 100); + + /** + * @private + * @type {?ol.PreRenderFunction} + */ + this.kineticPreRenderFn_ = null; + + /** + * @type {ol.Pixel} + */ + this.lastCentroid = null; + +}; +goog.inherits(ol.interaction.TouchPan, ol.interaction.Touch); + + +/** + * @inheritDoc + */ +ol.interaction.TouchPan.prototype.handleTouchMove = function(mapBrowserEvent) { + goog.asserts.assert(this.targetTouches.length >= 1); + var centroid = ol.interaction.Touch.centroid(this.targetTouches); + if (!goog.isNull(this.lastCentroid)) { + this.kinetic_.update(centroid.x, centroid.y); + var deltaX = this.lastCentroid.x - centroid.x; + var deltaY = centroid.y - this.lastCentroid.y; + var view = mapBrowserEvent.map.getView(); + var center = new ol.Coordinate(deltaX, deltaY) + .scale(view.getResolution()) + .rotate(view.getRotation()) + .add(view.getCenter()); + view.setCenter(center); + } + this.lastCentroid = centroid; +}; + + +/** + * @inheritDoc + */ +ol.interaction.TouchPan.prototype.handleTouchEnd = + function(mapBrowserEvent) { + var map = mapBrowserEvent.map; + var view = map.getView(); + if (this.targetTouches.length == 0) { + view.setHint(ol.ViewHint.PANNING, -1); + if (this.kinetic_.end()) { + var distance = this.kinetic_.getDistance(); + var angle = this.kinetic_.getAngle(); + var center = view.getCenter(); + this.kineticPreRenderFn_ = this.kinetic_.pan(center); + map.addPreRenderFunction(this.kineticPreRenderFn_); + var centerpx = map.getPixelFromCoordinate(center); + var destpx = new ol.Pixel( + centerpx.x - distance * Math.cos(angle), + centerpx.y - distance * Math.sin(angle)); + var dest = map.getCoordinateFromPixel(destpx); + view.setCenter(dest); + } + return false; + } else { + return true; + } +}; + + +/** + * @inheritDoc + */ +ol.interaction.TouchPan.prototype.handleTouchStart = + function(mapBrowserEvent) { + if (this.targetTouches.length >= 1) { + var map = mapBrowserEvent.map; + var view = map.getView(); + this.lastCentroid = null; + if (!goog.isNull(this.kineticPreRenderFn_) && + map.removePreRenderFunction(this.kineticPreRenderFn_)) { + map.requestRenderFrame(); + view.setCenter(mapBrowserEvent.frameState.view2DState.center); + this.kineticPreRenderFn_ = null; + } + this.kinetic_.begin(); + view.setHint(ol.ViewHint.PANNING, 1); + return true; + } else { + return false; + } +}; diff --git a/src/ol/interaction/touchrotateandzoominteraction.js b/src/ol/interaction/touchrotateandzoominteraction.js new file mode 100644 index 0000000000..d06d6ee93d --- /dev/null +++ b/src/ol/interaction/touchrotateandzoominteraction.js @@ -0,0 +1,149 @@ +// FIXME works for View2D only + +goog.provide('ol.interaction.TouchRotateAndZoom'); + +goog.require('goog.asserts'); +goog.require('ol.View'); +goog.require('ol.ViewHint'); +goog.require('ol.interaction.Touch'); + + + +/** + * @constructor + * @extends {ol.interaction.Touch} + */ +ol.interaction.TouchRotateAndZoom = function() { + + goog.base(this); + + /** + * @private + * @type {number|undefined} + */ + this.lastAngle_; + + /** + * @private + * @type {number|undefined} + */ + this.lastDistance_; + + /** + * @private + * @type {boolean} + */ + this.rotating_ = false; + + /** + * @private + * @type {number} + */ + this.rotationDelta_ = 0.0; + + /** + * @private + * @type {number} + */ + this.rotationThreshold_ = 0.3; + +}; +goog.inherits(ol.interaction.TouchRotateAndZoom, ol.interaction.Touch); + + +/** + * @inheritDoc + */ +ol.interaction.TouchRotateAndZoom.prototype.handleTouchMove = + function(mapBrowserEvent) { + goog.asserts.assert(this.targetTouches.length >= 2); + var scaleDelta = 1.0; + var rotationDelta = 0.0; + + var centroid = ol.interaction.Touch.centroid(this.targetTouches); + + var touch0 = this.targetTouches[0]; + var touch1 = this.targetTouches[1]; + var dx = touch0.clientX - touch1.clientX; + var dy = touch0.clientY - touch1.clientY; + + // angle between touches + var angle = Math.atan2( + touch1.clientY - touch0.clientY, + touch1.clientX - touch0.clientX); + + // distance between touches + var distance = Math.sqrt(dx * dx + dy * dy); + + if (goog.isDef(this.lastDistance_)) { + scaleDelta = this.lastDistance_ / distance; + } + this.lastDistance_ = distance; + + if (goog.isDef(this.lastAngle_)) { + var delta = angle - this.lastAngle_; + this.rotationDelta_ += delta; + if (!this.rotating_ && + Math.abs(this.rotationDelta_) > this.rotationThreshold_) { + this.rotating_ = true; + } + rotationDelta = delta; + } + this.lastAngle_ = angle; + + var map = mapBrowserEvent.map; + var view = map.getView(); + + // rotate / scale anchor point. + // FIXME: should be the intersection point between the lines: + // touch0,touch1 and previousTouch0,previousTouch1 + var viewportPosition = goog.style.getClientPosition(map.getViewport()); + centroid.x -= viewportPosition.x; + centroid.y -= viewportPosition.y; + var anchor = map.getCoordinateFromPixel(centroid); + + // scale, bypass the resolution constraint + view.zoom_(map, view.getResolution() * scaleDelta, anchor); + + // rotate + if (this.rotating_) { + view.rotate(map, view.getRotation() + rotationDelta, anchor); + } +}; + + +/** + * @inheritDoc + */ +ol.interaction.TouchRotateAndZoom.prototype.handleTouchEnd = + function(mapBrowserEvent) { + if (this.targetTouches.length < 2) { + var map = mapBrowserEvent.map; + var view = map.getView(); + // take the resolution constraint into account + view.zoomToResolution(map, view.getResolution()); + view.setHint(ol.ViewHint.PANNING, -1); + return false; + } else { + return true; + } +}; + + +/** + * @inheritDoc + */ +ol.interaction.TouchRotateAndZoom.prototype.handleTouchStart = + function(mapBrowserEvent) { + if (this.targetTouches.length >= 2) { + var view = mapBrowserEvent.map.getView(); + this.lastDistance_ = undefined; + this.lastAngle_ = undefined; + this.rotating_ = false; + this.rotationDelta_ = 0.0; + view.setHint(ol.ViewHint.PANNING, 1); + return true; + } else { + return false; + } +}; diff --git a/src/ol/map.js b/src/ol/map.js index 2c4ceea383..ad1fa4d8c0 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -54,6 +54,8 @@ goog.require('ol.interaction.Interaction'); goog.require('ol.interaction.KeyboardPan'); goog.require('ol.interaction.KeyboardZoom'); goog.require('ol.interaction.MouseWheelZoom'); +goog.require('ol.interaction.TouchPan'); +goog.require('ol.interaction.TouchRotateAndZoom'); goog.require('ol.interaction.condition'); goog.require('ol.layer.Layer'); goog.require('ol.renderer.Map'); @@ -959,6 +961,18 @@ ol.Map.createInteractions_ = function(mapOptions) { interactions.push(new ol.interaction.DblClickZoom(zoomDelta)); } + var touchPan = goog.isDef(mapOptions.touchPan) ? + mapOptions.touchPan : true; + if (touchPan) { + interactions.push(new ol.interaction.TouchPan()); + } + + var touchRotateZoom = goog.isDef(mapOptions.touchRotateZoom) ? + mapOptions.touchRotateZoom : true; + if (touchRotateZoom) { + interactions.push(new ol.interaction.TouchRotateAndZoom()); + } + var dragPan = goog.isDef(mapOptions.dragPan) ? mapOptions.dragPan : true; if (dragPan) { diff --git a/src/ol/mapbrowserevent.js b/src/ol/mapbrowserevent.js index 079f43b47b..792585eff0 100644 --- a/src/ol/mapbrowserevent.js +++ b/src/ol/mapbrowserevent.js @@ -3,7 +3,6 @@ goog.provide('ol.MapBrowserEvent.EventType'); goog.provide('ol.MapBrowserEventHandler'); goog.require('goog.array'); -goog.require('goog.asserts'); goog.require('goog.events.BrowserEvent'); goog.require('goog.events.EventTarget'); goog.require('goog.events.EventType'); @@ -49,6 +48,20 @@ ol.MapBrowserEvent = function(type, map, browserEvent, opt_frameState) { goog.inherits(ol.MapBrowserEvent, ol.MapEvent); +/** + * IE specific events. + * See http://msdn.microsoft.com/en-us/library/ie/hh673557(v=vs.85).aspx + * FIXME: replace with goog.events.EventType enum once we use + * goog/events/eventtype.js above r2211 + * @enum {string} + */ +ol.MapBrowserEvent.IEEventType = { + MSPOINTERDOWN: 'MSPointerDown', + MSPOINTERMOVE: 'MSPointerMove', + MSPOINTERUP: 'MSPointerUp' +}; + + /** * @return {ol.Coordinate} Coordinate. */ @@ -114,10 +127,10 @@ ol.MapBrowserEventHandler = function(map) { /** * Timestamp for the first click of a double click. Will be set back to 0 * as soon as a double click is detected. - * @type {number} + * @type {?number} * @private */ - this.timestamp_ = 0; + this.timestamp_ = null; /** * @type {?number} @@ -137,6 +150,12 @@ ol.MapBrowserEventHandler = function(map) { */ this.dragListenerKeys_ = null; + /** + * @type {Array.} + * @private + */ + this.touchListenerKeys_ = null; + /** * @type {goog.events.BrowserEvent} * @private @@ -144,38 +163,32 @@ ol.MapBrowserEventHandler = function(map) { this.down_ = null; var element = this.map_.getViewport(); - if (!ol.BrowserFeature.HAS_TOUCH) { - this.clickListenerKey_ = goog.events.listen(element, - [goog.events.EventType.CLICK, goog.events.EventType.DBLCLICK], - this.click_, false, this); - } + this.clickListenerKey_ = goog.events.listen(element, + [goog.events.EventType.CLICK, goog.events.EventType.DBLCLICK], + this.click_, false, this); this.downListenerKey_ = goog.events.listen(element, - ol.BrowserFeature.HAS_TOUCH ? - goog.events.EventType.TOUCHSTART : - goog.events.EventType.MOUSEDOWN, - this.handleDown_, false, this); + goog.events.EventType.MOUSEDOWN, + this.handleMouseDown_, false, this); + // touch events + this.touchListenerKeys_ = [ + goog.events.listen(element, [ + goog.events.EventType.TOUCHSTART, + ol.MapBrowserEvent.IEEventType.MSPOINTERDOWN + ], this.handleTouchStart_, false, this), + goog.events.listen(element, [ + goog.events.EventType.TOUCHMOVE, + ol.MapBrowserEvent.IEEventType.MSPOINTERMOVE + ], this.handleTouchMove_, false, this), + goog.events.listen(element, [ + goog.events.EventType.TOUCHEND, + ol.MapBrowserEvent.IEEventType.MSPOINTERUP + ], this.handleTouchEnd_, false, this) + ]; + }; goog.inherits(ol.MapBrowserEventHandler, goog.events.EventTarget); -/** - * @param {goog.events.BrowserEvent} browserEvent Browser event. - * @private - */ -ol.MapBrowserEventHandler.prototype.touchEnableBrowserEvent_ = - function(browserEvent) { - if (ol.BrowserFeature.HAS_TOUCH) { - goog.asserts.assert(browserEvent instanceof goog.events.BrowserEvent); - var nativeEvent = browserEvent.getBrowserEvent(); - if (nativeEvent.touches && nativeEvent.touches.length) { - var nativeTouch = nativeEvent.touches[0]; - browserEvent.clientX = nativeTouch.clientX; - browserEvent.clientY = nativeTouch.clientY; - } - } -}; - - /** * @param {goog.events.BrowserEvent} browserEvent Browser event. * @private @@ -183,15 +196,15 @@ ol.MapBrowserEventHandler.prototype.touchEnableBrowserEvent_ = ol.MapBrowserEventHandler.prototype.click_ = function(browserEvent) { if (!this.dragged_) { var newEvent; - if (browserEvent.type !== goog.events.EventType.DBLCLICK) { - newEvent = new ol.MapBrowserEvent( - ol.MapBrowserEvent.EventType.CLICK, this.map_, browserEvent); - this.dispatchEvent(newEvent); - } - if (!this.timestamp_) { + var type = browserEvent.type; + if (this.timestamp_ == 0 || type == goog.events.EventType.DBLCLICK) { newEvent = new ol.MapBrowserEvent( ol.MapBrowserEvent.EventType.DBLCLICK, this.map_, browserEvent); this.dispatchEvent(newEvent); + } else if (type == goog.events.EventType.CLICK) { + newEvent = new ol.MapBrowserEvent( + ol.MapBrowserEvent.EventType.CLICK, this.map_, browserEvent); + this.dispatchEvent(newEvent); } } }; @@ -201,19 +214,8 @@ ol.MapBrowserEventHandler.prototype.click_ = function(browserEvent) { * @param {goog.events.BrowserEvent} browserEvent Browser event. * @private */ -ol.MapBrowserEventHandler.prototype.handleUp_ = function(browserEvent) { +ol.MapBrowserEventHandler.prototype.handleMouseUp_ = function(browserEvent) { if (this.previous_) { - if (!this.dragged_) { - var now = new Date().getTime(); - if (!this.timestamp_ || now - this.timestamp_ > 250) { - this.timestamp_ = now; - } else { - this.timestamp_ = 0; - } - if (ol.BrowserFeature.HAS_TOUCH) { - this.click_(this.down_); - } - } this.down_ = null; goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey); this.dragListenerKeys_ = null; @@ -231,12 +233,11 @@ ol.MapBrowserEventHandler.prototype.handleUp_ = function(browserEvent) { * @param {goog.events.BrowserEvent} browserEvent Browser event. * @private */ -ol.MapBrowserEventHandler.prototype.handleDown_ = function(browserEvent) { +ol.MapBrowserEventHandler.prototype.handleMouseDown_ = function(browserEvent) { var newEvent = new ol.MapBrowserEvent( ol.MapBrowserEvent.EventType.DOWN, this.map_, browserEvent); this.dispatchEvent(newEvent); if (!this.previous_) { - this.touchEnableBrowserEvent_(browserEvent); this.down_ = browserEvent; this.previous_ = { clientX: browserEvent.clientX, @@ -244,21 +245,13 @@ ol.MapBrowserEventHandler.prototype.handleDown_ = function(browserEvent) { }; this.dragged_ = false; this.dragListenerKeys_ = [ - goog.events.listen(document, - ol.BrowserFeature.HAS_TOUCH ? - goog.events.EventType.TOUCHMOVE : - goog.events.EventType.MOUSEMOVE, - this.drag_, false, this), - goog.events.listen(document, - ol.BrowserFeature.HAS_TOUCH ? - goog.events.EventType.TOUCHEND : - goog.events.EventType.MOUSEUP, - this.handleUp_, false, this) + goog.events.listen(document, goog.events.EventType.MOUSEMOVE, + this.handleMouseMove_, false, this), + goog.events.listen(document, goog.events.EventType.MOUSEUP, + this.handleMouseUp_, false, this) ]; - if (browserEvent.type === goog.events.EventType.MOUSEDOWN) { - // prevent browser image dragging on pointer devices - browserEvent.preventDefault(); - } + // prevent browser image dragging with the dom renderer + browserEvent.preventDefault(); } }; @@ -267,7 +260,7 @@ ol.MapBrowserEventHandler.prototype.handleDown_ = function(browserEvent) { * @param {goog.events.BrowserEvent} browserEvent Browser event. * @private */ -ol.MapBrowserEventHandler.prototype.drag_ = function(browserEvent) { +ol.MapBrowserEventHandler.prototype.handleMouseMove_ = function(browserEvent) { var newEvent; if (!this.dragged_) { this.dragged_ = true; @@ -275,19 +268,64 @@ ol.MapBrowserEventHandler.prototype.drag_ = function(browserEvent) { ol.MapBrowserEvent.EventType.DRAGSTART, this.map_, this.down_); this.dispatchEvent(newEvent); } - this.touchEnableBrowserEvent_(browserEvent); this.previous_ = { clientX: browserEvent.clientX, clientY: browserEvent.clientY }; - // prevent viewport dragging on touch devices - browserEvent.preventDefault(); newEvent = new ol.MapBrowserEvent( ol.MapBrowserEvent.EventType.DRAG, this.map_, browserEvent); this.dispatchEvent(newEvent); }; +/** + * @param {goog.events.BrowserEvent} browserEvent Browser event. + * @private + */ +ol.MapBrowserEventHandler.prototype.handleTouchStart_ = function(browserEvent) { + // prevent context menu + browserEvent.preventDefault(); + this.down_ = browserEvent; + this.dragged_ = false; + var newEvent = new ol.MapBrowserEvent( + ol.MapBrowserEvent.EventType.TOUCHSTART, this.map_, browserEvent); + this.dispatchEvent(newEvent); +}; + + +/** + * @param {goog.events.BrowserEvent} browserEvent Browser event. + * @private + */ +ol.MapBrowserEventHandler.prototype.handleTouchMove_ = function(browserEvent) { + this.dragged_ = true; + var newEvent = new ol.MapBrowserEvent( + ol.MapBrowserEvent.EventType.TOUCHMOVE, this.map_, browserEvent); + this.dispatchEvent(newEvent); +}; + + +/** + * @param {goog.events.BrowserEvent} browserEvent Browser event. + * @private + */ +ol.MapBrowserEventHandler.prototype.handleTouchEnd_ = function(browserEvent) { + var newEvent = new ol.MapBrowserEvent( + ol.MapBrowserEvent.EventType.TOUCHEND, this.map_, browserEvent); + this.dispatchEvent(newEvent); + if (!this.dragged_) { + var now = goog.now(); + if (!this.timestamp_ || now - this.timestamp_ > 250) { + this.timestamp_ = now; + } else { + this.timestamp_ = 0; + } + this.click_(this.down_); + } + this.down_ = null; +}; + + /** * FIXME empty description for jsdoc */ @@ -298,6 +336,10 @@ ol.MapBrowserEventHandler.prototype.disposeInternal = function() { goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey); this.dragListenerKeys_ = null; } + if (!goog.isNull(this.touchListenerKeys_)) { + goog.array.forEach(this.touchListenerKeys_, goog.events.unlistenByKey); + this.touchListenerKeys_ = null; + } goog.base(this, 'disposeInternal'); }; @@ -309,8 +351,11 @@ ol.MapBrowserEventHandler.prototype.disposeInternal = function() { ol.MapBrowserEvent.EventType = { CLICK: goog.events.EventType.CLICK, DBLCLICK: goog.events.EventType.DBLCLICK, + DOWN: 'down', DRAGSTART: 'dragstart', DRAG: 'drag', DRAGEND: 'dragend', - DOWN: 'down' + TOUCHSTART: goog.events.EventType.TOUCHSTART, + TOUCHMOVE: goog.events.EventType.TOUCHMOVE, + TOUCHEND: goog.events.EventType.TOUCHEND }; diff --git a/src/ol/projection.js b/src/ol/projection.js index d6c57812a0..3e1692d01c 100644 --- a/src/ol/projection.js +++ b/src/ol/projection.js @@ -254,7 +254,6 @@ ol.Projection.createProjection = function(projection, defaultCode) { * @param {ol.TransformFunction} transformFn Transform. */ ol.Projection.addTransform = function(source, destination, transformFn) { - var projections = ol.Projection.projections_; var sourceCode = source.getCode(); var destinationCode = destination.getCode(); var transforms = ol.Projection.transforms_; @@ -267,6 +266,31 @@ ol.Projection.addTransform = function(source, destination, transformFn) { }; +/** + * Unregisters the conversion function to convert coordinates from the source + * projection to the destination projection. This method is used to clean up + * cached transforms during testing. + * + * @param {ol.Projection} source Source projection. + * @param {ol.Projection} destination Destination projection. + * @return {ol.TransformFunction} transformFn The unregistered transform. + */ +ol.Projection.removeTransform = function(source, destination) { + var sourceCode = source.getCode(); + var destinationCode = destination.getCode(); + var transforms = ol.Projection.transforms_; + goog.asserts.assert(sourceCode in transforms); + goog.asserts.assert(destinationCode in transforms[sourceCode]); + var transform = transforms[sourceCode][destinationCode]; + delete transforms[sourceCode][destinationCode]; + var keys = goog.object.getKeys(transforms[sourceCode]); + if (keys.length == 0) { + delete transforms[sourceCode]; + } + return transform; +}; + + /** * @param {string} code Code which is a combination of authority and identifier * such as “EPSG:4326”. @@ -354,7 +378,7 @@ ol.Projection.getTransform = function(source, destination) { proj4jsDestination = destination; } else { proj4jsDestination = - ol.Projection.getProj4jsProjectionFromCode_(source.getCode()); + ol.Projection.getProj4jsProjectionFromCode_(destination.getCode()); } var destinationProj4jsProj = proj4jsDestination.getProj4jsProj(); transform = diff --git a/src/ol/renderer/canvas/canvastilelayerrenderer.js b/src/ol/renderer/canvas/canvastilelayerrenderer.js index e7c48f044c..8204edcf33 100644 --- a/src/ol/renderer/canvas/canvastilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvastilelayerrenderer.js @@ -129,9 +129,9 @@ ol.renderer.canvas.TileLayer.prototype.renderFrame = var tilesToDrawByZ = {}; tilesToDrawByZ[z] = {}; - function isLoaded(tile) { + var isLoaded = function(tile) { return !goog.isNull(tile) && tile.getState() == ol.TileState.LOADED; - } + }; var findLoadedTiles = goog.bind(tileSource.findLoadedTiles, tileSource, tilesToDrawByZ, isLoaded); @@ -199,6 +199,7 @@ ol.renderer.canvas.TileLayer.prototype.renderFrame = } this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange); + tileSource.useLowResolutionTiles(z, frameState.extent); this.scheduleExpireCache(frameState, tileSource); var transform = this.transform_; diff --git a/src/ol/renderer/dom/domtilelayerrenderer.js b/src/ol/renderer/dom/domtilelayerrenderer.js index faeb67d47d..9b5dd26b32 100644 --- a/src/ol/renderer/dom/domtilelayerrenderer.js +++ b/src/ol/renderer/dom/domtilelayerrenderer.js @@ -93,9 +93,9 @@ ol.renderer.dom.TileLayer.prototype.renderFrame = var tilesToDrawByZ = {}; tilesToDrawByZ[z] = {}; - function isLoaded(tile) { + var isLoaded = function(tile) { return !goog.isNull(tile) && tile.getState() == ol.TileState.LOADED; - } + }; var findLoadedTiles = goog.bind(tileSource.findLoadedTiles, tileSource, tilesToDrawByZ, isLoaded); @@ -217,6 +217,7 @@ ol.renderer.dom.TileLayer.prototype.renderFrame = } this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange); + tileSource.useLowResolutionTiles(z, frameState.extent); this.scheduleExpireCache(frameState, tileSource); }; diff --git a/src/ol/renderer/layerrenderer.js b/src/ol/renderer/layerrenderer.js index 3379e0da5e..f1477ac8d7 100644 --- a/src/ol/renderer/layerrenderer.js +++ b/src/ol/renderer/layerrenderer.js @@ -14,7 +14,6 @@ goog.require('ol.TileState'); goog.require('ol.layer.Layer'); goog.require('ol.layer.LayerProperty'); goog.require('ol.layer.LayerState'); -goog.require('ol.source.Source'); goog.require('ol.source.TileSource'); @@ -222,24 +221,24 @@ ol.renderer.Layer.prototype.updateAttributions = /** * @protected * @param {Object.>} usedTiles Used tiles. - * @param {ol.source.Source} source Source. + * @param {ol.source.TileSource} tileSource Tile source. * @param {number} z Z. * @param {ol.TileRange} tileRange Tile range. */ ol.renderer.Layer.prototype.updateUsedTiles = - function(usedTiles, source, z, tileRange) { + function(usedTiles, tileSource, z, tileRange) { // FIXME should we use tilesToDrawByZ instead? - var sourceKey = goog.getUid(source).toString(); + var tileSourceKey = goog.getUid(tileSource).toString(); var zKey = z.toString(); - if (sourceKey in usedTiles) { - if (zKey in usedTiles[sourceKey]) { - usedTiles[sourceKey][zKey].extend(tileRange); + if (tileSourceKey in usedTiles) { + if (zKey in usedTiles[tileSourceKey]) { + usedTiles[tileSourceKey][zKey].extend(tileRange); } else { - usedTiles[sourceKey][zKey] = tileRange; + usedTiles[tileSourceKey][zKey] = tileRange; } } else { - usedTiles[sourceKey] = {}; - usedTiles[sourceKey][zKey] = tileRange; + usedTiles[tileSourceKey] = {}; + usedTiles[tileSourceKey][zKey] = tileRange; } }; @@ -247,15 +246,15 @@ ol.renderer.Layer.prototype.updateUsedTiles = /** * @protected * @param {Object.>} wantedTiles Wanted tiles. - * @param {ol.source.Source} source Source. + * @param {ol.source.TileSource} tileSource Tile source. * @param {ol.TileCoord} tileCoord Tile coordinate. */ ol.renderer.Layer.prototype.updateWantedTiles = - function(wantedTiles, source, tileCoord) { - var sourceKey = goog.getUid(source).toString(); + function(wantedTiles, tileSource, tileCoord) { + var tileSourceKey = goog.getUid(tileSource).toString(); var coordKey = tileCoord.toString(); - if (!(sourceKey in wantedTiles)) { - wantedTiles[sourceKey] = {}; + if (!(tileSourceKey in wantedTiles)) { + wantedTiles[tileSourceKey] = {}; } - wantedTiles[sourceKey][coordKey] = true; + wantedTiles[tileSourceKey][coordKey] = true; }; diff --git a/src/ol/renderer/webgl/webgltilelayerrenderer.js b/src/ol/renderer/webgl/webgltilelayerrenderer.js index 53405ca10a..9b75e0d0b1 100644 --- a/src/ol/renderer/webgl/webgltilelayerrenderer.js +++ b/src/ol/renderer/webgl/webgltilelayerrenderer.js @@ -365,10 +365,10 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = var tilesToDrawByZ = {}; tilesToDrawByZ[z] = {}; - function isLoaded(tile) { + var isLoaded = function(tile) { return !goog.isNull(tile) && tile.getState() == ol.TileState.LOADED && mapRenderer.isTileTextureLoaded(tile); - } + }; var findLoadedTiles = goog.bind(tileSource.findLoadedTiles, tileSource, tilesToDrawByZ, isLoaded); @@ -459,6 +459,7 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = } this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange); + tileSource.useLowResolutionTiles(z, frameState.extent); this.scheduleExpireCache(frameState, tileSource); goog.vec.Mat4.makeIdentity(this.texCoordMatrix_); diff --git a/src/ol/source/imagetilesource.js b/src/ol/source/imagetilesource.js index 4a59b1bcff..d47b9215e5 100644 --- a/src/ol/source/imagetilesource.js +++ b/src/ol/source/imagetilesource.js @@ -109,3 +109,14 @@ ol.source.ImageTileSource.prototype.getTile = function(tileCoord) { ol.source.ImageTileSource.prototype.getTileCoordUrl = function(tileCoord) { return this.tileUrlFunction(tileCoord); }; + + +/** + * @inheritDoc + */ +ol.source.ImageTileSource.prototype.useTile = function(tileCoord) { + var key = tileCoord.toString(); + if (this.tileCache_.containsKey(key)) { + this.tileCache_.get(key); + } +}; diff --git a/src/ol/source/singleimagewmssource.js b/src/ol/source/singleimagewmssource.js index 3529678443..84e66db61f 100644 --- a/src/ol/source/singleimagewmssource.js +++ b/src/ol/source/singleimagewmssource.js @@ -1,5 +1,6 @@ goog.provide('ol.source.SingleImageWMS'); +goog.require('goog.uri.utils'); goog.require('ol.Extent'); goog.require('ol.Image'); goog.require('ol.ImageUrlFunction'); diff --git a/src/ol/source/tiledwmssource.js b/src/ol/source/tiledwmssource.js index b09147cf2c..a1093f21e4 100644 --- a/src/ol/source/tiledwmssource.js +++ b/src/ol/source/tiledwmssource.js @@ -69,7 +69,7 @@ ol.source.TiledWMS = function(tiledWMSOptions) { tileUrlFunction = ol.TileUrlFunction.nullTileUrlFunction; } - function tileCoordTransform(tileCoord) { + var tileCoordTransform = function(tileCoord) { if (tileGrid.getResolutions().length <= tileCoord.z) { return null; } @@ -90,7 +90,7 @@ ol.source.TiledWMS = function(tiledWMSOptions) { return null; } return new ol.TileCoord(tileCoord.z, x, tileCoord.y); - } + }; goog.base(this, { attributions: tiledWMSOptions.attributions, diff --git a/src/ol/source/tilesource.js b/src/ol/source/tilesource.js index e9666dada6..c250951e7e 100644 --- a/src/ol/source/tilesource.js +++ b/src/ol/source/tilesource.js @@ -119,3 +119,29 @@ ol.source.TileSource.prototype.getTile = goog.abstractMethod; ol.source.TileSource.prototype.getTileGrid = function() { return this.tileGrid; }; + + +/** + * @param {number} z Z. + * @param {ol.Extent} extent Extent. + */ +ol.source.TileSource.prototype.useLowResolutionTiles = function(z, extent) { + var tileGrid = this.getTileGrid(); + var tileRange, x, y, zKey; + // FIXME this should loop up to tileGrid's minZ when implemented + for (; z >= 0; --z) { + tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z); + for (x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (y = tileRange.minY; y <= tileRange.maxY; ++y) { + this.useTile(z + '/' + x + '/' + y); + } + } + } +}; + + +/** + * Marks a tile coord as being used, without triggering a load. + * @param {string} tileCoordKey Tile coordinate key. + */ +ol.source.TileSource.prototype.useTile = goog.nullFunction; diff --git a/test/spec/ol/map.test.js b/test/spec/ol/map.test.js index e859e7e8e8..db5ed7f546 100644 --- a/test/spec/ol/map.test.js +++ b/test/spec/ol/map.test.js @@ -77,7 +77,9 @@ describe('ol.Map', function() { dragPan: false, keyboard: false, mouseWheelZoom: false, - shiftDragZoom: false + shiftDragZoom: false, + touchPan: false, + touchRotateZoom: false }; }); diff --git a/test/spec/ol/projection.test.js b/test/spec/ol/projection.test.js index eb1c9b4787..1ee2c4d91c 100644 --- a/test/spec/ol/projection.test.js +++ b/test/spec/ol/projection.test.js @@ -2,6 +2,27 @@ goog.provide('ol.test.Projection'); describe('ol.Projection', function() { + beforeEach(function() { + spyOn(ol.Projection, 'addTransform').andCallThrough(); + }); + + afterEach(function() { + var argsForCall = ol.Projection.addTransform.argsForCall; + for (var i = 0, ii = argsForCall.length; i < ii; ++i) { + try { + ol.Projection.removeTransform.apply( + ol.Projection, argsForCall[i].splice(0, 2)); + } catch (error) { + if (error instanceof goog.asserts.AssertionError) { + // The removeTransform function may have been called explicitly by the + // tests, so we pass. + } else { + throw error; + } + } + } + }); + describe('projection equivalence', function() { function _testAllEquivalent(codes) { @@ -23,14 +44,13 @@ describe('ol.Projection', function() { }); it('gives that CRS:84, urn:ogc:def:crs:EPSG:6.6:4326, EPSG:4326 are ' + - 'equivalent', function() { - _testAllEquivalent([ - 'CRS:84', - 'urn:ogc:def:crs:EPSG:6.6:4326', - 'EPSG:4326' - ]); - }); - + 'equivalent', function() { + _testAllEquivalent([ + 'CRS:84', + 'urn:ogc:def:crs:EPSG:6.6:4326', + 'EPSG:4326' + ]); + }); }); describe('identify transform', function() { @@ -126,8 +146,72 @@ describe('ol.Projection', function() { }); + describe('ol.Projection.getTransform()', function() { + + var sm = ol.Projection.getFromCode('GOOGLE'); + var gg = ol.Projection.getFromCode('EPSG:4326'); + + it('returns a transform function', function() { + var transform = ol.Projection.getTransform(sm, gg); + expect(typeof transform).toBe('function'); + + var coordinate = transform(new ol.Coordinate(-12000000, 5000000)); + + expect(coordinate.x).toRoughlyEqual(-107.79783409434258, 1e-9); + expect(coordinate.y).toRoughlyEqual(40.91627447067577, 1e-9); + }); + + }); + + + describe('ol.Projection.getTransformFromCodes()', function() { + + it('returns a function', function() { + var transform = ol.Projection.getTransformFromCodes( + 'GOOGLE', 'EPSG:4326'); + expect(typeof transform).toBe('function'); + }); + + it('returns a transform function', function() { + var transform = ol.Projection.getTransformFromCodes( + 'GOOGLE', 'EPSG:4326'); + expect(typeof transform).toBe('function'); + + var coordinate = transform( + new ol.Coordinate(-626172.13571216376, 6887893.4928337997)); + + expect(coordinate.x).toRoughlyEqual(-5.625, 1e-9); + expect(coordinate.y).toRoughlyEqual(52.4827802220782, 1e-9); + + }); + + }); + + describe('ol.Projection.removeTransform()', function() { + + var extent = new ol.Extent(-180, -90, 180, 90); + var units = ol.ProjectionUnits.DEGREES; + + it('removes functions cached by addTransform', function() { + var foo = new ol.Projection('foo', units, extent); + var bar = new ol.Projection('bar', units, extent); + var transform = function() {}; + ol.Projection.addTransform(foo, bar, transform); + expect(ol.Projection.transforms_).not.toBeUndefined(); + expect(ol.Projection.transforms_.foo).not.toBeUndefined(); + expect(ol.Projection.transforms_.foo.bar).toBe(transform); + + var removed = ol.Projection.removeTransform(foo, bar); + expect(removed).toBe(transform); + expect(ol.Projection.transforms_.foo).toBeUndefined(); + }); + + }); + }); goog.require('goog.array'); goog.require('ol.Coordinate'); +goog.require('ol.Extent'); goog.require('ol.Projection'); +goog.require('ol.ProjectionUnits');