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);
|
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();
|
var sourceDataExtent = ol.extent.createEmpty();
|
||||||
sources.forEach(function(src, i, arr) {
|
sources.forEach(function(src, i, arr) {
|
||||||
if (wrapXType == ol.reproj.WrapXRendering_.STITCH_SHIFT) {
|
ol.extent.extend(sourceDataExtent, src.extent);
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
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 canvasHeightInUnits = ol.extent.getHeight(sourceDataExtent);
|
||||||
var stitchContext = ol.dom.createCanvasContext2D(
|
var stitchContext = ol.dom.createCanvasContext2D(
|
||||||
Math.round(pixelRatio * canvasWidthInUnits / sourceResolution),
|
Math.round(pixelRatio * canvasWidthInUnits / sourceResolution),
|
||||||
@@ -180,15 +137,7 @@ ol.reproj.render = function(width, height, pixelRatio,
|
|||||||
var srcWidth = ol.extent.getWidth(src.extent);
|
var srcWidth = ol.extent.getWidth(src.extent);
|
||||||
var srcHeight = ol.extent.getHeight(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);
|
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);
|
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,
|
var u2 = (target[2][0] - targetTopLeft[0]) / targetResolution,
|
||||||
v2 = -(target[2][1] - targetTopLeft[1]) / 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
|
// Shift all the source points to improve numerical stability
|
||||||
// of all the subsequent calculations. The [x0, y0] is used here.
|
// of all the subsequent calculations. The [x0, y0] is used here.
|
||||||
// This is also used to simplify the linear system.
|
// This is also used to simplify the linear system.
|
||||||
@@ -322,15 +256,3 @@ ol.reproj.render = function(width, height, pixelRatio,
|
|||||||
}
|
}
|
||||||
return context.canvas;
|
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.Tile');
|
||||||
goog.require('ol.TileState');
|
goog.require('ol.TileState');
|
||||||
goog.require('ol.extent');
|
goog.require('ol.extent');
|
||||||
|
goog.require('ol.math');
|
||||||
goog.require('ol.proj');
|
goog.require('ol.proj');
|
||||||
goog.require('ol.reproj');
|
goog.require('ol.reproj');
|
||||||
goog.require('ol.reproj.Triangulation');
|
goog.require('ol.reproj.Triangulation');
|
||||||
@@ -151,56 +152,38 @@ ol.reproj.Tile = function(sourceProj, sourceTileGrid,
|
|||||||
this.sourceZ_ = sourceTileGrid.getZForResolution(sourceResolution);
|
this.sourceZ_ = sourceTileGrid.getZForResolution(sourceResolution);
|
||||||
var sourceExtent = this.triangulation_.calculateSourceExtent();
|
var sourceExtent = this.triangulation_.calculateSourceExtent();
|
||||||
|
|
||||||
if (maxSourceExtent &&
|
if (maxSourceExtent) {
|
||||||
!this.triangulation_.getWrapsXInSource() &&
|
if (sourceProj.canWrapX()) {
|
||||||
!ol.extent.intersects(maxSourceExtent, sourceExtent)) {
|
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;
|
this.state = ol.TileState.EMPTY;
|
||||||
} else {
|
} else {
|
||||||
var sourceRange = sourceTileGrid.getTileRangeForExtentAndZ(
|
var sourceRange = sourceTileGrid.getTileRangeForExtentAndZ(
|
||||||
sourceExtent, this.sourceZ_);
|
sourceExtent, this.sourceZ_);
|
||||||
|
|
||||||
var xRange = [];
|
var tilesRequired = sourceRange.getWidth() * sourceRange.getHeight();
|
||||||
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();
|
|
||||||
if (!goog.asserts.assert(
|
if (!goog.asserts.assert(
|
||||||
tilesRequired < ol.RASTER_REPROJECTION_MAX_SOURCE_TILES,
|
tilesRequired < ol.RASTER_REPROJECTION_MAX_SOURCE_TILES,
|
||||||
'reasonable number of tiles is required')) {
|
'reasonable number of tiles is required')) {
|
||||||
this.state = ol.TileState.ERROR;
|
this.state = ol.TileState.ERROR;
|
||||||
return;
|
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++) {
|
for (var srcY = sourceRange.minY; srcY <= sourceRange.maxY; srcY++) {
|
||||||
var tile = getTileFunction(this.sourceZ_, srcX, srcY, pixelRatio);
|
var tile = getTileFunction(this.sourceZ_, srcX, srcY, pixelRatio);
|
||||||
if (tile) {
|
if (tile) {
|
||||||
this.sourceTiles_.push(tile);
|
this.sourceTiles_.push(tile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, this);
|
}
|
||||||
|
|
||||||
if (this.sourceTiles_.length === 0) {
|
if (this.sourceTiles_.length === 0) {
|
||||||
this.state = ol.TileState.EMPTY;
|
this.state = ol.TileState.EMPTY;
|
||||||
|
|||||||
@@ -75,12 +75,6 @@ ol.reproj.Triangulation = function(sourceProj, targetProj, targetExtent,
|
|||||||
*/
|
*/
|
||||||
this.triangles_ = [];
|
this.triangles_ = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {ol.Extent}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
this.trianglesSourceExtent_ = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that source coordinates have to be shifted during reprojection.
|
* Indicates that source coordinates have to be shifted during reprojection.
|
||||||
* This is needed when the triangulation crosses
|
* This is needed when the triangulation crosses
|
||||||
@@ -129,6 +123,49 @@ ol.reproj.Triangulation = function(sourceProj, targetProj, targetExtent,
|
|||||||
sourceTopLeft, sourceTopRight, sourceBottomRight, sourceBottomLeft,
|
sourceTopLeft, sourceTopRight, sourceBottomRight, sourceBottomLeft,
|
||||||
ol.RASTER_REPROJECTION_MAX_SUBDIVISION);
|
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 = {};
|
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.
|
* 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.
|
* @return {ol.Extent} Calculated extent.
|
||||||
*/
|
*/
|
||||||
ol.reproj.Triangulation.prototype.calculateSourceExtent = function() {
|
ol.reproj.Triangulation.prototype.calculateSourceExtent = function() {
|
||||||
if (this.trianglesSourceExtent_) {
|
|
||||||
return this.trianglesSourceExtent_;
|
|
||||||
}
|
|
||||||
|
|
||||||
var extent = ol.extent.createEmpty();
|
var extent = ol.extent.createEmpty();
|
||||||
|
|
||||||
if (this.wrapsXInSource_) {
|
this.triangles_.forEach(function(triangle, i, arr) {
|
||||||
// although only some of the triangles are crossing the dateline,
|
var src = triangle.source;
|
||||||
// all coordinates need to be "shifted" to be positive
|
ol.extent.extendCoordinate(extent, src[0]);
|
||||||
// to properly calculate the extent (and then possibly shifted back)
|
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 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.
|
* @return {Array.<ol.reproj.Triangle>} Array of the calculated triangles.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user