Optimize the reprojection by stitching the sources prior to rendering

This solves canvas antialiasing issues and also simplifies the code
handling wrapX in the source projection.
This commit is contained in:
Petr Sloup
2015-07-09 18:05:54 +02:00
parent 6482ccf2f7
commit 8ab197eba6
2 changed files with 117 additions and 54 deletions

View File

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

View File

@@ -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.<ol.Coordinate>,
* target: Array.<ol.Coordinate>,
* needsShift: boolean}}
* target: Array.<ol.Coordinate>}}
*/
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.<ol.reproj.Triangle>}
*/