Files
openlayers/src/ol/reproj/tile.js

316 lines
8.7 KiB
JavaScript

goog.provide('ol.reproj.Tile');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.math');
goog.require('goog.object');
goog.require('ol.Tile');
goog.require('ol.TileState');
goog.require('ol.dom');
goog.require('ol.extent');
goog.require('ol.proj');
goog.require('ol.reproj');
goog.require('ol.reproj.triangulation');
/**
* @constructor
* @extends {ol.Tile}
* @param {ol.proj.Projection} sourceProj
* @param {ol.tilegrid.TileGrid} sourceTileGrid
* @param {ol.proj.Projection} targetProj
* @param {ol.tilegrid.TileGrid} targetTileGrid
* @param {number} z
* @param {number} x
* @param {number} y
* @param {number} pixelRatio
* @param {function(number, number, number, number) : ol.Tile} getTileFunction
*/
ol.reproj.Tile = function(sourceProj, sourceTileGrid,
targetProj, targetTileGrid, z, x, y, pixelRatio, getTileFunction) {
goog.base(this, [z, x, y], ol.TileState.IDLE);
/**
* @private
* @type {HTMLCanvasElement}
*/
this.canvas_ = null;
/**
* @private
* @type {Object.<number, HTMLCanvasElement>}
*/
this.canvasByContext_ = {};
/**
* @private
* @type {ol.tilegrid.TileGrid}
*/
this.sourceTileGrid_ = sourceTileGrid;
/**
* @private
* @type {ol.tilegrid.TileGrid}
*/
this.targetTileGrid_ = targetTileGrid;
var targetExtent = targetTileGrid.getTileCoordExtent(this.getTileCoord());
var maxTargetExtent = this.targetTileGrid_.getExtent();
var maxSourceExtent = this.sourceTileGrid_.getExtent();
/**
* @private
* @type {!ol.reproj.Triangulation}
*/
this.triangulation_ = ol.reproj.triangulation.createForExtent(
targetExtent, sourceProj, targetProj,
maxTargetExtent, maxSourceExtent);
/**
* @private
* @type {!Array.<ol.Tile>}
*/
this.srcTiles_ = [];
/**
* @private
* @type {Array.<goog.events.Key>}
*/
this.sourcesListenerKeys_ = null;
/**
* @private
* @type {number}
*/
this.srcZ_ = 0;
if (!ol.extent.intersects(maxTargetExtent, targetExtent)) {
// Tile is completely outside range -> EMPTY
// TODO: is it actually correct that the source even creates the tile ?
this.state = ol.TileState.EMPTY;
return;
}
if (this.triangulation_.triangles.length === 0) {
// no valid triangles -> EMPTY
this.state = ol.TileState.EMPTY;
return;
}
var targetCenter = ol.extent.getCenter(targetExtent);
var targetResolution = targetTileGrid.getResolution(z);
var sourceResolution = ol.reproj.calculateSourceResolution(
sourceProj, targetProj, targetCenter, targetResolution);
if (!goog.math.isFiniteNumber(sourceResolution) || sourceResolution <= 0) {
// invalid sourceResolution -> EMPTY
// probably edges of the projections when no extent is defined
this.state = ol.TileState.EMPTY;
return;
}
this.srcZ_ = sourceTileGrid.getZForResolution(sourceResolution);
var srcExtent = ol.reproj.triangulation.getSourceExtent(this.triangulation_);
var sourceProjExtent = sourceProj.getExtent();
if (!sourceProj.isGlobal() && sourceProjExtent) {
srcExtent = ol.extent.getIntersection(srcExtent, sourceProjExtent);
}
if (!goog.isNull(maxSourceExtent) &&
!ol.extent.intersects(maxSourceExtent, srcExtent)) {
this.state = ol.TileState.EMPTY;
} else {
var srcRange = sourceTileGrid.getTileRangeForExtentAndZ(
srcExtent, this.srcZ_);
var srcFullRange = sourceTileGrid.getFullTileRange(this.srcZ_);
srcRange.minY = Math.max(srcRange.minY, srcFullRange.minY);
srcRange.maxY = Math.min(srcRange.maxY, srcFullRange.maxY);
var xRange;
if (srcRange.minX > srcRange.maxX) {
xRange = goog.array.concat(
goog.array.range(srcRange.minX, srcFullRange.maxX + 1),
goog.array.range(srcFullRange.minX, srcRange.maxX + 1)
);
} else {
xRange = goog.array.range(
Math.max(srcRange.minX, srcFullRange.minX),
Math.min(srcRange.maxX, srcFullRange.maxX) + 1
);
}
if (xRange.length * srcRange.getHeight() > 100) {
// Too many source tiles are needed -- something probably went wrong
// This sometimes happens for certain non-global projections
// if no extent is specified.
// TODO: detect somehow better? or at least make this a define
this.state = ol.TileState.ERROR;
return;
}
goog.array.forEach(xRange, function(srcX, i, arr) {
for (var srcY = srcRange.minY; srcY <= srcRange.maxY; srcY++) {
var tile = getTileFunction(this.srcZ_, srcX, srcY, pixelRatio);
if (tile) {
this.srcTiles_.push(tile);
}
}
}, this);
if (this.srcTiles_.length === 0) {
this.state = ol.TileState.EMPTY;
}
}
};
goog.inherits(ol.reproj.Tile, ol.Tile);
/**
* @inheritDoc
*/
ol.reproj.Tile.prototype.disposeInternal = function() {
if (this.state == ol.TileState.LOADING) {
this.unlistenSources_();
}
goog.base(this, 'disposeInternal');
};
/**
* @inheritDoc
*/
ol.reproj.Tile.prototype.getImage = function(opt_context) {
if (goog.isDef(opt_context)) {
var image;
var key = goog.getUid(opt_context);
if (key in this.canvasByContext_) {
return this.canvasByContext_[key];
} else if (goog.object.isEmpty(this.canvasByContext_)) {
image = this.canvas_;
} else {
image = /** @type {HTMLCanvasElement} */ (this.canvas_.cloneNode(false));
}
this.canvasByContext_[key] = image;
return image;
} else {
return this.canvas_;
}
};
/**
* @private
*/
ol.reproj.Tile.prototype.reproject_ = function() {
var sources = [];
goog.array.forEach(this.srcTiles_, function(tile, i, arr) {
if (tile && tile.getState() == ol.TileState.LOADED) {
sources.push({
extent: this.sourceTileGrid_.getTileCoordExtent(tile.tileCoord),
image: tile.getImage()
});
}
}, this);
// create the canvas
var tileCoord = this.getTileCoord();
var z = tileCoord[0];
var size = this.targetTileGrid_.getTileSize(z);
var targetResolution = this.targetTileGrid_.getResolution(z);
var srcResolution = this.sourceTileGrid_.getResolution(this.srcZ_);
var width = goog.isNumber(size) ? size : size[0];
var height = goog.isNumber(size) ? size : size[1];
var context = ol.dom.createCanvasContext2D(width, height);
context.imageSmoothingEnabled = true;
if (goog.DEBUG) {
context.fillStyle =
sources.length === 0 ? 'rgba(255,0,0,.8)' :
(sources.length == 1 ? 'rgba(0,255,0,.3)' : 'rgba(0,0,255,.1)');
context.fillRect(0, 0, width, height);
}
if (sources.length > 0) {
var targetExtent = this.targetTileGrid_.getTileCoordExtent(tileCoord);
ol.reproj.renderTriangles(context, srcResolution, targetResolution,
targetExtent, this.triangulation_, sources);
}
this.canvas_ = context.canvas;
this.state = ol.TileState.LOADED;
this.changed();
};
/**
* @inheritDoc
*/
ol.reproj.Tile.prototype.load = function() {
if (this.state == ol.TileState.IDLE) {
this.state = ol.TileState.LOADING;
this.changed();
var leftToLoad = 0;
var onSingleSourceLoaded = goog.bind(function() {
leftToLoad--;
goog.asserts.assert(leftToLoad >= 0, 'leftToLoad should not be negative');
if (leftToLoad <= 0) {
this.unlistenSources_();
this.reproject_();
}
}, this);
goog.asserts.assert(goog.isNull(this.sourcesListenerKeys_),
'this.sourcesListenerKeys_ should be null');
this.sourcesListenerKeys_ = [];
goog.array.forEach(this.srcTiles_, function(tile, i, arr) {
var state = tile.getState();
if (state == ol.TileState.IDLE || state == ol.TileState.LOADING) {
leftToLoad++;
var sourceListenKey;
sourceListenKey = tile.listen(goog.events.EventType.CHANGE,
function(e) {
var state = tile.getState();
if (state == ol.TileState.LOADED ||
state == ol.TileState.ERROR ||
state == ol.TileState.EMPTY) {
onSingleSourceLoaded();
goog.events.unlistenByKey(sourceListenKey);
}
});
this.sourcesListenerKeys_.push(sourceListenKey);
}
}, this);
goog.array.forEach(this.srcTiles_, function(tile, i, arr) {
var state = tile.getState();
if (state == ol.TileState.IDLE) {
tile.load();
}
});
if (leftToLoad === 0) {
this.reproject_();
}
}
};
/**
* @private
*/
ol.reproj.Tile.prototype.unlistenSources_ = function() {
goog.asserts.assert(!goog.isNull(this.sourcesListenerKeys_),
'this.sourcesListenerKeys_ should not be null');
goog.array.forEach(this.sourcesListenerKeys_, goog.events.unlistenByKey);
this.sourcesListenerKeys_ = null;
};