diff --git a/demo/loader.js b/demo/loader.js index 0b9230f5a5..8b01bc2322 100644 --- a/demo/loader.js +++ b/demo/loader.js @@ -1,36 +1,7 @@ /** - Adds the plovr generated script to the document. The following default - values may be overridden with query string parameters: - - * hostname - localhost - * port - 9810 - * mode - SIMPLE - * id - ol + Adds the plovr generated script to the document. */ (function() { - var search = window.location.search.substring(1); - var params = { - hostname: "localhost", - port: "9810", - mode: "SIMPLE", - id: "ol" - }; - var chunks = search.split("&"); - var pair; - for (var i=chunks.length-1; i>=0; --i) { - pair = chunks[i].split("="); - params[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]); - } - - var host = params.hostname + ":" + params.port; - delete params.hostname; - delete params.port; - - var pairs = []; - for (var key in params) { - pairs.push(encodeURIComponent(key) + "=" + encodeURIComponent(params[key])); - } - - var url = "http://" + host + "/compile?" + pairs.join("&"); + var url = "http://" + window.location.hostname + ":9810/compile?id=ol"; document.write(""); })(); diff --git a/src/ol/Map.js b/src/ol/Map.js index a90674e76f..64fe7cea0e 100644 --- a/src/ol/Map.js +++ b/src/ol/Map.js @@ -90,6 +90,12 @@ ol.Map = function() { * @type {Element} */ this.viewport_ = null; + + /** + * @private + * @type {goog.math.Size} + */ + this.viewportSize_ = null; /** * @private @@ -266,34 +272,60 @@ ol.Map.prototype.getResolutionForZoom = function(zoom) { } }; +/** + * @return {number} the resolution for the map at the given zoom level + */ +ol.Map.prototype.getResolution = function() { + goog.asserts.assert(goog.isDef(this.renderer_)); + return this.renderer_.getResolution(); +}; + /** + * TODO: We'll have to ask the map overlay renderer for this. This method will + * not work once map space is not aligned with pixel space. + * * @param {goog.math.Coordinate|{x: number, y: number}} pixel * @return {ol.Loc} */ -ol.Map.prototype.getLocForPixel = function(pixel) { - return goog.isDef(this.renderer_) ? - this.renderer_.getLocForPixel(pixel) : null; +ol.Map.prototype.getLocForViewportPixel = function(pixel) { + var size = this.getViewportSize(); + var center = this.center_; + var resolution = this.getResolution(); + var x = center.getX() + (resolution * (pixel.x - (size.width / 2))); + var y = center.getY() - (resolution * (pixel.y - (size.height / 2))); + return new ol.Loc(x, y, undefined, this.getProjection()); }; /** + * TODO: We'll have to ask the map overlay renderer for this. This method will + * not work once map space is not aligned with pixel space. + * * @param {ol.Loc} loc * @return {{x: number, y: number}} */ -ol.Map.prototype.getPixelForLoc = function(loc) { - return goog.isDef(this.renderer_) ? - this.renderer_.getPixelForLoc(loc) : null; +ol.Map.prototype.getViewportPixelForLoc = function(loc) { + var size = this.getViewportSize(); + var center = this.center_; + var resolution = this.getResolution(); + return { + x: ((loc.getX() - center.getX()) / resolution) + (size.width / 2), + y: ((center.getY() - loc.getY()) / resolution) + (size.height / 2) + }; }; /** - * @return {goog.math.Size} The currently rendered map size in pixels. + * @return {goog.math.Size} */ -ol.Map.prototype.getSize = function() { - //TODO consider caching this when we have something like updateSize - return goog.isDef(this.renderer_) ? this.renderer_.getSize() : null; +ol.Map.prototype.getViewportSize = function() { + // TODO: listen for resize and set this.viewportSize_ null + // https://github.com/openlayers/ol3/issues/2 + if (goog.isNull(this.viewportSize_)) { + this.viewportSize_ = goog.style.getSize(this.viewport_); + } + return this.viewportSize_; }; - /** * @param {ol.Loc} center Center in map projection. */ @@ -329,8 +361,8 @@ ol.Map.prototype.setZoom = function(zoom, opt_anchor) { return; } if (goog.isDef(opt_anchor)) { - var size = this.getSize(), - anchorLoc = this.getLocForPixel(opt_anchor), + var size = this.getViewportSize(), + anchorLoc = this.getLocForViewportPixel(opt_anchor), newRes = this.getResolutionForZoom(newZoom); newCenter = anchorLoc.add( (size.width/2 - opt_anchor.x) * newRes, @@ -491,7 +523,7 @@ ol.Map.prototype.createRenderer = function() { this.mapOverlay_ = goog.dom.createDom('div', 'ol-overlay-map'); this.staticOverlay_ = goog.dom.createDom('div', { 'class': staticCls, - 'style': 'width:100%;height:100%;top:0;left:0;position:absolute' + 'style': 'width:100%;height:100%;top:0;left:0;position:absolute;z-index:1' }); } if (!goog.isNull(viewport)) { @@ -500,10 +532,13 @@ ol.Map.prototype.createRenderer = function() { }; /** + * TODO: This method will need to be reworked/revisited when renderers can + * display a map that is rotated or otherwise not aligned with pixel space. + * * @param {number} dx pixels to move in x direction * @param {number} dy pixels to move in x direction */ -ol.Map.prototype.moveByPx = function(dx, dy) { +ol.Map.prototype.moveByViewportPx = function(dx, dy) { if (!goog.isNull(this.center_) && goog.isDef(this.zoom_)) { var resolution = this.getResolutionForZoom(this.zoom_), center = new ol.Loc( diff --git a/src/ol/Popup.js b/src/ol/Popup.js index 8ec7018aee..5a5beff366 100644 --- a/src/ol/Popup.js +++ b/src/ol/Popup.js @@ -243,7 +243,7 @@ ol.Popup.prototype.setAnchorOffset_ = function() { this.pos_ = new ol.geom.Point(this.anchor_.getX(), this.anchor_.getY()); } var pos = /** @type {ol.Loc} */ (this.pos_); - var popupPosPx = this.map_.getPixelForLoc(pos); + var popupPosPx = this.map_.getViewportPixelForLoc(pos); var popupSize = goog.style.getSize(this.container_); switch(this.placement_) { diff --git a/src/ol/control/Attribution.js b/src/ol/control/Attribution.js index 0c03182366..35875a3de4 100644 --- a/src/ol/control/Attribution.js +++ b/src/ol/control/Attribution.js @@ -43,9 +43,7 @@ ol.control.Attribution.prototype.setMap = function(map) { var staticOverlay = map.getStaticOverlay(); if (goog.isNull(this.container_)) { this.container_ = goog.dom.createDom('div', this.CLS); - // This is not registered as priority listener, so priority listeners - // can still get the click event. - map.getEvents().register('click', this.stopLinkClick, this); + goog.events.listen(this.container_, 'click', ol.event.stopPropagation); } if (!goog.isNull(staticOverlay)) { goog.dom.append(staticOverlay, this.container_); @@ -53,16 +51,6 @@ ol.control.Attribution.prototype.setMap = function(map) { goog.base(this, 'setMap', map); }; -/** - * Prevent clicks on links in the attribution from getting through to - * listeners. - */ -ol.control.Attribution.prototype.stopLinkClick = function(evt) { - var node = evt.target; - return node.nodeName !== 'A' || - !goog.dom.getAncestorByClass(node, this.CLS); -}; - /** @inheritDoc */ ol.control.Attribution.prototype.activate = function() { var active = goog.base(this, 'activate'); @@ -96,6 +84,7 @@ ol.control.Attribution.prototype.update = function() { }; ol.control.Attribution.prototype.destroy = function() { + goog.events.unlisten(this.container_, 'click', ol.event.stopPropagation); goog.dom.removeNode(this.container_); goog.base(this, 'destroy'); }; diff --git a/src/ol/control/Navigation.js b/src/ol/control/Navigation.js index 2a3eb0b070..64ac0737cf 100644 --- a/src/ol/control/Navigation.js +++ b/src/ol/control/Navigation.js @@ -20,6 +20,12 @@ ol.control.Navigation = function(opt_autoActivate) { this.autoActivate_ = goog.isDef(opt_autoActivate) ? opt_autoActivate : true; + /** + * @type {number?} + * @private + */ + this.zoomBlocked_ = null; + }; goog.inherits(ol.control.Navigation, ol.control.Control); @@ -52,7 +58,7 @@ ol.control.Navigation.prototype.deactivate = function() { * @param {Object} evt */ ol.control.Navigation.prototype.moveMap = function(evt) { - this.map_.moveByPx(evt.deltaX, evt.deltaY); + this.map_.moveByViewportPx(evt.deltaX, evt.deltaY); return false; }; @@ -60,12 +66,17 @@ ol.control.Navigation.prototype.moveMap = function(evt) { * @param {Event} evt */ ol.control.Navigation.prototype.zoomMap = function(evt) { - var map = this.map_, - delta = ((evt.deltaY / 3) | 0); - if (Math.abs(delta) === 0) { + var me = this; + if (evt.deltaY === 0 || me.zoomBlocked_) { return; } - map.setZoom(map.getZoom()-delta, map.getEvents().getPointerPosition(evt)); + me.zoomBlocked_ = window.setTimeout(function() { + me.zoomBlocked_ = null; + }, 200); + + var map = me.map_, + step = evt.deltaY / Math.abs(evt.deltaY); + map.setZoom(map.getZoom()-step, map.getEvents().getPointerPosition(evt)); // We don't want the page to scroll. evt.preventDefault(); return false; diff --git a/src/ol/control/Zoom.js b/src/ol/control/Zoom.js index 07010a492a..a25cbac6d7 100644 --- a/src/ol/control/Zoom.js +++ b/src/ol/control/Zoom.js @@ -12,13 +12,23 @@ goog.require('goog.dom'); * @param {boolean|undefined} opt_autoActivate */ ol.control.Zoom = function(opt_autoActivate) { - + goog.base(this, opt_autoActivate); + /** + * @type {Node} + */ + this.inButton_ = null; + + /** + * @type {Node} + */ + this.outButton_ = null; + /** * Activate this control when it is added to a map. Default is true. * - * @type {boolean} autoActivate + * @type {boolean} */ this.autoActivate_ = goog.isDef(opt_autoActivate) ? opt_autoActivate : true; @@ -30,38 +40,38 @@ goog.inherits(ol.control.Zoom, ol.control.Control); * @param {ol.Map} map */ ol.control.Zoom.prototype.setMap = function(map) { - goog.base(this, 'setMap', map); var container = goog.dom.createDom('div', ol.control.Zoom.RES.CLS); - var inButton = goog.dom.createDom( + this.inButton_ = goog.dom.createDom( 'div', ol.control.Zoom.RES.IN_CLS, goog.dom.createDom('a', {'href': '#zoomIn'}) ); goog.dom.setTextContent( - /** @type {Element} */ (inButton.firstChild), + /** @type {Element} */ (this.inButton_.firstChild), ol.control.Zoom.RES.IN_TEXT ); - var outButton = goog.dom.createDom( + this.outButton_ = goog.dom.createDom( 'div', ol.control.Zoom.RES.OUT_CLS, goog.dom.createDom('a', {'href': '#zoomOut'}) ); goog.dom.setTextContent( - /** @type {Element} */ (outButton.firstChild), + /** @type {Element} */ (this.outButton_.firstChild), ol.control.Zoom.RES.OUT_TEXT ); - goog.dom.append(container, inButton, outButton); + goog.dom.append(container, this.inButton_, this.outButton_); var overlay = map.getStaticOverlay(); if (goog.isDefAndNotNull(overlay)) { goog.dom.append(overlay, container); } + goog.base(this, 'setMap', map); }; /** @inheritDoc */ ol.control.Zoom.prototype.activate = function() { var active = goog.base(this, 'activate'); if (active) { - this.map_.getEvents().register('click', this.handle, this); - this.map_.getEvents().register('keypress', this.handle, this); + goog.events.listen(this.inButton_, 'click', this.handleIn, false, this); + goog.events.listen(this.outButton_, 'click', this.handleOut, false, this); } return active; }; @@ -70,37 +80,28 @@ ol.control.Zoom.prototype.activate = function() { ol.control.Zoom.prototype.deactivate = function() { var inactive = goog.base(this, 'deactivate'); if (inactive) { - this.map_.getEvents().unregister('click', this.handle, this); - this.map_.getEvents().unregister('keypress', this.handle, this); + goog.events.unlisten(this.inButton_, 'click', this.handleIn, false, this); + goog.events.unlisten(this.outButton_, 'click', this.handleOut, false, this); } return inactive; }; /** * @param {Event} evt - * @return {boolean} */ -ol.control.Zoom.prototype.handle = function(evt) { - var target = /** @type {Node} */ (evt.target), - handled = false; - if (evt.type === 'click' || ol.event.isEnterOrSpace(evt)) { - if (goog.dom.getAncestorByClass(target, ol.control.Zoom.RES.IN_CLS)) { - this.map_.zoomIn(); - handled = true; - } else - if (goog.dom.getAncestorByClass(target, ol.control.Zoom.RES.OUT_CLS)) { - this.map_.zoomOut(); - handled = true; - } - if (handled) { - // Stop default behavior (i.e. don't follow link to anchor) - evt.preventDefault(); - // On Android 2.3, the above does not prevent the link from being - // followed, but stopPropagation does. - evt.stopPropagation(); - } - } - return !handled; +ol.control.Zoom.prototype.handleIn = function(evt) { + this.map_.zoomIn(); + evt.preventDefault(); + evt.stopPropagation(); +}; + +/** + * @param {Event} evt + */ +ol.control.Zoom.prototype.handleOut = function(evt) { + this.map_.zoomOut(); + evt.preventDefault(); + evt.stopPropagation(); }; ol.control.Zoom.prototype.destroy = function() { diff --git a/src/ol/event/Drag.js b/src/ol/event/Drag.js new file mode 100644 index 0000000000..af1eee8fe3 --- /dev/null +++ b/src/ol/event/Drag.js @@ -0,0 +1,95 @@ +goog.provide('ol.event.Drag'); + +goog.require('ol.event'); +goog.require('ol.event.ISequence'); + +goog.require('goog.functions'); +goog.require('goog.fx.Dragger'); +goog.require('goog.fx.DragEvent'); +goog.require('goog.fx.Dragger.EventType'); + + +/** + * Event sequence that provides a 'dragstart', 'drag' and 'dragend' events. + * Event objects of the 'drag' events have 'deltaX' and 'deltaY' values with + * the relative pixel movement since the previous 'drag' or 'dragstart' event. + * + * @constructor + * @param {ol.event.Events} target The Events instance that handles events. + * @implements {ol.event.ISequence} + * @export + */ +ol.event.Drag = function(target) { + var previousX = 0, previousY = 0, + element = target.getElement(), + dragger = new goog.fx.Dragger(element); + + /** + * @private + * @type {goog.fx.Dragger} + */ + this.dragger_ = dragger; + + /** + * @private + * @type {ol.event.Events} + */ + this.target_ = target; + + // We want to swallow the click event that gets fired after dragging. + var newSequence; + function unregisterClickStopper() { + target.unregister('click', goog.functions.FALSE, undefined, true); + } + + // no default for mousemove and touchmove events to avoid page scrolling. + target.register('mousemove', ol.event.preventDefault); + target.register('touchmove', ol.event.preventDefault); + + dragger.defaultAction = function(x, y) {}; + dragger.addEventListener(goog.fx.Dragger.EventType.START, function(evt) { + evt.target = element; + evt.type = 'dragstart'; + previousX = evt.clientX; + previousY = evt.clientY; + newSequence = true; + target.triggerEvent(evt.type, evt); + }); + dragger.addEventListener(goog.fx.Dragger.EventType.DRAG, function(evt) { + evt.target = element; + evt.deltaX = evt.clientX - previousX; + evt.deltaY = evt.clientY - previousY; + previousX = evt.clientX; + previousY = evt.clientY; + if (newSequence) { + // Once we are in the drag sequence, we know that we need to + // get rid of the click event that gets fired when we are done + // dragging. + target.register('click', goog.functions.FALSE, undefined, true); + newSequence = false; + } + target.triggerEvent(evt.type, evt); + }); + dragger.addEventListener(goog.fx.Dragger.EventType.END, function(evt) { + evt.target = element; + evt.type = 'dragend'; + target.triggerEvent(evt.type, evt); + // Unregister the click stopper in the next cycle + window.setTimeout(unregisterClickStopper, 0); + }); + // Don't swallow the click event if our sequence cancels early. + dragger.addEventListener( + goog.fx.Dragger.EventType.EARLY_CANCEL, unregisterClickStopper + ); +}; + +/** @inheritDoc */ +ol.event.Drag.prototype.destroy = function() { + this.target_.unregister('mousemove', ol.event.preventDefault); + this.target_.unregister('touchmove', ol.event.preventDefault); + this.dragger_.dispose(); + goog.object.clear(this); +}; + + +ol.event.addSequenceProvider('drag', ol.event.Drag); \ No newline at end of file diff --git a/src/ol/event/Events.js b/src/ol/event/Events.js index fe65d8b363..3ad7969228 100644 --- a/src/ol/event/Events.js +++ b/src/ol/event/Events.js @@ -42,16 +42,21 @@ ol.event.isMultiTouch = function(evt) { }; /** - * Is the event a keyboard event with Enter or Space pressed? + * Call preventDefault on the provided event. * * @param {!Event} evt - * @return {boolean} */ -ol.event.isEnterOrSpace = function(evt) { - return evt.type === "keypress" && - (evt.keyCode === goog.events.KeyCodes.ENTER || - evt.keyCode === goog.events.KeyCodes.SPACE || - evt.keyCode === goog.events.KeyCodes.MAC_ENTER); +ol.event.preventDefault = function(evt) { + evt.preventDefault(); +}; + +/** + * Call stopPropagation on the provided event. + * + * @param {!Event} evt + */ +ol.event.stopPropagation = function(evt) { + evt.stopPropagation(); }; @@ -155,7 +160,7 @@ ol.event.Events.prototype.setElement = function(element) { if (this.element_) { for (t in types) { goog.events.unlisten( - this.element_, types[t], this.handleBrowserEvent, true, this + this.element_, types[t], this.handleBrowserEvent, false, this ); } this.destroySequences(); @@ -166,7 +171,7 @@ ol.event.Events.prototype.setElement = function(element) { this.createSequences(); for (t in types) { goog.events.listen( - element, types[t], this.handleBrowserEvent, true, this + element, types[t], this.handleBrowserEvent, false, this ); } } diff --git a/src/ol/renderer/Composite.js b/src/ol/renderer/Composite.js index 1857e3dc42..62105865bc 100644 --- a/src/ol/renderer/Composite.js +++ b/src/ol/renderer/Composite.js @@ -4,7 +4,11 @@ goog.require('ol.renderer.MapRenderer'); goog.require('ol.renderer.LayerRenderer'); goog.require('ol.layer.Layer'); goog.require('ol.Loc'); + goog.require('goog.array'); +goog.require('goog.dom'); +goog.require('goog.style'); +goog.require('goog.math.Coordinate'); /** * @constructor @@ -21,21 +25,43 @@ ol.renderer.Composite = function(container) { */ this.renderers_ = []; - var target = document.createElement("div"); - target.className = "ol-renderer-composite"; - target.style.position = "absolute"; - target.style.height = "100%"; - target.style.width = "100%"; - container.appendChild(target); - /** - * @type Element + * Pixel buffer for renderer container. + * + * @type {number} * @private */ - this.target_ = target; + this.buffer_ = 128; + + /** + * @type {Element} + * @private + */ + this.target_ = null; + + /** + * The current top left corner location of the target element (map coords). + * + * @type {ol.Loc} + * @private + */ + this.targetOrigin_ = null; + + /** + * The pixel offset of the target element with respect to its container. + * + * @type {goog.math.Coordinate} + * @private + */ + this.targetOffset_ = null; + + /** + * @type {Object} + * @private + */ + this.layerContainers_ = {}; }; - goog.inherits(ol.renderer.Composite, ol.renderer.MapRenderer); /** @@ -45,22 +71,129 @@ goog.inherits(ol.renderer.Composite, ol.renderer.MapRenderer); * @param {boolean} animate */ ol.renderer.Composite.prototype.draw = function(layers, center, resolution, animate) { + if (goog.isNull(this.target_)) { + // first rendering + this.createTarget_(center, resolution); + } + // TODO: deal with layer order and removal - for (var i=0, ii=layers.length; i} layers * @param {ol.Loc} center @@ -37,6 +58,13 @@ ol.renderer.MapRenderer = function(container) { ol.renderer.MapRenderer.prototype.draw = function(layers, center, resolution, animate) { }; +/** + * @return {number} The rendered resolution. + */ +ol.renderer.MapRenderer.prototype.getResolution = function() { + return this.renderedResolution_; +}; + /** * TODO: determine a closure friendly way to register map renderers. * @type {Array} @@ -80,38 +108,3 @@ ol.renderer.MapRenderer.pickRendererType = function(preferences) { // if we didn't find any of the preferred renderers, use the first return Renderer || Candidates[0] || null; }; - -/** - * @param {goog.math.Coordinate|{x: number, y: number}} pixel - * @return {ol.Loc} - */ -ol.renderer.MapRenderer.prototype.getLocForPixel = function(pixel) { - var center = this.renderedCenter_, - resolution = this.renderedResolution_, - size = goog.style.getSize(this.container_); - return center.add( - (pixel.x - size.width/2) * resolution, - (size.height/2 - pixel.y) * resolution - ); -}; - -/** - * @param {ol.Loc} loc - * @return {{x: number, y: number}} - */ -ol.renderer.MapRenderer.prototype.getPixelForLoc = function(loc) { - var center = this.renderedCenter_, - resolution = this.renderedResolution_, - size = this.getSize(); - return { - x: (size.width*resolution/2 + loc.getX() - center.getX())/resolution, - y: (size.height*resolution/2 - loc.getY() + center.getY())/resolution - }; -}; - -/** - * @return {goog.math.Size} The currently rendered map size in pixels. - */ -ol.renderer.MapRenderer.prototype.getSize = function() { - return goog.style.getSize(this.container_); -}; diff --git a/src/ol/renderer/TileLayerRenderer.js b/src/ol/renderer/TileLayerRenderer.js index 0a01910432..219341c7bc 100644 --- a/src/ol/renderer/TileLayerRenderer.js +++ b/src/ol/renderer/TileLayerRenderer.js @@ -7,6 +7,7 @@ goog.require('ol.TileSet'); goog.require('ol.Bounds'); goog.require('goog.style'); +goog.require('goog.math.Box'); /** @@ -37,14 +38,14 @@ ol.renderer.TileLayerRenderer = function(container, layer) { this.tileSize_ = layer.getTileSize(); /** - * @type {number} + * @type {boolean} */ - this.xRight_ = layer.getXRight() ? 1 : -1; + this.xRight_ = layer.getXRight(); /** - * @type {number} + * @type {boolean} */ - this.yDown_ = layer.getYDown() ? 1 : -1; + this.yDown_ = layer.getYDown(); /** * @type {number|undefined} @@ -52,86 +53,15 @@ ol.renderer.TileLayerRenderer = function(container, layer) { */ this.renderedResolution_ = undefined; - /** - * @type {number|undefined} - * @private - */ - this.renderedTop_ = undefined; - - /** - * @type {number|undefined} - * @private - */ - this.renderedRight_ = undefined; - - /** - * @type {number|undefined} - * @private - */ - this.renderedBottom_ = undefined; - - /** - * @type {number|undefined} - * @private - */ - this.renderedLeft_ = undefined; - /** * @type {number|undefined} * @private */ this.renderedZ_ = undefined; - /** - * @type {goog.math.Size} - * @private - */ - this.containerSize_ = null; - }; - goog.inherits(ol.renderer.TileLayerRenderer, ol.renderer.LayerRenderer); -/** - * @param {number} resolution - * @return {Array.} - */ -ol.renderer.TileLayerRenderer.prototype.getPreferredResAndZ_ = function(resolution) { - var minDiff = Number.POSITIVE_INFINITY; - var candidate, diff, z, r; - for (var i=0, ii=this.layerResolutions_.length; i 0) { - pxMinX = Math.round(leftTileX * pxTileWidth) - pxOffsetX; - } else { - pxMinX = Math.round((-leftTileX-1) * pxTileWidth) - pxOffsetX; - } - var pxMinY; - if (yDown > 0) { - pxMinY = Math.round(topTileY * pxTileHeight) - pxOffsetY; - } else { - pxMinY = Math.round((-topTileY-1) * pxTileHeight) - pxOffsetY; - } - - var pxTileLeft = pxMinX; - var pxTileTop = pxMinY; - - var numTilesWide = Math.ceil(pxMapSize.width / pxTileWidth); - var numTilesHigh = Math.ceil(pxMapSize.height / pxTileHeight); - - // assume a buffer of zero for now - if (pxMinX < 0) { - numTilesWide += 1; - } - if (pxMinY < 0) { - numTilesHigh += 1; - } - - var tileX, tileY, tile, img, pxTileRight, pxTileBottom, xyz, append; var fragment = document.createDocumentFragment(); - var newTiles = false; - for (var i=0; i} ijz + * @param {number} resolution + * @return {goog.math.Box} + */ +ol.renderer.TileLayerRenderer.prototype.getTilePixelBox_ = function(ijz, resolution) { + var tileResolution = this.layerResolutions_[ijz[2]]; + var scale = resolution / tileResolution; + var tileSize = this.tileSize_; + + // desired tile size (in fractional pixels) + var fpxTileWidth = tileSize[0] / scale; + var fpxTileHeight = tileSize[1] / scale; + + var col = ijz[0]; + var left = Math.round(col * fpxTileWidth); // inclusive + var right = Math.round((col + 1) * fpxTileWidth); // exclusive + + var row = ijz[1]; + var top = Math.round(row * fpxTileHeight); // inclusive + var bottom = Math.round((row + 1) * fpxTileWidth); // exclusive + + return new goog.math.Box(top, right, bottom, left); +}; + +/** + * @param {ol.Loc} loc + * @param {number} resolution + * @return {goog.math.Coordinate} + */ +ol.renderer.TileLayerRenderer.prototype.getNormalizedTileCoord_ = function(loc, resolution) { + var tileOrigin = this.tileOrigin_; + var tileSize = this.tileSize_; + var pair = this.getPreferredResAndZ_(resolution); + var tileResolution = pair[0]; + var z = pair[1]; + var scale = resolution / tileResolution; + + // offset from tile origin in pixel space + var dx = Math.round((loc.getX() - tileOrigin[0]) / resolution); + var dy = Math.round((tileOrigin[1] - loc.getY()) / resolution); + + // desired tile size (in fractional pixels) + var fpxTileWidth = tileSize[0] / scale; + var fpxTileHeight = tileSize[1] / scale; + + // determine normalized col number (0 based, ascending right) + var col = Math.floor(dx / fpxTileWidth); + // determine normalized row number (0 based, ascending down) + var row = Math.floor(dy / fpxTileHeight); + + var box = this.getTilePixelBox_([col, row, z], resolution); + + // adjust col to allow for stretched tiles + if (dx < box.left) { + col -= 1; + } else if (dx >= box.right) { + col += 1; + } + + // adjust row to allow for stretched tiles + if (dy < box.top) { + row -= 1; + } else if (dy >= box.bottom) { + row += 1; + } + + return new goog.math.Coordinate(col, row); +}; + +/** + * @param {number} resolution + * @return {Array.} + */ +ol.renderer.TileLayerRenderer.prototype.getPreferredResAndZ_ = (function() { + var cache = {}; + return function(resolution) { + if (resolution in cache) { + return cache[resolution]; + } + var minDiff = Number.POSITIVE_INFINITY; + var candidate, diff, z, r; + for (var i=0, ii=this.layerResolutions_.length; i} ijz + * @return {Array.} + */ +ol.renderer.TileLayerRenderer.prototype.getTileCoordsFromNormalizedCoords_ = function(ijz) { + return [ + this.xRight_ ? ijz[0] : -ijz[0] - 1, + this.yDown_ ? ijz[1] : -ijz[1] - 1, + ijz[2] + ]; +}; + +/** + * @param {ol.Loc} center + * @param {number} resolution + * @return {goog.math.Box} + */ +ol.renderer.TileLayerRenderer.prototype.getTileBox_ = function(center, resolution) { + var size = this.getContainerSize(); + var halfWidth = size.width / 2; + var halfHeight = size.height / 2; + + var leftTop = new ol.Loc( + center.getX() - (resolution * halfWidth), + center.getY() + (resolution * halfHeight)); + + var rightBottom = new ol.Loc( + center.getX() + (resolution * halfWidth), + center.getY() - (resolution * halfHeight)); + + var ltCoord = this.getNormalizedTileCoord_(leftTop, resolution); + var rbCoord = this.getNormalizedTileCoord_(rightBottom, resolution); + + // right and bottom are treated as excluded, so we increment for the box + rbCoord.x += 1; + rbCoord.y += 1; + + return goog.math.Box.boundingBox(ltCoord, rbCoord); +}; /** * Get rid of tiles outside the rendered extent. */ ol.renderer.TileLayerRenderer.prototype.removeInvisibleTiles_ = function() { - var index, prune, x, y, z, tile; - var xRight = (this.xRight_ > 0); - var yDown = (this.yDown_ > 0); - var top = this.renderedTop_; - var right = this.renderedRight_; - var bottom = this.renderedBottom_; - var left = this.renderedLeft_; - for (var xyz in this.renderedTiles_) { - index = xyz.split(","); - x = +index[0]; - y = +index[1]; + var index, prune, i, j, z, tile; + var box = this.renderedTileBox_; + for (var ijz in this.renderedTiles_) { + index = ijz.split(","); + i = +index[0]; + j = +index[1]; z = +index[2]; prune = this.renderedZ_ !== z || - // beyond on the left side - (xRight ? x < left : x > left) || - // beyond on the right side - (xRight ? x > right : x < right) || - // above - (yDown ? y < top : y > top) || - // below - (yDown ? y > bottom : y < bottom); + i < box.left || // beyond on the left side + i >= box.right || // beyond on the right side + j < box.top || // above + j >= box.bottom; // below if (prune) { - tile = this.renderedTiles_[xyz]; - delete this.renderedTiles_[xyz]; - this.target_.removeChild(tile.getImg()); + tile = this.renderedTiles_[ijz]; + delete this.renderedTiles_[ijz]; + this.container_.removeChild(tile.getImg()); } } }; @@ -294,12 +306,14 @@ ol.renderer.TileLayerRenderer.prototype.removeInvisibleTiles_ = function() { * Deal with changes in resolution. * TODO: implement the animation * - * @param {ol.Loc} center New center. * @param {number} resolution New resolution. */ -ol.renderer.TileLayerRenderer.prototype.changeResolution_ = function(center, resolution) { +ol.renderer.TileLayerRenderer.prototype.changeResolution_ = function(resolution) { + var pair = this.getPreferredResAndZ_(resolution); + this.renderedZ_ = pair[1]; + this.renderedResolution_ = resolution; this.renderedTiles_ = {}; - goog.dom.removeChildren(this.target_); + goog.dom.removeChildren(this.container_); }; diff --git a/test/spec/ol/Events.test.js b/test/spec/ol/Events.test.js index 643f1a648c..604e131708 100644 --- a/test/spec/ol/Events.test.js +++ b/test/spec/ol/Events.test.js @@ -172,10 +172,4 @@ describe("ol.event.Events", function() { expect(ol.event.isMultiTouch({})).toBe(false); }); - it("provides an isEnterOrSpace() function", function() { - expect(ol.event.isEnterOrSpace({type: 'keypress', keyCode: 13})).toBe(true); - expect(ol.event.isEnterOrSpace({type: 'keypress', keyCode: 32})).toBe(true); - expect(ol.event.isEnterOrSpace({type: 'keypress', keyCode: 3})).toBe(true); - }); - }); diff --git a/tests/Map.html b/tests/Map.html index e09f132442..1b7c5c2e60 100644 --- a/tests/Map.html +++ b/tests/Map.html @@ -1935,12 +1935,12 @@ map.destroy(); } - function test_moveByPx(t) { + function test_moveByViewportPx(t) { t.plan(16); var moved; var Layer = OpenLayers.Class(OpenLayers.Layer, { - moveByPx: function(dx, dy) { + moveByViewportPx: function(dx, dy) { moved[this.name] = true; } }); @@ -1972,7 +1972,7 @@ // move to a valid position moved = {}; - map.moveByPx(-455, 455); + map.moveByViewportPx(-455, 455); t.eq(map.layerContainerDiv.style.left, '455px', '[valid position] layer container left correct'); t.eq(map.layerContainerDiv.style.top, '-455px', @@ -1984,7 +1984,7 @@ // move outside the max extent moved = {}; - map.moveByPx(-4500, 4500); + map.moveByViewportPx(-4500, 4500); t.eq(map.layerContainerDiv.style.left, '455px', '[outside max extent] layer container left correct'); t.eq(map.layerContainerDiv.style.top, '-455px', @@ -1996,7 +1996,7 @@ // move outside the restricted extent moved = {}; - map.moveByPx(-500, 500); + map.moveByViewportPx(-500, 500); t.eq(map.layerContainerDiv.style.left, '455px', '[outside restricted extent] layer container left correct'); t.eq(map.layerContainerDiv.style.top, '-455px', @@ -2011,7 +2011,7 @@ } // test for http://trac.osgeo.org/openlayers/ticket/3388 - function test_moveByPx_restrictedExtent(t) { + function test_moveByViewportPx_restrictedExtent(t) { t.plan(2); var map = new OpenLayers.Map({ @@ -2024,7 +2024,7 @@ map.zoomToExtent(new OpenLayers.Bounds(-11.25, 0, 11.25, 11.25)); - map.moveByPx(-10, -10); + map.moveByViewportPx(-10, -10); t.eq(map.layerContainerDiv.style.left, '10px', 'layer container left correct'); t.eq(map.layerContainerDiv.style.top, '0px', 'layer container top correct'); }