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:
@@ -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
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user