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.} */ 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.} */ this.srcTiles_ = []; /** * @private * @type {Array.} */ 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; };