diff --git a/src/ol/reproj/image.js b/src/ol/reproj/image.js index 4a81022e77..c7e583951d 100644 --- a/src/ol/reproj/image.js +++ b/src/ol/reproj/image.js @@ -5,7 +5,6 @@ goog.require('goog.events'); goog.require('goog.events.EventType'); goog.require('ol.ImageBase'); goog.require('ol.ImageState'); -goog.require('ol.dom'); goog.require('ol.extent'); goog.require('ol.proj'); goog.require('ol.reproj'); @@ -70,9 +69,6 @@ ol.reproj.Image = function(sourceProj, targetProj, */ this.srcImage_ = getImageFunction(srcExtent, sourceResolution, pixelRatio); - var width = ol.extent.getWidth(targetExtent) / targetResolution; - var height = ol.extent.getHeight(targetExtent) / targetResolution; - /** * @private * @type {number} @@ -80,21 +76,11 @@ ol.reproj.Image = function(sourceProj, targetProj, this.srcPixelRatio_ = !goog.isNull(this.srcImage_) ? this.srcImage_.getPixelRatio() : 1; - /** - * @private - * @type {CanvasRenderingContext2D} - */ - this.context_ = ol.dom.createCanvasContext2D( - Math.round(this.srcPixelRatio_ * width), - Math.round(this.srcPixelRatio_ * height)); - this.context_.imageSmoothingEnabled = true; - this.context_.scale(this.srcPixelRatio_, this.srcPixelRatio_); - /** * @private * @type {HTMLCanvasElement} */ - this.canvas_ = this.context_.canvas; + this.canvas_ = null; /** * @private @@ -142,13 +128,16 @@ ol.reproj.Image.prototype.getImage = function(opt_context) { ol.reproj.Image.prototype.reproject_ = function() { var srcState = this.srcImage_.getState(); if (srcState == ol.ImageState.LOADED) { - // render the reprojected content - ol.reproj.renderTriangles(this.context_, + var width = ol.extent.getWidth(this.targetExtent_) / this.targetResolution_; + var height = + ol.extent.getHeight(this.targetExtent_) / this.targetResolution_; + + this.canvas_ = ol.reproj.render(width, height, this.srcPixelRatio_, this.srcImage_.getResolution(), this.maxSourceExtent_, this.targetResolution_, this.targetExtent_, this.triangulation_, [{ extent: this.srcImage_.getExtent(), image: this.srcImage_.getImage() - }], this.srcPixelRatio_); + }]); } this.state = srcState; this.changed(); diff --git a/src/ol/reproj/reproj.js b/src/ol/reproj/reproj.js index db42c15f68..0b1af19ee2 100644 --- a/src/ol/reproj/reproj.js +++ b/src/ol/reproj/reproj.js @@ -10,6 +10,20 @@ goog.require('ol.math'); goog.require('ol.proj'); +/** + * We need to employ more sophisticated solution + * if the web browser antialiases clipping edges on canvas. + * + * Currently only Chrome does not antialias the edges, but this is probably + * going to be "fixed" in the future: http://crbug.com/424291 + * + * @type {boolean} + * @private + */ +ol.reproj.browserAntialiasesClip_ = !goog.labs.userAgent.browser.isChrome() || + goog.labs.userAgent.platform.isIos(); + + /** * Calculates ideal resolution to use from the source in order to achieve * pixel mapping as close as possible to 1:1 during reprojection. @@ -27,16 +41,20 @@ ol.reproj.calculateSourceResolution = function(sourceProj, targetProj, var sourceCenter = ol.proj.transform(targetCenter, targetProj, sourceProj); - var targetMPU = targetProj.getMetersPerUnit(); - var sourceMPU = sourceProj.getMetersPerUnit(); - // calculate the ideal resolution of the source data var sourceResolution = targetProj.getPointResolution(targetResolution, targetCenter); - if (goog.isDef(targetMPU)) sourceResolution *= targetMPU; - if (goog.isDef(sourceMPU)) sourceResolution /= sourceMPU; - // based on the projection properties, the point resolution at the specified + var targetMPU = targetProj.getMetersPerUnit(); + if (goog.isDef(targetMPU)) { + sourceResolution *= targetMPU; + } + var sourceMPU = sourceProj.getMetersPerUnit(); + if (goog.isDef(sourceMPU)) { + sourceResolution /= sourceMPU; + } + + // Based on the projection properties, the point resolution at the specified // coordinates may be slightly different. We need to reverse-compensate this // in order to achieve optimal results. @@ -53,34 +71,27 @@ ol.reproj.calculateSourceResolution = function(sourceProj, targetProj, /** - * Type of solution used to solve the wrapX issue. - * @enum {number} + * Enlarge the clipping triangle point by 1 pixel to ensure the edges overlap + * in order to mask gaps caused by antialiasing. + * @param {number} centroidX Centroid of the triangle. + * @param {number} centroidY Centroid of the triangle. + * @param {number} u + * @param {number} v + * @return {ol.Coordinate} * @private */ -ol.reproj.WrapXRendering_ = { - NONE: 0, - STITCH_SHIFT: 1, - STITCH_EXTENDED: 2 +ol.reproj.enlargeClipPoint_ = function(centroidX, centroidY, u, v) { + var dX = u - centroidX, dY = v - centroidY; + var distance = Math.sqrt(dX * dX + dY * dY); + return [Math.round(u + dX / distance), Math.round(v + dY / distance)]; }; -/** - * We need to employ more sophisticated solution - * if the web browser antialiases clipping edges on canvas. - * - * Currently only Chrome does not antialias the edges, but this is probably - * going to be "fixed" in the future: http://crbug.com/424291 - * - * @type {boolean} - * @private - */ -ol.reproj.browserAntialiasesClip_ = !goog.labs.userAgent.browser.isChrome() || - goog.labs.userAgent.platform.isIos(); - - /** * Renders the source into the canvas based on the triangulation. - * @param {CanvasRenderingContext2D} context + * @param {number} width + * @param {number} height + * @param {number} pixelRatio * @param {number} sourceResolution * @param {ol.Extent} sourceExtent * @param {number} targetResolution @@ -88,12 +99,21 @@ ol.reproj.browserAntialiasesClip_ = !goog.labs.userAgent.browser.isChrome() || * @param {ol.reproj.Triangulation} triangulation * @param {Array.<{extent: ol.Extent, * image: (HTMLCanvasElement|Image)}>} sources - * @param {number} sourcePixelRatio * @param {boolean=} opt_renderEdges + * @return {HTMLCanvasElement} */ -ol.reproj.renderTriangles = function(context, +ol.reproj.render = function(width, height, pixelRatio, sourceResolution, sourceExtent, targetResolution, targetExtent, - triangulation, sources, sourcePixelRatio, opt_renderEdges) { + triangulation, sources, opt_renderEdges) { + + var context = ol.dom.createCanvasContext2D(Math.round(pixelRatio * width), + Math.round(pixelRatio * height)); + + if (sources.length === 0) { + return context.canvas; + } + + context.scale(pixelRatio, pixelRatio); var wrapXShiftDistance = !goog.isNull(sourceExtent) ? ol.extent.getWidth(sourceExtent) : 0; @@ -148,11 +168,11 @@ ol.reproj.renderTriangles = function(context, } var stitchContext = ol.dom.createCanvasContext2D( - Math.round(sourcePixelRatio * canvasWidthInUnits / sourceResolution), - Math.round(sourcePixelRatio * srcDataHeight / sourceResolution)); + Math.round(pixelRatio * canvasWidthInUnits / sourceResolution), + Math.round(pixelRatio * srcDataHeight / sourceResolution)); - stitchContext.scale(sourcePixelRatio / sourceResolution, - sourcePixelRatio / sourceResolution); + stitchContext.scale(pixelRatio / sourceResolution, + pixelRatio / sourceResolution); stitchContext.translate(-srcDataExtent[0], srcDataExtent[3]); goog.array.forEach(sources, function(src, i, arr) { @@ -208,15 +228,13 @@ ol.reproj.renderTriangles = function(context, var u2 = (tgt[2][0] - targetTL[0]) / targetResolution, v2 = -(tgt[2][1] - targetTL[1]) / targetResolution; - var performWrapXShift = false; - if (wrapXType == ol.reproj.WrapXRendering_.STITCH_SHIFT) { - performWrapXShift = true; - } else if (wrapXType == ol.reproj.WrapXRendering_.STITCH_EXTENDED) { + 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 = (maxX - minX) > wrapXShiftDistance / 2 || - minX <= sourceExtent[0]; + performWrapXShift = minX <= sourceExtent[0] || + (maxX - minX) > wrapXShiftDistance / 2; } if (performWrapXShift) { @@ -249,18 +267,10 @@ ol.reproj.renderTriangles = function(context, context.beginPath(); if (ol.reproj.browserAntialiasesClip_) { - // Enlarge the clipping triangle by 1 pixel to ensure the edges overlap - // in order to mask gaps caused by antialiasing. var centroidX = (u0 + u1 + u2) / 3, centroidY = (v0 + v1 + v2) / 3; - var calcClipPoint = function(u, v) { - var dX = u - centroidX, dY = v - centroidY; - var distance = Math.sqrt(dX * dX + dY * dY); - return [Math.round(u + dX / distance), Math.round(v + dY / distance)]; - }; - - var p0 = calcClipPoint(u0, v0); - var p1 = calcClipPoint(u1, v1); - var p2 = calcClipPoint(u2, v2); + var p0 = ol.reproj.enlargeClipPoint_(centroidX, centroidY, u0, v0); + var p1 = ol.reproj.enlargeClipPoint_(centroidX, centroidY, u1, v1); + var p2 = ol.reproj.enlargeClipPoint_(centroidX, centroidY, u2, v2); context.moveTo(p0[0], p0[1]); context.lineTo(p1[0], p1[1]); @@ -278,8 +288,8 @@ ol.reproj.renderTriangles = function(context, context.translate(srcDataExtent[0] - srcNumericalShiftX, srcDataExtent[3] - srcNumericalShiftY); - context.scale(sourceResolution / sourcePixelRatio, - -sourceResolution / sourcePixelRatio); + context.scale(sourceResolution / pixelRatio, + -sourceResolution / pixelRatio); context.drawImage(stitchContext.canvas, 0, 0); context.restore(); @@ -311,4 +321,17 @@ ol.reproj.renderTriangles = function(context, context.restore(); } + 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 17ed887718..650b7ab092 100644 --- a/src/ol/reproj/tile.js +++ b/src/ol/reproj/tile.js @@ -8,7 +8,6 @@ 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'); @@ -247,28 +246,20 @@ ol.reproj.Tile.prototype.reproject_ = function() { } }, this); - // create the canvas var tileCoord = this.getTileCoord(); var z = tileCoord[0]; var size = this.targetTileGrid_.getTileSize(z); + var width = goog.isNumber(size) ? size : size[0]; + var height = goog.isNumber(size) ? size : size[1]; var targetResolution = this.targetTileGrid_.getResolution(z); - var srcResolution = this.sourceTileGrid_.getResolution(this.srcZ_); + var sourceResolution = this.sourceTileGrid_.getResolution(this.srcZ_); - var width = this.pixelRatio_ * (goog.isNumber(size) ? size : size[0]); - var height = this.pixelRatio_ * (goog.isNumber(size) ? size : size[1]); - var context = ol.dom.createCanvasContext2D(width, height); - context.imageSmoothingEnabled = true; - context.scale(this.pixelRatio_, this.pixelRatio_); + var targetExtent = this.targetTileGrid_.getTileCoordExtent(tileCoord); + this.canvas_ = ol.reproj.render(width, height, this.pixelRatio_, + sourceResolution, this.sourceTileGrid_.getExtent(), + targetResolution, targetExtent, this.triangulation_, sources, + this.renderEdges_); - if (sources.length > 0) { - var targetExtent = this.targetTileGrid_.getTileCoordExtent(tileCoord); - ol.reproj.renderTriangles(context, - srcResolution, this.sourceTileGrid_.getExtent(), - targetResolution, targetExtent, this.triangulation_, sources, - this.pixelRatio_, this.renderEdges_); - } - - this.canvas_ = context.canvas; this.state = ol.TileState.LOADED; this.changed(); }; @@ -283,14 +274,6 @@ ol.reproj.Tile.prototype.load = function() { 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'); @@ -308,10 +291,16 @@ ol.reproj.Tile.prototype.load = function() { if (state == ol.TileState.LOADED || state == ol.TileState.ERROR || state == ol.TileState.EMPTY) { - onSingleSourceLoaded(); goog.events.unlistenByKey(sourceListenKey); + leftToLoad--; + goog.asserts.assert(leftToLoad >= 0, + 'leftToLoad should not be negative'); + if (leftToLoad <= 0) { + this.unlistenSources_(); + this.reproject_(); + } } - }); + }, false, this); this.sourcesListenerKeys_.push(sourceListenKey); } }, this); diff --git a/src/ol/reproj/triangulation.js b/src/ol/reproj/triangulation.js index 4b34d172b5..ee4a143220 100644 --- a/src/ol/reproj/triangulation.js +++ b/src/ol/reproj/triangulation.js @@ -120,9 +120,9 @@ ol.reproj.Triangulation = function(sourceProj, targetProj, targetExtent, var brDstSrc = this.transformInv_(brDst); var blDstSrc = this.transformInv_(blDst); - this.addQuadIfValid_(tlDst, trDst, brDst, blDst, - tlDstSrc, trDstSrc, brDstSrc, blDstSrc, - ol.RASTER_REPROJ_MAX_SUBDIVISION); + this.addQuad_(tlDst, trDst, brDst, blDst, + tlDstSrc, trDstSrc, brDstSrc, blDstSrc, + ol.RASTER_REPROJ_MAX_SUBDIVISION); transformInvCache = {}; }; @@ -139,7 +139,7 @@ ol.reproj.Triangulation = function(sourceProj, targetProj, targetExtent, * @private */ ol.reproj.Triangulation.prototype.addTriangle_ = function(a, b, c, - aSrc, bSrc, cSrc) { + aSrc, bSrc, cSrc) { this.triangles_.push({ source: [aSrc, bSrc, cSrc], target: [a, b, c] @@ -161,7 +161,7 @@ ol.reproj.Triangulation.prototype.addTriangle_ = function(a, b, c, * @param {number} maxSubdiv Maximal allowed subdivision of the quad. * @private */ -ol.reproj.Triangulation.prototype.addQuadIfValid_ = function(a, b, c, d, +ol.reproj.Triangulation.prototype.addQuad_ = function(a, b, c, d, aSrc, bSrc, cSrc, dSrc, maxSubdiv) { var srcQuadExtent = ol.extent.boundingExtent([aSrc, bSrc, cSrc, dSrc]); @@ -229,25 +229,23 @@ ol.reproj.Triangulation.prototype.addQuadIfValid_ = function(a, b, c, d, } if (needsSubdivision) { if (Math.abs(a[0] - c[0]) <= Math.abs(a[1] - c[1])) { + // split horizontally (top & bottom) var bc = [(b[0] + c[0]) / 2, (b[1] + c[1]) / 2]; var bcSrc = this.transformInv_(bc); var da = [(d[0] + a[0]) / 2, (d[1] + a[1]) / 2]; var daSrc = this.transformInv_(da); - this.addQuadIfValid_(a, b, bc, da, - aSrc, bSrc, bcSrc, daSrc, maxSubdiv - 1); - this.addQuadIfValid_(da, bc, c, d, - daSrc, bcSrc, cSrc, dSrc, maxSubdiv - 1); + this.addQuad_(a, b, bc, da, aSrc, bSrc, bcSrc, daSrc, maxSubdiv - 1); + this.addQuad_(da, bc, c, d, daSrc, bcSrc, cSrc, dSrc, maxSubdiv - 1); } else { + // split vertically (left & right) var ab = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2]; var abSrc = this.transformInv_(ab); var cd = [(c[0] + d[0]) / 2, (c[1] + d[1]) / 2]; var cdSrc = this.transformInv_(cd); - this.addQuadIfValid_(a, ab, cd, d, - aSrc, abSrc, cdSrc, dSrc, maxSubdiv - 1); - this.addQuadIfValid_(ab, b, c, cd, - abSrc, bSrc, cSrc, cdSrc, maxSubdiv - 1); + this.addQuad_(a, ab, cd, d, aSrc, abSrc, cdSrc, dSrc, maxSubdiv - 1); + this.addQuad_(ab, b, c, cd, abSrc, bSrc, cSrc, cdSrc, maxSubdiv - 1); } return; } @@ -293,8 +291,12 @@ ol.reproj.Triangulation.prototype.calculateSourceExtent = function() { 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_; + if (extent[0] > right) { + extent[0] -= this.sourceWorldWidth_; + } + if (extent[2] > right) { + extent[2] -= this.sourceWorldWidth_; + } } else { goog.array.forEach(this.triangles_, function(triangle, i, arr) { var src = triangle.source;