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('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();

View File

@@ -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
};

View File

@@ -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_);
if (sources.length > 0) {
var targetExtent = this.targetTileGrid_.getTileCoordExtent(tileCoord);
ol.reproj.renderTriangles(context,
srcResolution, this.sourceTileGrid_.getExtent(),
this.canvas_ = ol.reproj.render(width, height, this.pixelRatio_,
sourceResolution, this.sourceTileGrid_.getExtent(),
targetResolution, targetExtent, this.triangulation_, sources,
this.pixelRatio_, this.renderEdges_);
}
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);

View File

@@ -120,7 +120,7 @@ ol.reproj.Triangulation = function(sourceProj, targetProj, targetExtent,
var brDstSrc = this.transformInv_(brDst);
var blDstSrc = this.transformInv_(blDst);
this.addQuadIfValid_(tlDst, trDst, brDst, blDst,
this.addQuad_(tlDst, trDst, brDst, blDst,
tlDstSrc, trDstSrc, brDstSrc, blDstSrc,
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.
* @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;