Files
openlayers/src/ol/renderer/TileLayerRenderer.js
2012-07-09 01:15:56 -06:00

353 lines
10 KiB
JavaScript

goog.provide('ol.renderer.TileLayerRenderer');
goog.require('ol.renderer.LayerRenderer');
goog.require('ol.layer.TileLayer');
goog.require('ol.renderer.Composite');
goog.require('ol.TileSet');
goog.require('ol.Bounds');
goog.require('goog.style');
goog.require('goog.math.Box');
/**
* A single layer renderer that will be created by the composite map renderer.
*
* @constructor
* @param {!Element} container
* @param {!ol.layer.Layer} layer
* @extends {ol.renderer.LayerRenderer}
*/
ol.renderer.TileLayerRenderer = function(container, layer) {
goog.base(this, container, layer);
/**
* @type {Array.<number>}
*/
this.layerResolutions_ = layer.getResolutions();
/**
* @type {Array.<number>}
*/
this.tileOrigin_ = layer.getTileOrigin();
/**
* @type {Array.<number>}
*/
this.tileSize_ = layer.getTileSize();
/**
* @type {boolean}
*/
this.xRight_ = layer.getXRight();
/**
* @type {boolean}
*/
this.yDown_ = layer.getYDown();
/**
* @type {number|undefined}
* @private
*/
this.renderedResolution_ = undefined;
/**
* @type {number|undefined}
* @private
*/
this.renderedZ_ = undefined;
};
goog.inherits(ol.renderer.TileLayerRenderer, ol.renderer.LayerRenderer);
/**
* Render the layer.
*
* @param {!ol.Loc} center
* @param {number} resolution
*/
ol.renderer.TileLayerRenderer.prototype.draw = function(center, resolution) {
if (resolution !== this.renderedResolution_) {
this.changeResolution_(resolution);
}
var z = this.renderedZ_;
var tileOrigin = this.tileOrigin_;
var offset = this.getTileOffset_();
var tileBox = this.getTileBox_(center, resolution);
var fragment = document.createDocumentFragment();
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) {
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_[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;
}
}
}
}
if (newTiles) {
this.container_.appendChild(fragment);
}
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.floor((loc.getX() - tileOrigin[0]) / resolution);
var dy = Math.floor((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, 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 ||
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_[ijz];
delete this.renderedTiles_[ijz];
this.container_.removeChild(tile.getImg());
}
}
};
/**
* Deal with changes in resolution.
* TODO: implement the animation
*
* @param {number} resolution New 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.container_);
};
/**
* Get an identifying string for this renderer.
*
* @export
* @returns {string}
*/
ol.renderer.TileLayerRenderer.getType = function() {
// TODO: revisit
return "tile";
};
/**
* Determine if this renderer type is supported in this environment.
*
* @export
* @return {boolean} This renderer is supported.
*/
ol.renderer.TileLayerRenderer.isSupported = function() {
return true;
};
/**
* Determine if this renderer can render the given layer.
*
* @export
* @param {ol.layer.Layer} layer The candidate layer.
* @return {boolean} This renderer is capable of rendering the layer.
*/
ol.renderer.TileLayerRenderer.canRender = function(layer) {
return layer instanceof ol.layer.TileLayer;
};
ol.renderer.Composite.register(ol.renderer.TileLayerRenderer);