Merge branch 'master' into event

Conflicts:
	src/ol/Map.js
	src/ol/event/Drag.js
This commit is contained in:
Éric Lemoine
2012-07-09 08:57:55 +02:00
14 changed files with 670 additions and 381 deletions

View File

@@ -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("<script type='text/javascript' src='" + url + "'></script>");
})();

View File

@@ -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(

View File

@@ -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_) {

View File

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

View File

@@ -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;

View File

@@ -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() {

95
src/ol/event/Drag.js Normal file
View File

@@ -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);

View File

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

View File

@@ -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<ii; ++i) {
this.getOrCreateRenderer(layers[i], i).draw(center, resolution);
if (this.renderedResolution_) {
if (resolution !== this.renderedResolution_) {
// TODO: apply transition to old target
this.resetTarget_(center, resolution);
}
}
this.renderedResolution_ = resolution;
// shift target element to account for center change
if (this.renderedCenter_) {
this.shiftTarget_(center, resolution);
}
this.renderedCenter_ = center;
this.renderedResolution_ = resolution;
// update each layer renderer
var renderer;
for (var i=0, ii=layers.length; i<ii; ++i) {
renderer = this.getOrCreateRenderer_(layers[i], i);
renderer.draw(center, resolution);
}
};
/**
* Create a new target element for layer renderer containers.
*
* @param {ol.Loc} center
* @param {number} resolution
*/
ol.renderer.Composite.prototype.createTarget_ = function(center, resolution) {
this.targetOrigin_ = this.getOriginForCenterAndRes_(center, resolution);
var containerSize = this.getContainerSize();
var containerWidth = containerSize.width;
var containerHeight = containerSize.height;
var buffer = this.buffer_;
var targetWidth = containerWidth + (2 * buffer);
var targetHeight = containerHeight + (2 * buffer);
var offset = new goog.math.Coordinate(-buffer, -buffer);
var target = goog.dom.createDom('div', {
'class': 'ol-renderer-composite',
'style': 'width:' + targetWidth + 'px;height:' + targetHeight + 'px;' +
'top:' + offset.y + 'px;left:' + offset.x + 'px;' +
'position:absolute'
});
this.target_ = target;
this.targetOffset_ = offset;
this.renderedCenter_ = center;
goog.dom.appendChild(this.container_, target);
};
/**
* @param {ol.Loc} center
* @param {number} resolution
* @return {ol.Loc}
*/
ol.renderer.Composite.prototype.getOriginForCenterAndRes_ = function(center, resolution) {
var containerSize = this.getContainerSize();
var containerWidth = containerSize.width;
var containerHeight = containerSize.height;
var buffer = this.buffer_;
var targetWidth = containerWidth + (2 * buffer);
var targetHeight = containerHeight + (2 * buffer);
return new ol.Loc(
center.getX() - (resolution * targetWidth / 2),
center.getY() + (resolution * targetHeight / 2)
);
};
/**
* Adjust the position of the renderer target given the new center.
*
* @param {ol.Loc} center
* @param {number} resolution
*/
ol.renderer.Composite.prototype.shiftTarget_ = function(center, resolution) {
var oldCenter = this.renderedCenter_;
var dx = Math.round((oldCenter.getX() - center.getX()) / resolution);
var dy = Math.round((center.getY() - oldCenter.getY()) / resolution);
if (!(dx == 0 && dy == 0)) {
var offset = this.targetOffset_;
offset.x += Math.round((oldCenter.getX() - center.getX()) / resolution);
offset.y += Math.round((center.getY() - oldCenter.getY()) / resolution);
goog.style.setPosition(this.target_, offset);
}
};
/**
* Reposition the target element back to the original offset.
*
* @param {ol.Loc} center
* @param {number} resolution
*/
ol.renderer.Composite.prototype.resetTarget_ = function(center, resolution) {
this.targetOrigin_ = this.getOriginForCenterAndRes_(center, resolution);
for (var i=0, ii=this.renderers_.length; i<ii; ++i) {
this.renderers_[i].setContainerOrigin(this.targetOrigin_);
}
var offset = new goog.math.Coordinate(-this.buffer_, -this.buffer_);
this.targetOffset_ = offset;
this.renderedCenter_ = center;
goog.style.setPosition(this.target_, offset);
};
/**
* @param {ol.layer.Layer} layer
* @param {number} index
*/
ol.renderer.Composite.prototype.getOrCreateRenderer = function(layer, index) {
var renderer = this.getRenderer(layer);
ol.renderer.Composite.prototype.getOrCreateRenderer_ = function(layer, index) {
var renderer = this.getRenderer_(layer);
if (goog.isNull(renderer)) {
renderer = this.createRenderer(layer);
renderer = this.createRenderer_(layer);
goog.array.insertAt(this.renderers_, renderer, index);
}
return renderer;
@@ -68,21 +201,42 @@ ol.renderer.Composite.prototype.getOrCreateRenderer = function(layer, index) {
/**
* @param {ol.layer.Layer} layer
* @return {ol.renderer.LayerRenderer}
*/
ol.renderer.Composite.prototype.getRenderer = function(layer) {
ol.renderer.Composite.prototype.getRenderer_ = function(layer) {
function finder(candidate) {
return candidate.getLayer() === layer;
}
return goog.array.find(this.renderers_, finder);
var renderer = goog.array.find(this.renderers_, finder);
return /** @type {ol.renderer.LayerRenderer} */ renderer;
};
/**
* @param {ol.layer.Layer} layer
*/
ol.renderer.Composite.prototype.createRenderer = function(layer) {
ol.renderer.Composite.prototype.createRenderer_ = function(layer) {
var Renderer = this.pickRendererType(layer);
goog.asserts.assert(Renderer, "No supported renderer for layer: " + layer);
return new Renderer(this.target_, layer);
var container = goog.dom.createDom('div', {
'class': 'ol-renderer-composite-layer',
'style': 'width:100%;height:100%;top:0;left:0;position:absolute'
});
goog.dom.appendChild(this.target_, container);
var renderer = new Renderer(container, layer);
renderer.setContainerOrigin(this.targetOrigin_);
this.layerContainers_[goog.getUid(renderer)] = container;
return renderer;
};
/**
* @param {ol.renderer.LayerRenderer} renderer
* @return {Element}
*/
ol.renderer.Composite.prototype.getRendererContainer_ = function(renderer) {
var container = this.layerContainers_[goog.getUid(renderer)];
goog.asserts.assert(goog.isDef(container));
return container;
};
/**

View File

@@ -1,5 +1,8 @@
goog.provide('ol.renderer.LayerRenderer');
goog.require('goog.math.Coordinate');
goog.require('goog.math.Size');
/**
* A single layer renderer that will be created by the composite map renderer.
*
@@ -15,24 +18,48 @@ ol.renderer.LayerRenderer = function(container, layer) {
*/
this.container_ = container;
/**
* @type {goog.math.Size}
* @private
*/
this.containerSize_ = null;
/**
* Location of the top-left corner of the renderer container in map coords.
*
* @type {ol.Loc}
* @protected
*/
this.containerOrigin_ = null;
/**
* @type {!ol.layer.Layer}
* @protected
*/
this.layer_ = layer;
var target = goog.dom.createDom('div', {
'class': 'ol-renderer-layer',
'style': 'position:absolute;height:1px:width:1px'
});
goog.dom.appendChild(container, target);
};
/**
* @type Element
* @protected
*/
this.target_ = target;
/**
* @return {goog.math.Size}
* @protected
*/
ol.renderer.LayerRenderer.prototype.getContainerSize = function() {
// TODO: listen for resize and set this.constainerSize_ null
// https://github.com/openlayers/ol3/issues/2
if (goog.isNull(this.containerSize_)) {
this.containerSize_ = goog.style.getSize(this.container_);
}
return this.containerSize_;
};
/**
* Set the location of the top-left corner of the renderer container.
*
* @param {ol.Loc} origin The container origin.
*/
ol.renderer.LayerRenderer.prototype.setContainerOrigin = function(origin) {
this.containerOrigin_ = origin;
};
/**

View File

@@ -1,6 +1,7 @@
goog.provide('ol.renderer.MapRenderer');
goog.require('goog.style');
goog.require('goog.math.Size');
/**
* @constructor
@@ -13,6 +14,12 @@ ol.renderer.MapRenderer = function(container) {
* @protected
*/
this.container_ = container;
/**
* @type {goog.math.Size}
* @private
*/
this.containerSize_ = null;
/**
* @type {ol.Loc}
@@ -28,6 +35,20 @@ ol.renderer.MapRenderer = function(container) {
};
/**
* @return {goog.math.Size}
* @protected
*/
ol.renderer.MapRenderer.prototype.getContainerSize = function() {
// TODO: listen for resize and set this.constainerSize_ null
// https://github.com/openlayers/ol3/issues/2
if (goog.isNull(this.containerSize_)) {
this.containerSize_ = goog.style.getSize(this.container_);
}
return this.containerSize_;
};
/**
* @param {Array.<ol.layer.Layer>} 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_);
};

View File

@@ -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.<number>}
*/
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<ii; ++i) {
// assumes sorted resolutions
candidate = this.layerResolutions_[i];
diff = Math.abs(resolution - candidate);
if (diff < minDiff) {
z = i;
r = candidate;
minDiff = diff;
} else {
break;
}
}
return [r, z];
};
/**
* @return {goog.math.Size}
*/
ol.renderer.TileLayerRenderer.prototype.getContainerSize_ = function() {
// TODO: listen for resize and set this.constainerSize_ null
// https://github.com/openlayers/ol3/issues/2
if (goog.isNull(this.containerSize_)) {
this.containerSize_ = goog.style.getSize(this.container_);
}
return this.containerSize_;
};
/**
* Tiles rendered at the current resolution;
* @type {Object}
*/
ol.renderer.TileLayerRenderer.prototype.renderedTiles_ = {};
/**
* Render the layer.
*
@@ -140,152 +70,234 @@ ol.renderer.TileLayerRenderer.prototype.renderedTiles_ = {};
*/
ol.renderer.TileLayerRenderer.prototype.draw = function(center, resolution) {
if (resolution !== this.renderedResolution_) {
this.changeResolution_(center, resolution);
this.changeResolution_(resolution);
}
var pair = this.getPreferredResAndZ_(resolution);
var tileResolution = pair[0];
var tileZ = pair[1];
var scale = resolution / tileResolution;
var pxMapSize = this.getContainerSize_();
var xRight = this.xRight_;
var yDown = this.yDown_;
var z = this.renderedZ_;
var tileOrigin = this.tileOrigin_;
var halfMapWidth = (resolution * pxMapSize.width) / 2;
var halfMapHeight = (resolution * pxMapSize.height) / 2;
var centerX = center.getX();
var centerY = center.getY();
var mapMinX = centerX - halfMapWidth;
var mapMaxY = centerY + halfMapHeight;
var pxOffsetX = Math.round((mapMinX - this.tileOrigin_[0]) / resolution);
var pxOffsetY = Math.round((this.tileOrigin_[1] - mapMaxY) / resolution);
// this gives us the desired size in fractional pixels
var pxTileWidth = this.tileSize_[0] / scale;
var pxTileHeight = this.tileSize_[1] / scale;
// this is the index of the top left tile
var leftTileX = Math.floor(xRight * pxOffsetX / pxTileWidth);
var topTileY = Math.floor(yDown * pxOffsetY / pxTileHeight);
var offset = this.getTileOffset_();
var tileBox = this.getTileBox_(center, resolution);
var pxMinX;
if (xRight > 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<numTilesWide; ++i) {
pxTileTop = pxMinY;
tileX = leftTileX + (i * xRight);
if (scale !== 1) {
pxTileRight = Math.round(pxMinX + ((i+1) * pxTileWidth));
} else {
pxTileRight = pxTileLeft + pxTileWidth;
}
for (var j=0; j<numTilesHigh; ++j) {
append = false;
tileY = topTileY + (j * yDown);
xyz = tileX + "," + tileY + "," + tileZ;
if (scale !== 1) {
pxTileBottom = Math.round(pxMinY + ((j+1) * pxTileHeight));
} else {
pxTileBottom = pxTileTop + pxTileHeight;
}
img = null;
tile = this.renderedTiles_[xyz];
var ijz, key, tile, xyz, box, img, newTiles = false;
for (var i=tileBox.left; i<tileBox.right; ++i) {
for (var j=tileBox.top; j<tileBox.bottom; ++j) {
ijz = [i, j, z];
key = ijz.join(",");
tile = this.renderedTiles_[key];
if (!tile) {
tile = this.layer_.getTileForXYZ(tileX, tileY, tileZ);
xyz = this.getTileCoordsFromNormalizedCoords_(ijz);
tile = this.layer_.getTileForXYZ(xyz[0], xyz[1], xyz[2]);
if (tile) {
if (!tile.isLoaded() && !tile.isLoading()) {
tile.load();
}
this.renderedTiles_[xyz] = tile;
this.renderedTiles_[key] = tile;
box = this.getTilePixelBox_(ijz, resolution);
img = tile.getImg();
img.style.top = (box.top - offset.y) + "px";
img.style.left = (box.left - offset.x) + "px";
/**
* We need to set the size here even if the scale is 1
* because the image may have been scaled previously. If
* we want to avoid setting size unnecessarily, the tile
* should keep track of the scale.
*/
img.style.height = (box.bottom - box.top) + "px";
img.style.width = (box.right - box.left) + "px";
goog.dom.appendChild(fragment, img);
newTiles = true;
}
} else {
img = tile.getImg();
}
if (img) {
img.style.top = pxTileTop + "px";
img.style.left = pxTileLeft + "px";
if (scale !== 1) {
img.style.height = (pxTileRight - pxTileLeft) + "px";
img.style.width = (pxTileBottom - pxTileTop) + "px";
}
}
pxTileTop = pxTileBottom;
}
pxTileLeft = pxTileRight;
}
if (newTiles) {
this.target_.appendChild(fragment);
this.container_.appendChild(fragment);
}
this.renderedResolution_ = resolution;
this.renderedTop_ = topTileY;
this.renderedRight_ = tileX;
this.renderedBottom_ = tileY;
this.renderedLeft_ = leftTileX;
this.renderedZ_ = tileZ;
this.renderedTileBox_ = tileBox;
this.removeInvisibleTiles_();
};
/**
* Get the pixel offset between the tile origin and the container origin.
* TODO: cache this and invalidate it with changes to the container origin.
*
* @return {goog.math.Coordinate}
*/
ol.renderer.TileLayerRenderer.prototype.getTileOffset_ = function() {
var resolution = this.renderedResolution_;
return new goog.math.Coordinate(
Math.round((this.containerOrigin_.getX() - this.tileOrigin_[0]) / resolution),
Math.round((this.tileOrigin_[1] - this.containerOrigin_.getY()) / resolution)
);
};
/**
* @param {Array.<number>} 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.<number>}
*/
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<ii; ++i) {
// assumes sorted resolutions
candidate = this.layerResolutions_[i];
diff = Math.abs(resolution - candidate);
if (diff < minDiff) {
z = i;
r = candidate;
minDiff = diff;
} else {
break;
}
}
var pair = cache[resolution] = [r, z];
return pair;
};
})();
/**
* Tiles rendered at the current resolution;
* @type {Object}
*/
ol.renderer.TileLayerRenderer.prototype.renderedTiles_ = {};
/**
* @param {Array.<number>} ijz
* @return {Array.<number>}
*/
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_);
};

View File

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

View File

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