diff --git a/src/ol/reproj/reproj.js b/src/ol/reproj/reproj.js index 95856191f1..ed4e4a6940 100644 --- a/src/ol/reproj/reproj.js +++ b/src/ol/reproj/reproj.js @@ -116,55 +116,12 @@ ol.reproj.render = function(width, height, pixelRatio, context.scale(pixelRatio, pixelRatio); - var wrapXShiftDistance = sourceExtent ? ol.extent.getWidth(sourceExtent) : 0; - - var wrapXType = ol.reproj.WrapXRendering_.NONE; - - if (triangulation.getWrapsXInSource() && wrapXShiftDistance > 0) { - // If possible, stitch the sources shifted to solve the wrapX issue here. - // This is not possible if crossing both "dateline" and "prime meridian". - var triangulationSrcExtent = triangulation.calculateSourceExtent(); - var triangulationSrcWidth = goog.math.modulo( - ol.extent.getWidth(triangulationSrcExtent), wrapXShiftDistance); - - if (triangulationSrcWidth < wrapXShiftDistance / 2) { - wrapXType = ol.reproj.WrapXRendering_.STITCH_SHIFT; - } else { - wrapXType = ol.reproj.WrapXRendering_.STITCH_EXTENDED; - } - } - var sourceDataExtent = ol.extent.createEmpty(); sources.forEach(function(src, i, arr) { - if (wrapXType == ol.reproj.WrapXRendering_.STITCH_SHIFT) { - var srcW = src.extent[2] - src.extent[0]; - var srcX = goog.math.modulo(src.extent[0], wrapXShiftDistance); - ol.extent.extend(sourceDataExtent, [srcX, src.extent[1], - srcX + srcW, src.extent[3]]); - } else { - ol.extent.extend(sourceDataExtent, src.extent); - } + ol.extent.extend(sourceDataExtent, src.extent); }); - if (sourceExtent) { - if (wrapXType == ol.reproj.WrapXRendering_.NONE) { - sourceDataExtent[0] = ol.math.clamp( - sourceDataExtent[0], sourceExtent[0], sourceExtent[2]); - sourceDataExtent[2] = ol.math.clamp( - sourceDataExtent[2], sourceExtent[0], sourceExtent[2]); - } - sourceDataExtent[1] = ol.math.clamp( - sourceDataExtent[1], sourceExtent[1], sourceExtent[3]); - sourceDataExtent[3] = ol.math.clamp( - sourceDataExtent[3], sourceExtent[1], sourceExtent[3]); - } - - var canvasWidthInUnits; - if (wrapXType == ol.reproj.WrapXRendering_.STITCH_EXTENDED) { - canvasWidthInUnits = 2 * wrapXShiftDistance; - } else { - canvasWidthInUnits = ol.extent.getWidth(sourceDataExtent); - } + var canvasWidthInUnits = ol.extent.getWidth(sourceDataExtent); var canvasHeightInUnits = ol.extent.getHeight(sourceDataExtent); var stitchContext = ol.dom.createCanvasContext2D( Math.round(pixelRatio * canvasWidthInUnits / sourceResolution), @@ -180,15 +137,7 @@ ol.reproj.render = function(width, height, pixelRatio, var srcWidth = ol.extent.getWidth(src.extent); var srcHeight = ol.extent.getHeight(src.extent); - if (wrapXType == ol.reproj.WrapXRendering_.STITCH_SHIFT) { - xPos = goog.math.modulo(xPos, wrapXShiftDistance); - } stitchContext.drawImage(src.image, xPos, yPos, srcWidth, srcHeight); - - if (wrapXType == ol.reproj.WrapXRendering_.STITCH_EXTENDED) { - stitchContext.drawImage(src.image, wrapXShiftDistance + xPos, yPos, - srcWidth, srcHeight); - } }); var targetTopLeft = ol.extent.getTopLeft(targetExtent); @@ -227,21 +176,6 @@ ol.reproj.render = function(width, height, pixelRatio, var u2 = (target[2][0] - targetTopLeft[0]) / targetResolution, v2 = -(target[2][1] - targetTopLeft[1]) / targetResolution; - var performWrapXShift = wrapXType == ol.reproj.WrapXRendering_.STITCH_SHIFT; - if (wrapXType == ol.reproj.WrapXRendering_.STITCH_EXTENDED) { - var minX = Math.min(x0, x1, x2); - var maxX = Math.max(x0, x1, x2); - - performWrapXShift = minX <= sourceExtent[0] || - (maxX - minX) > wrapXShiftDistance / 2; - } - - if (performWrapXShift) { - x0 = goog.math.modulo(x0, wrapXShiftDistance); - x1 = goog.math.modulo(x1, wrapXShiftDistance); - x2 = goog.math.modulo(x2, wrapXShiftDistance); - } - // Shift all the source points to improve numerical stability // of all the subsequent calculations. The [x0, y0] is used here. // This is also used to simplify the linear system. @@ -322,15 +256,3 @@ ol.reproj.render = function(width, height, pixelRatio, } return context.canvas; }; - - -/** - * Type of solution used to solve wrapX in source during rendering. - * @enum {number} - * @private - */ -ol.reproj.WrapXRendering_ = { - NONE: 0, - STITCH_SHIFT: 1, - STITCH_EXTENDED: 2 -}; diff --git a/src/ol/reproj/tile.js b/src/ol/reproj/tile.js index cc20abc8db..7e5192e525 100644 --- a/src/ol/reproj/tile.js +++ b/src/ol/reproj/tile.js @@ -8,6 +8,7 @@ goog.require('goog.object'); goog.require('ol.Tile'); goog.require('ol.TileState'); goog.require('ol.extent'); +goog.require('ol.math'); goog.require('ol.proj'); goog.require('ol.reproj'); goog.require('ol.reproj.Triangulation'); @@ -151,56 +152,38 @@ ol.reproj.Tile = function(sourceProj, sourceTileGrid, this.sourceZ_ = sourceTileGrid.getZForResolution(sourceResolution); var sourceExtent = this.triangulation_.calculateSourceExtent(); - if (maxSourceExtent && - !this.triangulation_.getWrapsXInSource() && - !ol.extent.intersects(maxSourceExtent, sourceExtent)) { + if (maxSourceExtent) { + if (sourceProj.canWrapX()) { + sourceExtent[1] = ol.math.clamp( + sourceExtent[1], maxSourceExtent[1], maxSourceExtent[3]); + sourceExtent[3] = ol.math.clamp( + sourceExtent[3], maxSourceExtent[1], maxSourceExtent[3]); + } else { + sourceExtent = ol.extent.getIntersection(sourceExtent, maxSourceExtent); + } + } + + if (!ol.extent.getArea(sourceExtent)) { this.state = ol.TileState.EMPTY; } else { var sourceRange = sourceTileGrid.getTileRangeForExtentAndZ( sourceExtent, this.sourceZ_); - var xRange = []; - var sourceFullRange = sourceTileGrid.getFullTileRange(this.sourceZ_); - if (sourceFullRange) { - sourceRange.minY = Math.max(sourceRange.minY, sourceFullRange.minY); - sourceRange.maxY = Math.min(sourceRange.maxY, sourceFullRange.maxY); - - if (sourceRange.minX > sourceRange.maxX) { - var i; - for (i = sourceRange.minX; i <= sourceFullRange.maxX; i++) { - xRange.push(i); - } - for (i = sourceFullRange.minX; i <= sourceRange.maxX; i++) { - xRange.push(i); - } - } else { - var first = Math.max(sourceRange.minX, sourceFullRange.minX); - var last = Math.min(sourceRange.maxX, sourceFullRange.maxX); - for (var j = first; j <= last; j++) { - xRange.push(j); - } - } - } else { - for (var k = sourceRange.minX; k <= sourceRange.maxX; k++) { - xRange.push(k); - } - } - - var tilesRequired = xRange.length * sourceRange.getHeight(); + var tilesRequired = sourceRange.getWidth() * sourceRange.getHeight(); if (!goog.asserts.assert( tilesRequired < ol.RASTER_REPROJECTION_MAX_SOURCE_TILES, 'reasonable number of tiles is required')) { this.state = ol.TileState.ERROR; return; } - xRange.forEach(function(srcX, i, arr) { + for (var srcX = sourceRange.minX; srcX <= sourceRange.maxX; srcX++) { for (var srcY = sourceRange.minY; srcY <= sourceRange.maxY; srcY++) { var tile = getTileFunction(this.sourceZ_, srcX, srcY, pixelRatio); if (tile) { this.sourceTiles_.push(tile); } } - }, this); + } if (this.sourceTiles_.length === 0) { this.state = ol.TileState.EMPTY; diff --git a/src/ol/reproj/triangulation.js b/src/ol/reproj/triangulation.js index 1f0d22662b..b69006fb01 100644 --- a/src/ol/reproj/triangulation.js +++ b/src/ol/reproj/triangulation.js @@ -75,12 +75,6 @@ ol.reproj.Triangulation = function(sourceProj, targetProj, targetExtent, */ this.triangles_ = []; - /** - * @type {ol.Extent} - * @private - */ - this.trianglesSourceExtent_ = null; - /** * Indicates that source coordinates have to be shifted during reprojection. * This is needed when the triangulation crosses @@ -129,6 +123,49 @@ ol.reproj.Triangulation = function(sourceProj, targetProj, targetExtent, sourceTopLeft, sourceTopRight, sourceBottomRight, sourceBottomLeft, ol.RASTER_REPROJECTION_MAX_SUBDIVISION); + if (this.wrapsXInSource_) { + // Fix coordinates (ol.proj returns wrapped coordinates, "unwrap" here). + // This significantly simplifies the rest of the reprojection process. + + goog.asserts.assert(this.sourceWorldWidth_ !== null); + var leftBound = Infinity; + this.triangles_.forEach(function(triangle, i, arr) { + leftBound = Math.min(leftBound, + triangle.source[0][0], triangle.source[1][0], triangle.source[2][0]); + }); + + // Shift triangles to be as close to `leftBound` as possible + // (if the distance is more than `worldWidth / 2` it can be closer. + this.triangles_.forEach(function(triangle) { + if (Math.max(triangle.source[0][0], triangle.source[1][0], + triangle.source[2][0]) - leftBound > this.sourceWorldWidth_ / 2) { + var newTriangle = [[triangle.source[0][0], triangle.source[0][1]], + [triangle.source[1][0], triangle.source[1][1]], + [triangle.source[2][0], triangle.source[2][1]]]; + if ((newTriangle[0][0] - leftBound) > this.sourceWorldWidth_ / 2) { + newTriangle[0][0] -= this.sourceWorldWidth_; + } + if ((newTriangle[1][0] - leftBound) > this.sourceWorldWidth_ / 2) { + newTriangle[1][0] -= this.sourceWorldWidth_; + } + if ((newTriangle[2][0] - leftBound) > this.sourceWorldWidth_ / 2) { + newTriangle[2][0] -= this.sourceWorldWidth_; + } + + // Rarely (if the extent contains both the dateline and prime meridian) + // the shift can in turn break some triangles. + // Detect this here and don't shift in such cases. + var minX = Math.min( + newTriangle[0][0], newTriangle[1][0], newTriangle[2][0]); + var maxX = Math.max( + newTriangle[0][0], newTriangle[1][0], newTriangle[2][0]); + if ((maxX - minX) < this.sourceWorldWidth_ / 2) { + triangle.source = newTriangle; + } + } + }, this); + } + transformInvCache = {}; }; @@ -278,66 +315,23 @@ ol.reproj.Triangulation.prototype.addQuad_ = function(a, b, c, d, /** * Calculates extent of the 'source' coordinates from all the triangles. - * The left bound of the returned extent can be higher than the right bound, - * if the triangulation wraps X in source (see - * {@link ol.reproj.Triangulation#getWrapsXInSource}). * * @return {ol.Extent} Calculated extent. */ ol.reproj.Triangulation.prototype.calculateSourceExtent = function() { - if (this.trianglesSourceExtent_) { - return this.trianglesSourceExtent_; - } - var extent = ol.extent.createEmpty(); - if (this.wrapsXInSource_) { - // although only some of the triangles are crossing the dateline, - // all coordinates need to be "shifted" to be positive - // to properly calculate the extent (and then possibly shifted back) + this.triangles_.forEach(function(triangle, i, arr) { + var src = triangle.source; + ol.extent.extendCoordinate(extent, src[0]); + ol.extent.extendCoordinate(extent, src[1]); + ol.extent.extendCoordinate(extent, src[2]); + }); - this.triangles_.forEach(function(triangle, i, arr) { - goog.asserts.assert(this.sourceWorldWidth_); - var src = triangle.source; - ol.extent.extendCoordinate(extent, - [goog.math.modulo(src[0][0], this.sourceWorldWidth_), src[0][1]]); - ol.extent.extendCoordinate(extent, - [goog.math.modulo(src[1][0], this.sourceWorldWidth_), src[1][1]]); - ol.extent.extendCoordinate(extent, - [goog.math.modulo(src[2][0], this.sourceWorldWidth_), src[2][1]]); - }, this); - - var sourceProjExtent = this.sourceProj_.getExtent(); - var right = sourceProjExtent[2]; - if (extent[0] > right) { - extent[0] -= this.sourceWorldWidth_; - } - if (extent[2] > right) { - extent[2] -= this.sourceWorldWidth_; - } - } else { - this.triangles_.forEach(function(triangle, i, arr) { - var src = triangle.source; - ol.extent.extendCoordinate(extent, src[0]); - ol.extent.extendCoordinate(extent, src[1]); - ol.extent.extendCoordinate(extent, src[2]); - }); - } - - this.trianglesSourceExtent_ = extent; return extent; }; -/** - * @return {boolean} Whether the source coordinates are wrapped in X - * (the triangulation "crosses the dateline"). - */ -ol.reproj.Triangulation.prototype.getWrapsXInSource = function() { - return this.wrapsXInSource_; -}; - - /** * @return {Array.