diff --git a/src/ol/reproj/reproj.js b/src/ol/reproj/reproj.js index d29d5d52a1..c302095568 100644 --- a/src/ol/reproj/reproj.js +++ b/src/ol/reproj/reproj.js @@ -2,6 +2,7 @@ goog.provide('ol.reproj'); goog.require('goog.array'); goog.require('goog.math'); +goog.require('ol.dom'); goog.require('ol.extent'); goog.require('ol.math'); goog.require('ol.proj'); @@ -60,10 +61,62 @@ ol.reproj.renderTriangles = function(context, sourceResolution, sourceExtent, targetResolution, targetExtent, triangulation, sources) { - var shiftDistance = !goog.isNull(sourceExtent) ? - ol.extent.getWidth(sourceExtent) : null; - var shiftThreshold = !goog.isNull(sourceExtent) ? - (sourceExtent[0] + sourceExtent[2]) / 2 : null; + var wrapXShiftDistance = !goog.isNull(sourceExtent) ? + ol.extent.getWidth(sourceExtent) : 0; + + var wrapXShiftNeeded = 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 performGlobalWrapXShift = false; + if (wrapXShiftNeeded) { + var triangulationSrcExtent = triangulation.calculateSourceExtent(); + var triangulationSrcWidth = goog.math.modulo( + ol.extent.getWidth(triangulationSrcExtent), wrapXShiftDistance); + performGlobalWrapXShift = triangulationSrcWidth < wrapXShiftDistance / 2; + } + + var srcDataExtent = ol.extent.createEmpty(); + goog.array.forEach(sources, function(src, i, arr) { + if (performGlobalWrapXShift) { + var srcW = src.extent[2] - src.extent[0]; + var srcX = goog.math.modulo(src.extent[0], wrapXShiftDistance); + ol.extent.extend(srcDataExtent, [srcX, src.extent[1], + srcX + srcW, src.extent[3]]); + } else { + ol.extent.extend(srcDataExtent, src.extent); + } + }); + if (!goog.isNull(sourceExtent)) { + if (wrapXType == ol.reproj.WrapXRendering_.NONE) { + srcDataExtent[0] = goog.math.clamp( + srcDataExtent[0], sourceExtent[0], sourceExtent[2]); + srcDataExtent[2] = goog.math.clamp( + srcDataExtent[2], sourceExtent[0], sourceExtent[2]); + } + srcDataExtent[1] = goog.math.clamp( + srcDataExtent[1], sourceExtent[1], sourceExtent[3]); + srcDataExtent[3] = goog.math.clamp( + srcDataExtent[3], sourceExtent[1], sourceExtent[3]); + } + + var srcDataWidth = ol.extent.getWidth(srcDataExtent); + var srcDataHeight = ol.extent.getHeight(srcDataExtent); + var stitchContext = ol.dom.createCanvasContext2D( + Math.ceil(srcDataWidth / sourceResolution), + Math.ceil(srcDataHeight / sourceResolution)); + + stitchContext.scale(1 / sourceResolution, 1 / sourceResolution); + stitchContext.translate(-srcDataExtent[0], srcDataExtent[3]); + + goog.array.forEach(sources, function(src, i, arr) { + var xPos = performGlobalWrapXShift ? + goog.math.modulo(src.extent[0], wrapXShiftDistance) : src.extent[0]; + stitchContext.drawImage(src.image, xPos, -src.extent[3], + src.extent[2] - src.extent[0], src.extent[3] - src.extent[1]); + }); + var targetTL = ol.extent.getTopLeft(targetExtent); goog.array.forEach(triangulation.getTriangles(), function(tri, i, arr) { @@ -96,23 +149,28 @@ ol.reproj.renderTriangles = function(context, var u0 = tgt[0][0] - targetTL[0], v0 = -(tgt[0][1] - targetTL[1]), u1 = tgt[1][0] - targetTL[0], v1 = -(tgt[1][1] - targetTL[1]), u2 = tgt[2][0] - targetTL[0], v2 = -(tgt[2][1] - targetTL[1]); - if (tri.needsShift && !goog.isNull(shiftDistance)) { - x0 = goog.math.modulo(x0, shiftDistance); - x1 = goog.math.modulo(x1, shiftDistance); - x2 = goog.math.modulo(x2, shiftDistance); + + var performIndividualWrapXShift = !performGlobalWrapXShift && + (wrapXShiftNeeded && + (Math.max(x0, x1, x2) - Math.min(x0, x1, x2)) > wrapXShiftDistance / 2); + + if (performGlobalWrapXShift || performIndividualWrapXShift) { + 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, because it should achieve reasonable results // but any values could actually be chosen. - var srcShiftX = x0, srcShiftY = y0; + var srcNumericalShiftX = x0, srcNumericalShiftY = y0; x0 = 0; y0 = 0; - x1 -= srcShiftX; - y1 -= srcShiftY; - x2 -= srcShiftX; - y2 -= srcShiftY; + x1 -= srcNumericalShiftX; + y1 -= srcNumericalShiftY; + x2 -= srcNumericalShiftX; + y2 -= srcNumericalShiftY; var augmentedMatrix = [ [x0, y0, 1, 0, 0, 0, u0 / targetResolution], @@ -155,27 +213,22 @@ ol.reproj.renderTriangles = function(context, context.closePath(); context.clip(); - goog.array.forEach(sources, function(src, i, arr) { - context.save(); - var dataTL = ol.extent.getTopLeft(src.extent); - context.translate(dataTL[0] - srcShiftX, dataTL[1] - srcShiftY); + context.save(); + context.translate(srcDataExtent[0] - srcNumericalShiftX, + srcDataExtent[3] - srcNumericalShiftY); - // if the triangle needs to be shifted (because of the dateline wrapping), - // shift back only the source images that need it - if (tri.needsShift && !goog.isNull(shiftDistance) && - dataTL[0] < shiftThreshold) { - context.translate(shiftDistance, 0); - } - context.scale(sourceResolution, -sourceResolution); + context.scale(sourceResolution, -sourceResolution); - // the image has to be scaled by half a pixel in every direction - // in order to prevent artifacts between the original tiles - // that are introduced by the canvas antialiasing. - context.drawImage(src.image, -0.5, -0.5, - src.image.width + 1, src.image.height + 1); + context.drawImage(stitchContext.canvas, 0, 0); - context.restore(); - }); + if (performIndividualWrapXShift) { + // It was not possible to solve the wrapX shifting during stitching -> + // render the data second time (shifted) to solve the wrapX. + context.translate(wrapXShiftDistance / sourceResolution, 0); + context.drawImage(stitchContext.canvas, 0, 0); + } + + context.restore(); if (goog.DEBUG) { context.strokeStyle = 'black'; diff --git a/src/ol/reproj/triangulation.js b/src/ol/reproj/triangulation.js index 7e19ab844c..384297ef44 100644 --- a/src/ol/reproj/triangulation.js +++ b/src/ol/reproj/triangulation.js @@ -8,13 +8,9 @@ goog.require('ol.proj'); /** * Single triangle; consists of 3 source points and 3 target points. - * `needsShift` can be used to indicate that the whole triangle has to be - * shifted during reprojection. This is needed for triangles crossing edges - * of the source projection (dateline). * * @typedef {{source: Array., - * target: Array., - * needsShift: boolean}} + * target: Array.}} */ ol.reproj.Triangle; @@ -72,12 +68,19 @@ ol.reproj.Triangulation = function(sourceProj, targetProj, targetExtent, this.triangles_ = []; /** - * Indicates that _any_ of the triangles has to be shifted during - * reprojection. See {@link ol.reproj.Triangle}. + * @type {ol.Extent} + * @private + */ + this.trianglesSourceExtent_ = null; + + /** + * Indicates that source coordinates has to be shifted during reprojection. + * This is needed when the triangulation crosses + * edge of the source projection (dateline). * @type {boolean} * @private */ - this.needsShift_ = false; + this.wrapsXInSource_ = false; /** * @type {number} @@ -108,24 +111,14 @@ ol.reproj.Triangulation = function(sourceProj, targetProj, targetExtent, * @param {ol.Coordinate} aSrc * @param {ol.Coordinate} bSrc * @param {ol.Coordinate} cSrc - * @param {boolean} wrapsX * @private */ ol.reproj.Triangulation.prototype.addTriangle_ = function(a, b, c, - aSrc, bSrc, cSrc, wrapsX) { - // wrapsX could be determined by transforming centroid of the target triangle. - // If the transformed centroid is outside the transformed triangle, - // the triangle wraps around projection extent. - // However, this would require additional transformation call. - + aSrc, bSrc, cSrc) { this.triangles_.push({ source: [aSrc, bSrc, cSrc], - target: [a, b, c], - needsShift: wrapsX + target: [a, b, c] }); - if (wrapsX) { - this.needsShift_ = true; - } }; @@ -205,8 +198,12 @@ ol.reproj.Triangulation.prototype.addQuadIfValid_ = function(a, b, c, d, } } - this.addTriangle_(a, c, d, aSrc, cSrc, dSrc, wrapsX); - this.addTriangle_(a, b, c, aSrc, bSrc, cSrc, wrapsX); + if (wrapsX) { + this.wrapsXInSource_ = true; + } + + this.addTriangle_(a, c, d, aSrc, cSrc, dSrc); + this.addTriangle_(a, b, c, aSrc, bSrc, cSrc); }; @@ -214,9 +211,13 @@ ol.reproj.Triangulation.prototype.addQuadIfValid_ = function(a, b, c, d, * @return {ol.Extent} */ ol.reproj.Triangulation.prototype.calculateSourceExtent = function() { + if (!goog.isNull(this.trianglesSourceExtent_)) { + return this.trianglesSourceExtent_; + } + var extent = ol.extent.createEmpty(); - if (this.needsShift_) { + if (this.wrapsXInSource_) { // although only some of the triangles are crossing the dateline, // all coordiantes need to be "shifted" to be positive // to properly calculate the extent (and then possibly shifted back) @@ -244,10 +245,19 @@ ol.reproj.Triangulation.prototype.calculateSourceExtent = function() { }); } + this.trianglesSourceExtent_ = extent; return extent; }; +/** + * @return {boolean} + */ +ol.reproj.Triangulation.prototype.getWrapsXInSource = function() { + return this.wrapsXInSource_; +}; + + /** * @return {Array.} */