Simplified wrapX handling

"Unwrap" the coordinates obtained from transformations and utilize wrapX
capabilities of the sources to handle calculations of TileRanges and
unwrapped tile extents.
This commit is contained in:
Petr Sloup
2015-10-16 09:18:55 +02:00
parent 52a7c5e582
commit a7cde96056
3 changed files with 67 additions and 168 deletions

View File

@@ -116,55 +116,12 @@ ol.reproj.render = function(width, height, pixelRatio,
context.scale(pixelRatio, pixelRatio);
var wrapXShiftDistance = sourceExtent ? ol.extent.getWidth(sourceExtent) : 0;
var wrapXType = ol.reproj.WrapXRendering_.NONE;
if (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 triangulationSrcExtent = triangulation.calculateSourceExtent();
var triangulationSrcWidth = goog.math.modulo(
ol.extent.getWidth(triangulationSrcExtent), wrapXShiftDistance);
if (triangulationSrcWidth < wrapXShiftDistance / 2) {
wrapXType = ol.reproj.WrapXRendering_.STITCH_SHIFT;
} else {
wrapXType = ol.reproj.WrapXRendering_.STITCH_EXTENDED;
}
}
var sourceDataExtent = ol.extent.createEmpty();
sources.forEach(function(src, i, arr) {
if (wrapXType == ol.reproj.WrapXRendering_.STITCH_SHIFT) {
var srcW = src.extent[2] - src.extent[0];
var srcX = goog.math.modulo(src.extent[0], wrapXShiftDistance);
ol.extent.extend(sourceDataExtent, [srcX, src.extent[1],
srcX + srcW, src.extent[3]]);
} else {
ol.extent.extend(sourceDataExtent, src.extent);
}
ol.extent.extend(sourceDataExtent, src.extent);
});
if (sourceExtent) {
if (wrapXType == ol.reproj.WrapXRendering_.NONE) {
sourceDataExtent[0] = ol.math.clamp(
sourceDataExtent[0], sourceExtent[0], sourceExtent[2]);
sourceDataExtent[2] = ol.math.clamp(
sourceDataExtent[2], sourceExtent[0], sourceExtent[2]);
}
sourceDataExtent[1] = ol.math.clamp(
sourceDataExtent[1], sourceExtent[1], sourceExtent[3]);
sourceDataExtent[3] = ol.math.clamp(
sourceDataExtent[3], sourceExtent[1], sourceExtent[3]);
}
var canvasWidthInUnits;
if (wrapXType == ol.reproj.WrapXRendering_.STITCH_EXTENDED) {
canvasWidthInUnits = 2 * wrapXShiftDistance;
} else {
canvasWidthInUnits = ol.extent.getWidth(sourceDataExtent);
}
var canvasWidthInUnits = ol.extent.getWidth(sourceDataExtent);
var canvasHeightInUnits = ol.extent.getHeight(sourceDataExtent);
var stitchContext = ol.dom.createCanvasContext2D(
Math.round(pixelRatio * canvasWidthInUnits / sourceResolution),
@@ -180,15 +137,7 @@ ol.reproj.render = function(width, height, pixelRatio,
var srcWidth = ol.extent.getWidth(src.extent);
var srcHeight = ol.extent.getHeight(src.extent);
if (wrapXType == ol.reproj.WrapXRendering_.STITCH_SHIFT) {
xPos = goog.math.modulo(xPos, wrapXShiftDistance);
}
stitchContext.drawImage(src.image, xPos, yPos, srcWidth, srcHeight);
if (wrapXType == ol.reproj.WrapXRendering_.STITCH_EXTENDED) {
stitchContext.drawImage(src.image, wrapXShiftDistance + xPos, yPos,
srcWidth, srcHeight);
}
});
var targetTopLeft = ol.extent.getTopLeft(targetExtent);
@@ -227,21 +176,6 @@ ol.reproj.render = function(width, height, pixelRatio,
var u2 = (target[2][0] - targetTopLeft[0]) / targetResolution,
v2 = -(target[2][1] - targetTopLeft[1]) / targetResolution;
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 = minX <= sourceExtent[0] ||
(maxX - minX) > wrapXShiftDistance / 2;
}
if (performWrapXShift) {
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.
// This is also used to simplify the linear system.
@@ -322,15 +256,3 @@ ol.reproj.render = function(width, height, pixelRatio,
}
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,6 +8,7 @@ goog.require('goog.object');
goog.require('ol.Tile');
goog.require('ol.TileState');
goog.require('ol.extent');
goog.require('ol.math');
goog.require('ol.proj');
goog.require('ol.reproj');
goog.require('ol.reproj.Triangulation');
@@ -151,56 +152,38 @@ ol.reproj.Tile = function(sourceProj, sourceTileGrid,
this.sourceZ_ = sourceTileGrid.getZForResolution(sourceResolution);
var sourceExtent = this.triangulation_.calculateSourceExtent();
if (maxSourceExtent &&
!this.triangulation_.getWrapsXInSource() &&
!ol.extent.intersects(maxSourceExtent, sourceExtent)) {
if (maxSourceExtent) {
if (sourceProj.canWrapX()) {
sourceExtent[1] = ol.math.clamp(
sourceExtent[1], maxSourceExtent[1], maxSourceExtent[3]);
sourceExtent[3] = ol.math.clamp(
sourceExtent[3], maxSourceExtent[1], maxSourceExtent[3]);
} else {
sourceExtent = ol.extent.getIntersection(sourceExtent, maxSourceExtent);
}
}
if (!ol.extent.getArea(sourceExtent)) {
this.state = ol.TileState.EMPTY;
} else {
var sourceRange = sourceTileGrid.getTileRangeForExtentAndZ(
sourceExtent, this.sourceZ_);
var xRange = [];
var sourceFullRange = sourceTileGrid.getFullTileRange(this.sourceZ_);
if (sourceFullRange) {
sourceRange.minY = Math.max(sourceRange.minY, sourceFullRange.minY);
sourceRange.maxY = Math.min(sourceRange.maxY, sourceFullRange.maxY);
if (sourceRange.minX > sourceRange.maxX) {
var i;
for (i = sourceRange.minX; i <= sourceFullRange.maxX; i++) {
xRange.push(i);
}
for (i = sourceFullRange.minX; i <= sourceRange.maxX; i++) {
xRange.push(i);
}
} else {
var first = Math.max(sourceRange.minX, sourceFullRange.minX);
var last = Math.min(sourceRange.maxX, sourceFullRange.maxX);
for (var j = first; j <= last; j++) {
xRange.push(j);
}
}
} else {
for (var k = sourceRange.minX; k <= sourceRange.maxX; k++) {
xRange.push(k);
}
}
var tilesRequired = xRange.length * sourceRange.getHeight();
var tilesRequired = sourceRange.getWidth() * sourceRange.getHeight();
if (!goog.asserts.assert(
tilesRequired < ol.RASTER_REPROJECTION_MAX_SOURCE_TILES,
'reasonable number of tiles is required')) {
this.state = ol.TileState.ERROR;
return;
}
xRange.forEach(function(srcX, i, arr) {
for (var srcX = sourceRange.minX; srcX <= sourceRange.maxX; srcX++) {
for (var srcY = sourceRange.minY; srcY <= sourceRange.maxY; srcY++) {
var tile = getTileFunction(this.sourceZ_, srcX, srcY, pixelRatio);
if (tile) {
this.sourceTiles_.push(tile);
}
}
}, this);
}
if (this.sourceTiles_.length === 0) {
this.state = ol.TileState.EMPTY;

View File

@@ -75,12 +75,6 @@ ol.reproj.Triangulation = function(sourceProj, targetProj, targetExtent,
*/
this.triangles_ = [];
/**
* @type {ol.Extent}
* @private
*/
this.trianglesSourceExtent_ = null;
/**
* Indicates that source coordinates have to be shifted during reprojection.
* This is needed when the triangulation crosses
@@ -129,6 +123,49 @@ ol.reproj.Triangulation = function(sourceProj, targetProj, targetExtent,
sourceTopLeft, sourceTopRight, sourceBottomRight, sourceBottomLeft,
ol.RASTER_REPROJECTION_MAX_SUBDIVISION);
if (this.wrapsXInSource_) {
// Fix coordinates (ol.proj returns wrapped coordinates, "unwrap" here).
// This significantly simplifies the rest of the reprojection process.
goog.asserts.assert(this.sourceWorldWidth_ !== null);
var leftBound = Infinity;
this.triangles_.forEach(function(triangle, i, arr) {
leftBound = Math.min(leftBound,
triangle.source[0][0], triangle.source[1][0], triangle.source[2][0]);
});
// Shift triangles to be as close to `leftBound` as possible
// (if the distance is more than `worldWidth / 2` it can be closer.
this.triangles_.forEach(function(triangle) {
if (Math.max(triangle.source[0][0], triangle.source[1][0],
triangle.source[2][0]) - leftBound > this.sourceWorldWidth_ / 2) {
var newTriangle = [[triangle.source[0][0], triangle.source[0][1]],
[triangle.source[1][0], triangle.source[1][1]],
[triangle.source[2][0], triangle.source[2][1]]];
if ((newTriangle[0][0] - leftBound) > this.sourceWorldWidth_ / 2) {
newTriangle[0][0] -= this.sourceWorldWidth_;
}
if ((newTriangle[1][0] - leftBound) > this.sourceWorldWidth_ / 2) {
newTriangle[1][0] -= this.sourceWorldWidth_;
}
if ((newTriangle[2][0] - leftBound) > this.sourceWorldWidth_ / 2) {
newTriangle[2][0] -= this.sourceWorldWidth_;
}
// Rarely (if the extent contains both the dateline and prime meridian)
// the shift can in turn break some triangles.
// Detect this here and don't shift in such cases.
var minX = Math.min(
newTriangle[0][0], newTriangle[1][0], newTriangle[2][0]);
var maxX = Math.max(
newTriangle[0][0], newTriangle[1][0], newTriangle[2][0]);
if ((maxX - minX) < this.sourceWorldWidth_ / 2) {
triangle.source = newTriangle;
}
}
}, this);
}
transformInvCache = {};
};
@@ -278,66 +315,23 @@ ol.reproj.Triangulation.prototype.addQuad_ = function(a, b, c, d,
/**
* Calculates extent of the 'source' coordinates from all the triangles.
* The left bound of the returned extent can be higher than the right bound,
* if the triangulation wraps X in source (see
* {@link ol.reproj.Triangulation#getWrapsXInSource}).
*
* @return {ol.Extent} Calculated extent.
*/
ol.reproj.Triangulation.prototype.calculateSourceExtent = function() {
if (this.trianglesSourceExtent_) {
return this.trianglesSourceExtent_;
}
var extent = ol.extent.createEmpty();
if (this.wrapsXInSource_) {
// although only some of the triangles are crossing the dateline,
// all coordinates need to be "shifted" to be positive
// to properly calculate the extent (and then possibly shifted back)
this.triangles_.forEach(function(triangle, i, arr) {
var src = triangle.source;
ol.extent.extendCoordinate(extent, src[0]);
ol.extent.extendCoordinate(extent, src[1]);
ol.extent.extendCoordinate(extent, src[2]);
});
this.triangles_.forEach(function(triangle, i, arr) {
goog.asserts.assert(this.sourceWorldWidth_);
var src = triangle.source;
ol.extent.extendCoordinate(extent,
[goog.math.modulo(src[0][0], this.sourceWorldWidth_), src[0][1]]);
ol.extent.extendCoordinate(extent,
[goog.math.modulo(src[1][0], this.sourceWorldWidth_), src[1][1]]);
ol.extent.extendCoordinate(extent,
[goog.math.modulo(src[2][0], this.sourceWorldWidth_), src[2][1]]);
}, this);
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_;
}
} else {
this.triangles_.forEach(function(triangle, i, arr) {
var src = triangle.source;
ol.extent.extendCoordinate(extent, src[0]);
ol.extent.extendCoordinate(extent, src[1]);
ol.extent.extendCoordinate(extent, src[2]);
});
}
this.trianglesSourceExtent_ = extent;
return extent;
};
/**
* @return {boolean} Whether the source coordinates are wrapped in X
* (the triangulation "crosses the dateline").
*/
ol.reproj.Triangulation.prototype.getWrapsXInSource = function() {
return this.wrapsXInSource_;
};
/**
* @return {Array.<ol.reproj.Triangle>} Array of the calculated triangles.
*/