Reprojection code refactoring and cleaning

This commit is contained in:
Petr Sloup
2015-09-02 15:38:09 +02:00
parent a2ae2f0f3e
commit 7a1533925a
4 changed files with 116 additions and 113 deletions

View File

@@ -5,7 +5,6 @@ goog.require('goog.events');
goog.require('goog.events.EventType'); goog.require('goog.events.EventType');
goog.require('ol.ImageBase'); goog.require('ol.ImageBase');
goog.require('ol.ImageState'); goog.require('ol.ImageState');
goog.require('ol.dom');
goog.require('ol.extent'); goog.require('ol.extent');
goog.require('ol.proj'); goog.require('ol.proj');
goog.require('ol.reproj'); goog.require('ol.reproj');
@@ -70,9 +69,6 @@ ol.reproj.Image = function(sourceProj, targetProj,
*/ */
this.srcImage_ = getImageFunction(srcExtent, sourceResolution, pixelRatio); this.srcImage_ = getImageFunction(srcExtent, sourceResolution, pixelRatio);
var width = ol.extent.getWidth(targetExtent) / targetResolution;
var height = ol.extent.getHeight(targetExtent) / targetResolution;
/** /**
* @private * @private
* @type {number} * @type {number}
@@ -80,21 +76,11 @@ ol.reproj.Image = function(sourceProj, targetProj,
this.srcPixelRatio_ = this.srcPixelRatio_ =
!goog.isNull(this.srcImage_) ? this.srcImage_.getPixelRatio() : 1; !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 * @private
* @type {HTMLCanvasElement} * @type {HTMLCanvasElement}
*/ */
this.canvas_ = this.context_.canvas; this.canvas_ = null;
/** /**
* @private * @private
@@ -142,13 +128,16 @@ ol.reproj.Image.prototype.getImage = function(opt_context) {
ol.reproj.Image.prototype.reproject_ = function() { ol.reproj.Image.prototype.reproject_ = function() {
var srcState = this.srcImage_.getState(); var srcState = this.srcImage_.getState();
if (srcState == ol.ImageState.LOADED) { if (srcState == ol.ImageState.LOADED) {
// render the reprojected content var width = ol.extent.getWidth(this.targetExtent_) / this.targetResolution_;
ol.reproj.renderTriangles(this.context_, var height =
ol.extent.getHeight(this.targetExtent_) / this.targetResolution_;
this.canvas_ = ol.reproj.render(width, height, this.srcPixelRatio_,
this.srcImage_.getResolution(), this.maxSourceExtent_, this.srcImage_.getResolution(), this.maxSourceExtent_,
this.targetResolution_, this.targetExtent_, this.triangulation_, [{ this.targetResolution_, this.targetExtent_, this.triangulation_, [{
extent: this.srcImage_.getExtent(), extent: this.srcImage_.getExtent(),
image: this.srcImage_.getImage() image: this.srcImage_.getImage()
}], this.srcPixelRatio_); }]);
} }
this.state = srcState; this.state = srcState;
this.changed(); this.changed();

View File

@@ -10,6 +10,20 @@ goog.require('ol.math');
goog.require('ol.proj'); 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 * Calculates ideal resolution to use from the source in order to achieve
* pixel mapping as close as possible to 1:1 during reprojection. * 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 sourceCenter = ol.proj.transform(targetCenter, targetProj, sourceProj);
var targetMPU = targetProj.getMetersPerUnit();
var sourceMPU = sourceProj.getMetersPerUnit();
// calculate the ideal resolution of the source data // calculate the ideal resolution of the source data
var sourceResolution = var sourceResolution =
targetProj.getPointResolution(targetResolution, targetCenter); 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 // coordinates may be slightly different. We need to reverse-compensate this
// in order to achieve optimal results. // 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. * Enlarge the clipping triangle point by 1 pixel to ensure the edges overlap
* @enum {number} * 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 * @private
*/ */
ol.reproj.WrapXRendering_ = { ol.reproj.enlargeClipPoint_ = function(centroidX, centroidY, u, v) {
NONE: 0, var dX = u - centroidX, dY = v - centroidY;
STITCH_SHIFT: 1, var distance = Math.sqrt(dX * dX + dY * dY);
STITCH_EXTENDED: 2 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. * 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 {number} sourceResolution
* @param {ol.Extent} sourceExtent * @param {ol.Extent} sourceExtent
* @param {number} targetResolution * @param {number} targetResolution
@@ -88,12 +99,21 @@ ol.reproj.browserAntialiasesClip_ = !goog.labs.userAgent.browser.isChrome() ||
* @param {ol.reproj.Triangulation} triangulation * @param {ol.reproj.Triangulation} triangulation
* @param {Array.<{extent: ol.Extent, * @param {Array.<{extent: ol.Extent,
* image: (HTMLCanvasElement|Image)}>} sources * image: (HTMLCanvasElement|Image)}>} sources
* @param {number} sourcePixelRatio
* @param {boolean=} opt_renderEdges * @param {boolean=} opt_renderEdges
* @return {HTMLCanvasElement}
*/ */
ol.reproj.renderTriangles = function(context, ol.reproj.render = function(width, height, pixelRatio,
sourceResolution, sourceExtent, targetResolution, targetExtent, 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) ? var wrapXShiftDistance = !goog.isNull(sourceExtent) ?
ol.extent.getWidth(sourceExtent) : 0; ol.extent.getWidth(sourceExtent) : 0;
@@ -148,11 +168,11 @@ ol.reproj.renderTriangles = function(context,
} }
var stitchContext = ol.dom.createCanvasContext2D( var stitchContext = ol.dom.createCanvasContext2D(
Math.round(sourcePixelRatio * canvasWidthInUnits / sourceResolution), Math.round(pixelRatio * canvasWidthInUnits / sourceResolution),
Math.round(sourcePixelRatio * srcDataHeight / sourceResolution)); Math.round(pixelRatio * srcDataHeight / sourceResolution));
stitchContext.scale(sourcePixelRatio / sourceResolution, stitchContext.scale(pixelRatio / sourceResolution,
sourcePixelRatio / sourceResolution); pixelRatio / sourceResolution);
stitchContext.translate(-srcDataExtent[0], srcDataExtent[3]); stitchContext.translate(-srcDataExtent[0], srcDataExtent[3]);
goog.array.forEach(sources, function(src, i, arr) { 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, var u2 = (tgt[2][0] - targetTL[0]) / targetResolution,
v2 = -(tgt[2][1] - targetTL[1]) / targetResolution; v2 = -(tgt[2][1] - targetTL[1]) / targetResolution;
var performWrapXShift = false; var performWrapXShift = wrapXType == ol.reproj.WrapXRendering_.STITCH_SHIFT;
if (wrapXType == ol.reproj.WrapXRendering_.STITCH_SHIFT) { if (wrapXType == ol.reproj.WrapXRendering_.STITCH_EXTENDED) {
performWrapXShift = true;
} else if (wrapXType == ol.reproj.WrapXRendering_.STITCH_EXTENDED) {
var minX = Math.min(x0, x1, x2); var minX = Math.min(x0, x1, x2);
var maxX = Math.max(x0, x1, x2); var maxX = Math.max(x0, x1, x2);
performWrapXShift = (maxX - minX) > wrapXShiftDistance / 2 || performWrapXShift = minX <= sourceExtent[0] ||
minX <= sourceExtent[0]; (maxX - minX) > wrapXShiftDistance / 2;
} }
if (performWrapXShift) { if (performWrapXShift) {
@@ -249,18 +267,10 @@ ol.reproj.renderTriangles = function(context,
context.beginPath(); context.beginPath();
if (ol.reproj.browserAntialiasesClip_) { 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 centroidX = (u0 + u1 + u2) / 3, centroidY = (v0 + v1 + v2) / 3;
var calcClipPoint = function(u, v) { var p0 = ol.reproj.enlargeClipPoint_(centroidX, centroidY, u0, v0);
var dX = u - centroidX, dY = v - centroidY; var p1 = ol.reproj.enlargeClipPoint_(centroidX, centroidY, u1, v1);
var distance = Math.sqrt(dX * dX + dY * dY); var p2 = ol.reproj.enlargeClipPoint_(centroidX, centroidY, u2, v2);
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);
context.moveTo(p0[0], p0[1]); context.moveTo(p0[0], p0[1]);
context.lineTo(p1[0], p1[1]); context.lineTo(p1[0], p1[1]);
@@ -278,8 +288,8 @@ ol.reproj.renderTriangles = function(context,
context.translate(srcDataExtent[0] - srcNumericalShiftX, context.translate(srcDataExtent[0] - srcNumericalShiftX,
srcDataExtent[3] - srcNumericalShiftY); srcDataExtent[3] - srcNumericalShiftY);
context.scale(sourceResolution / sourcePixelRatio, context.scale(sourceResolution / pixelRatio,
-sourceResolution / sourcePixelRatio); -sourceResolution / pixelRatio);
context.drawImage(stitchContext.canvas, 0, 0); context.drawImage(stitchContext.canvas, 0, 0);
context.restore(); context.restore();
@@ -311,4 +321,17 @@ ol.reproj.renderTriangles = function(context,
context.restore(); 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
}; };

View File

@@ -8,7 +8,6 @@ goog.require('goog.math');
goog.require('goog.object'); goog.require('goog.object');
goog.require('ol.Tile'); goog.require('ol.Tile');
goog.require('ol.TileState'); goog.require('ol.TileState');
goog.require('ol.dom');
goog.require('ol.extent'); goog.require('ol.extent');
goog.require('ol.proj'); goog.require('ol.proj');
goog.require('ol.reproj'); goog.require('ol.reproj');
@@ -247,28 +246,20 @@ ol.reproj.Tile.prototype.reproject_ = function() {
} }
}, this); }, this);
// create the canvas
var tileCoord = this.getTileCoord(); var tileCoord = this.getTileCoord();
var z = tileCoord[0]; var z = tileCoord[0];
var size = this.targetTileGrid_.getTileSize(z); 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 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_);
if (sources.length > 0) {
var targetExtent = this.targetTileGrid_.getTileCoordExtent(tileCoord); var targetExtent = this.targetTileGrid_.getTileCoordExtent(tileCoord);
ol.reproj.renderTriangles(context, this.canvas_ = ol.reproj.render(width, height, this.pixelRatio_,
srcResolution, this.sourceTileGrid_.getExtent(), sourceResolution, this.sourceTileGrid_.getExtent(),
targetResolution, targetExtent, this.triangulation_, sources, targetResolution, targetExtent, this.triangulation_, sources,
this.pixelRatio_, this.renderEdges_); this.renderEdges_);
}
this.canvas_ = context.canvas;
this.state = ol.TileState.LOADED; this.state = ol.TileState.LOADED;
this.changed(); this.changed();
}; };
@@ -283,14 +274,6 @@ ol.reproj.Tile.prototype.load = function() {
this.changed(); this.changed();
var leftToLoad = 0; 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_), goog.asserts.assert(goog.isNull(this.sourcesListenerKeys_),
'this.sourcesListenerKeys_ should be null'); 'this.sourcesListenerKeys_ should be null');
@@ -308,10 +291,16 @@ ol.reproj.Tile.prototype.load = function() {
if (state == ol.TileState.LOADED || if (state == ol.TileState.LOADED ||
state == ol.TileState.ERROR || state == ol.TileState.ERROR ||
state == ol.TileState.EMPTY) { state == ol.TileState.EMPTY) {
onSingleSourceLoaded();
goog.events.unlistenByKey(sourceListenKey); 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.sourcesListenerKeys_.push(sourceListenKey);
} }
}, this); }, this);

