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');
}