View File

@@ -120,7 +120,7 @@ ol.reproj.Triangulation = function(sourceProj, targetProj, targetExtent,
var brDstSrc = this.transformInv_(brDst); var brDstSrc = this.transformInv_(brDst);
var blDstSrc = this.transformInv_(blDst); var blDstSrc = this.transformInv_(blDst);
this.addQuadIfValid_(tlDst, trDst, brDst, blDst, this.addQuad_(tlDst, trDst, brDst, blDst,
tlDstSrc, trDstSrc, brDstSrc, blDstSrc, tlDstSrc, trDstSrc, brDstSrc, blDstSrc,
ol.RASTER_REPROJ_MAX_SUBDIVISION); ol.RASTER_REPROJ_MAX_SUBDIVISION);
@@ -161,7 +161,7 @@ ol.reproj.Triangulation.prototype.addTriangle_ = function(a, b, c,
* @param {number} maxSubdiv Maximal allowed subdivision of the quad. * @param {number} maxSubdiv Maximal allowed subdivision of the quad.
* @private * @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) { aSrc, bSrc, cSrc, dSrc, maxSubdiv) {
var srcQuadExtent = ol.extent.boundingExtent([aSrc, bSrc, cSrc, dSrc]); 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 (needsSubdivision) {
if (Math.abs(a[0] - c[0]) <= Math.abs(a[1] - c[1])) { 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 bc = [(b[0] + c[0]) / 2, (b[1] + c[1]) / 2];
var bcSrc = this.transformInv_(bc); var bcSrc = this.transformInv_(bc);
var da = [(d[0] + a[0]) / 2, (d[1] + a[1]) / 2]; var da = [(d[0] + a[0]) / 2, (d[1] + a[1]) / 2];
var daSrc = this.transformInv_(da); var daSrc = this.transformInv_(da);
this.addQuadIfValid_(a, b, bc, da, this.addQuad_(a, b, bc, da, aSrc, bSrc, bcSrc, daSrc, maxSubdiv - 1);
aSrc, bSrc, bcSrc, daSrc, maxSubdiv - 1); this.addQuad_(da, bc, c, d, daSrc, bcSrc, cSrc, dSrc, maxSubdiv - 1);
this.addQuadIfValid_(da, bc, c, d,
daSrc, bcSrc, cSrc, dSrc, maxSubdiv - 1);
} else { } else {
// split vertically (left & right)
var ab = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2]; var ab = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
var abSrc = this.transformInv_(ab); var abSrc = this.transformInv_(ab);
var cd = [(c[0] + d[0]) / 2, (c[1] + d[1]) / 2]; var cd = [(c[0] + d[0]) / 2, (c[1] + d[1]) / 2];
var cdSrc = this.transformInv_(cd); var cdSrc = this.transformInv_(cd);
this.addQuadIfValid_(a, ab, cd, d, this.addQuad_(a, ab, cd, d, aSrc, abSrc, cdSrc, dSrc, maxSubdiv - 1);
aSrc, abSrc, cdSrc, dSrc, maxSubdiv - 1); this.addQuad_(ab, b, c, cd, abSrc, bSrc, cSrc, cdSrc, maxSubdiv - 1);
this.addQuadIfValid_(ab, b, c, cd,
abSrc, bSrc, cSrc, cdSrc, maxSubdiv - 1);
} }
return; return;
} }
@@ -293,8 +291,12 @@ ol.reproj.Triangulation.prototype.calculateSourceExtent = function() {
var sourceProjExtent = this.sourceProj_.getExtent(); var sourceProjExtent = this.sourceProj_.getExtent();
var right = sourceProjExtent[2]; var right = sourceProjExtent[2];
if (extent[0] > right) extent[0] -= this.sourceWorldWidth_; if (extent[0] > right) {
if (extent[2] > right) extent[2] -= this.sourceWorldWidth_; extent[0] -= this.sourceWorldWidth_;
}
if (extent[2] > right) {
extent[2] -= this.sourceWorldWidth_;
}
} else { } else {
goog.array.forEach(this.triangles_, function(triangle, i, arr) { goog.array.forEach(this.triangles_, function(triangle, i, arr) {
var src = triangle.source; var src = triangle.source;