Detect and handle triangles (tiles) crossing the dateline (projection edge)
This commit is contained in:
@@ -57,6 +57,17 @@ ol.reproj.calculateSourceResolution = function(sourceProj, targetProj,
|
|||||||
*/
|
*/
|
||||||
ol.reproj.renderTriangles = function(context,
|
ol.reproj.renderTriangles = function(context,
|
||||||
sourceResolution, targetResolution, targetExtent, triangulation, sources) {
|
sourceResolution, targetResolution, targetExtent, triangulation, sources) {
|
||||||
|
|
||||||
|
var renderImage = function(image) {
|
||||||
|
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(image, -0.5, -0.5,
|
||||||
|
image.width + 1, image.height + 1);
|
||||||
|
};
|
||||||
|
|
||||||
goog.array.forEach(triangulation, function(tri, i, arr) {
|
goog.array.forEach(triangulation, function(tri, i, arr) {
|
||||||
context.save();
|
context.save();
|
||||||
|
|
||||||
@@ -88,6 +99,11 @@ ol.reproj.renderTriangles = function(context,
|
|||||||
var u0 = tri[0][1][0] - targetTL[0], v0 = -(tri[0][1][1] - targetTL[1]),
|
var u0 = tri[0][1][0] - targetTL[0], v0 = -(tri[0][1][1] - targetTL[1]),
|
||||||
u1 = tri[1][1][0] - targetTL[0], v1 = -(tri[1][1][1] - targetTL[1]),
|
u1 = tri[1][1][0] - targetTL[0], v1 = -(tri[1][1][1] - targetTL[1]),
|
||||||
u2 = tri[2][1][0] - targetTL[0], v2 = -(tri[2][1][1] - targetTL[1]);
|
u2 = tri[2][1][0] - targetTL[0], v2 = -(tri[2][1][1] - targetTL[1]);
|
||||||
|
if (tri.shiftDistance) {
|
||||||
|
x0 = goog.math.modulo(x0 + tri.shiftDistance, tri.shiftDistance);
|
||||||
|
x1 = goog.math.modulo(x1 + tri.shiftDistance, tri.shiftDistance);
|
||||||
|
x2 = goog.math.modulo(x2 + tri.shiftDistance, tri.shiftDistance);
|
||||||
|
}
|
||||||
var augmentedMatrix = [
|
var augmentedMatrix = [
|
||||||
[x0, y0, 1, 0, 0, 0, u0 / targetResolution],
|
[x0, y0, 1, 0, 0, 0, u0 / targetResolution],
|
||||||
[x1, y1, 1, 0, 0, 0, u1 / targetResolution],
|
[x1, y1, 1, 0, 0, 0, u1 / targetResolution],
|
||||||
@@ -133,13 +149,22 @@ ol.reproj.renderTriangles = function(context,
|
|||||||
context.save();
|
context.save();
|
||||||
var tlSrcFromData = ol.extent.getTopLeft(src.extent);
|
var tlSrcFromData = ol.extent.getTopLeft(src.extent);
|
||||||
context.translate(tlSrcFromData[0], tlSrcFromData[1]);
|
context.translate(tlSrcFromData[0], tlSrcFromData[1]);
|
||||||
context.scale(sourceResolution, -sourceResolution);
|
if (tri.shiftDistance) {
|
||||||
|
context.save();
|
||||||
|
context.translate(tri.shiftDistance, 0);
|
||||||
|
renderImage(src.image);
|
||||||
|
context.restore();
|
||||||
|
renderImage(src.image);
|
||||||
|
|
||||||
// the image has to be scaled by half a pixel in every direction
|
if (goog.DEBUG) {
|
||||||
// in order to prevent artifacts between the original tiles
|
context.fillStyle =
|
||||||
// that are introduced by the canvas antialiasing.
|
sources.length > 16 ? 'rgba(255,0,0,1)' :
|
||||||
context.drawImage(src.image, -0.5, -0.5,
|
(sources.length > 4 ? 'rgba(0,255,0,.3)' : 'rgba(0,0,255,.1)');
|
||||||
src.image.width + 1, src.image.height + 1);
|
context.fillRect(0, 0, 256, 256);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
renderImage(src.image);
|
||||||
|
}
|
||||||
context.restore();
|
context.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -97,6 +97,12 @@ ol.reproj.Tile = function(sourceProj, sourceTileGrid,
|
|||||||
targetExtent, sourceProj, targetProj,
|
targetExtent, sourceProj, targetProj,
|
||||||
maxTargetExtent, maxSourceExtent);
|
maxTargetExtent, maxSourceExtent);
|
||||||
|
|
||||||
|
if (this.triangles_.length === 0) {
|
||||||
|
// no valid triangles -> EMPTY
|
||||||
|
this.state = ol.TileState.EMPTY;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var targetCenter = ol.extent.getCenter(targetExtent);
|
var targetCenter = ol.extent.getCenter(targetExtent);
|
||||||
var targetResolution = targetTileGrid.getResolution(z);
|
var targetResolution = targetTileGrid.getResolution(z);
|
||||||
var sourceResolution = ol.reproj.calculateSourceResolution(
|
var sourceResolution = ol.reproj.calculateSourceResolution(
|
||||||
@@ -113,23 +119,35 @@ ol.reproj.Tile = function(sourceProj, sourceTileGrid,
|
|||||||
var srcExtent = ol.reproj.triangulation.getSourceExtent(this.triangles_);
|
var srcExtent = ol.reproj.triangulation.getSourceExtent(this.triangles_);
|
||||||
|
|
||||||
var sourceProjExtent = sourceProj.getExtent();
|
var sourceProjExtent = sourceProj.getExtent();
|
||||||
if (sourceProjExtent) {
|
if (!sourceProj.isGlobal() && sourceProjExtent) {
|
||||||
srcExtent = ol.extent.getIntersection(srcExtent, sourceProjExtent);
|
srcExtent = ol.extent.getIntersection(srcExtent, sourceProjExtent);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ol.extent.intersects(sourceTileGrid.getExtent(), srcExtent)) {
|
if (!goog.isNull(maxSourceExtent) &&
|
||||||
|
!ol.extent.intersects(maxSourceExtent, srcExtent)) {
|
||||||
this.state = ol.TileState.EMPTY;
|
this.state = ol.TileState.EMPTY;
|
||||||
} else {
|
} else {
|
||||||
var srcRange = sourceTileGrid.getTileRangeForExtentAndZ(
|
var srcRange = sourceTileGrid.getTileRangeForExtentAndZ(
|
||||||
srcExtent, this.srcZ_);
|
srcExtent, this.srcZ_);
|
||||||
|
|
||||||
var srcFullRange = sourceTileGrid.getFullTileRange(this.srcZ_);
|
var srcFullRange = sourceTileGrid.getFullTileRange(this.srcZ_);
|
||||||
srcRange.minX = Math.max(srcRange.minX, srcFullRange.minX);
|
|
||||||
srcRange.maxX = Math.min(srcRange.maxX, srcFullRange.maxX);
|
|
||||||
srcRange.minY = Math.max(srcRange.minY, srcFullRange.minY);
|
srcRange.minY = Math.max(srcRange.minY, srcFullRange.minY);
|
||||||
srcRange.maxY = Math.min(srcRange.maxY, srcFullRange.maxY);
|
srcRange.maxY = Math.min(srcRange.maxY, srcFullRange.maxY);
|
||||||
|
|
||||||
if (srcRange.getWidth() * srcRange.getHeight() > 100) {
|
var xRange;
|
||||||
|
if (srcRange.minX > srcRange.maxX) {
|
||||||
|
xRange = goog.array.concat(
|
||||||
|
goog.array.range(srcRange.minX, srcFullRange.maxX + 1),
|
||||||
|
goog.array.range(srcFullRange.minX, srcRange.maxX + 1)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
xRange = goog.array.range(
|
||||||
|
Math.max(srcRange.minX, srcFullRange.minX),
|
||||||
|
Math.min(srcRange.maxX, srcFullRange.maxX) + 1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xRange.length * srcRange.getHeight() > 100) {
|
||||||
// Too many source tiles are needed -- something probably went wrong
|
// Too many source tiles are needed -- something probably went wrong
|
||||||
// This sometimes happens for certain non-global projections
|
// This sometimes happens for certain non-global projections
|
||||||
// if no extent is specified.
|
// if no extent is specified.
|
||||||
@@ -137,14 +155,14 @@ ol.reproj.Tile = function(sourceProj, sourceTileGrid,
|
|||||||
this.state = ol.TileState.ERROR;
|
this.state = ol.TileState.ERROR;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (var srcX = srcRange.minX; srcX <= srcRange.maxX; srcX++) {
|
goog.array.forEach(xRange, function(srcX, i, arr) {
|
||||||
for (var srcY = srcRange.minY; srcY <= srcRange.maxY; srcY++) {
|
for (var srcY = srcRange.minY; srcY <= srcRange.maxY; srcY++) {
|
||||||
var tile = getTileFunction(this.srcZ_, srcX, srcY, pixelRatio);
|
var tile = getTileFunction(this.srcZ_, srcX, srcY, pixelRatio);
|
||||||
if (tile) {
|
if (tile) {
|
||||||
this.srcTiles_.push(tile);
|
this.srcTiles_.push(tile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}, this);
|
||||||
|
|
||||||
if (this.srcTiles_.length === 0) {
|
if (this.srcTiles_.length === 0) {
|
||||||
this.state = ol.TileState.EMPTY;
|
this.state = ol.TileState.EMPTY;
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ ol.reproj.Triangulation;
|
|||||||
* @param {ol.Coordinate} a
|
* @param {ol.Coordinate} a
|
||||||
* @param {ol.Coordinate} b
|
* @param {ol.Coordinate} b
|
||||||
* @param {ol.Coordinate} c
|
* @param {ol.Coordinate} c
|
||||||
* @param {ol.TransformFunction} transformFwd Forward transform (src -> dst).
|
* @param {ol.proj.Projection} sourceProj
|
||||||
* @param {ol.TransformFunction} transformInv Inverse transform (dst -> src).
|
* @param {ol.proj.Projection} targetProj
|
||||||
* @param {ol.Extent=} opt_maxTargetExtent
|
* @param {ol.Extent=} opt_maxTargetExtent
|
||||||
* @param {ol.Extent=} opt_maxSourceExtent
|
* @param {ol.Extent=} opt_maxSourceExtent
|
||||||
*/
|
*/
|
||||||
ol.reproj.triangulation.addTriangleIfValid_ = function(triangulation, a, b, c,
|
ol.reproj.triangulation.addTriangleIfValid_ = function(triangulation, a, b, c,
|
||||||
transformFwd, transformInv, opt_maxTargetExtent, opt_maxSourceExtent) {
|
sourceProj, targetProj, opt_maxTargetExtent, opt_maxSourceExtent) {
|
||||||
if (goog.isDefAndNotNull(opt_maxTargetExtent)) {
|
if (goog.isDefAndNotNull(opt_maxTargetExtent)) {
|
||||||
if (!ol.extent.containsCoordinate(opt_maxTargetExtent, a) &&
|
if (!ol.extent.containsCoordinate(opt_maxTargetExtent, a) &&
|
||||||
!ol.extent.containsCoordinate(opt_maxTargetExtent, b) &&
|
!ol.extent.containsCoordinate(opt_maxTargetExtent, b) &&
|
||||||
@@ -43,6 +43,7 @@ ol.reproj.triangulation.addTriangleIfValid_ = function(triangulation, a, b, c,
|
|||||||
b = ol.extent.closestCoordinate(opt_maxTargetExtent, b);
|
b = ol.extent.closestCoordinate(opt_maxTargetExtent, b);
|
||||||
c = ol.extent.closestCoordinate(opt_maxTargetExtent, c);
|
c = ol.extent.closestCoordinate(opt_maxTargetExtent, c);
|
||||||
}
|
}
|
||||||
|
var transformInv = ol.proj.getTransform(targetProj, sourceProj);
|
||||||
var aSrc = transformInv(a);
|
var aSrc = transformInv(a);
|
||||||
var bSrc = transformInv(b);
|
var bSrc = transformInv(b);
|
||||||
var cSrc = transformInv(c);
|
var cSrc = transformInv(c);
|
||||||
@@ -62,12 +63,56 @@ ol.reproj.triangulation.addTriangleIfValid_ = function(triangulation, a, b, c,
|
|||||||
aSrc = ol.extent.closestCoordinate(opt_maxSourceExtent, aSrc);
|
aSrc = ol.extent.closestCoordinate(opt_maxSourceExtent, aSrc);
|
||||||
bSrc = ol.extent.closestCoordinate(opt_maxSourceExtent, bSrc);
|
bSrc = ol.extent.closestCoordinate(opt_maxSourceExtent, bSrc);
|
||||||
cSrc = ol.extent.closestCoordinate(opt_maxSourceExtent, cSrc);
|
cSrc = ol.extent.closestCoordinate(opt_maxSourceExtent, cSrc);
|
||||||
|
var transformFwd = ol.proj.getTransform(sourceProj, targetProj);
|
||||||
a = transformFwd(aSrc);
|
a = transformFwd(aSrc);
|
||||||
b = transformFwd(bSrc);
|
b = transformFwd(bSrc);
|
||||||
c = transformFwd(cSrc);
|
c = transformFwd(cSrc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
triangulation.push([[aSrc, a], [bSrc, b], [cSrc, c]]);
|
var shiftDistance = 0;
|
||||||
|
if (sourceProj.isGlobal()) {
|
||||||
|
// determine if the triangle crosses the dateline here
|
||||||
|
// This can be detected by transforming centroid of the target triangle.
|
||||||
|
// If the transformed centroid is outside the transformed triangle,
|
||||||
|
// the triangle wraps around projection extent.
|
||||||
|
// In such case, the
|
||||||
|
|
||||||
|
var srcExtent = ol.extent.createEmpty();
|
||||||
|
ol.extent.extendCoordinate(srcExtent, aSrc);
|
||||||
|
ol.extent.extendCoordinate(srcExtent, bSrc);
|
||||||
|
ol.extent.extendCoordinate(srcExtent, cSrc);
|
||||||
|
|
||||||
|
var centroid = [(a[0] + b[0] + c[0]) / 3,
|
||||||
|
(a[1] + b[1] + c[1]) / 3];
|
||||||
|
var centroidSrc = transformInv(centroid);
|
||||||
|
|
||||||
|
var pInTriangle = function(p, p0, p1, p2) {
|
||||||
|
//TODO: move somewhere else
|
||||||
|
var A = (-p1[1] * p2[0] + p0[1] * (-p1[0] + p2[0]) +
|
||||||
|
p0[0] * (p1[1] - p2[1]) + p1[0] * p2[1]) / 2;
|
||||||
|
var sign = A < 0 ? -1 : 1;
|
||||||
|
var s = (p0[1] * p2[0] - p0[0] * p2[1] +
|
||||||
|
(p2[1] - p0[1]) * p[0] +
|
||||||
|
(p0[0] - p2[0]) * p[1]) * sign;
|
||||||
|
var t = (p0[0] * p1[1] - p0[1] * p1[0] +
|
||||||
|
(p0[1] - p1[1]) * p[0] +
|
||||||
|
(p1[0] - p0[0]) * p[1]) * sign;
|
||||||
|
|
||||||
|
return s > 0 && t > 0 && (s + t) < 2 * A * sign;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!pInTriangle(centroidSrc, aSrc, bSrc, cSrc)) {
|
||||||
|
var sourceProjExtent = sourceProj.getExtent();
|
||||||
|
shiftDistance = ol.extent.getWidth(sourceProjExtent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var tri = [[aSrc, a], [bSrc, b], [cSrc, c]];
|
||||||
|
// TODO: typing ! do not add properties to arrays !
|
||||||
|
tri.shiftDistance = shiftDistance;
|
||||||
|
if (shiftDistance) {
|
||||||
|
triangulation.shiftDistance = shiftDistance;
|
||||||
|
}
|
||||||
|
triangulation.push(tri);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -85,9 +130,6 @@ ol.reproj.triangulation.addTriangleIfValid_ = function(triangulation, a, b, c,
|
|||||||
ol.reproj.triangulation.createForExtent = function(extent, sourceProj,
|
ol.reproj.triangulation.createForExtent = function(extent, sourceProj,
|
||||||
targetProj, opt_maxTargetExtent, opt_maxSourceExtent, opt_subdiv) {
|
targetProj, opt_maxTargetExtent, opt_maxSourceExtent, opt_subdiv) {
|
||||||
|
|
||||||
var transformFwd = ol.proj.getTransform(sourceProj, targetProj);
|
|
||||||
var transformInv = ol.proj.getTransform(targetProj, sourceProj);
|
|
||||||
|
|
||||||
var triangulation = [];
|
var triangulation = [];
|
||||||
|
|
||||||
var tlDst = ol.extent.getTopLeft(extent);
|
var tlDst = ol.extent.getTopLeft(extent);
|
||||||
@@ -118,10 +160,10 @@ ol.reproj.triangulation.createForExtent = function(extent, sourceProj,
|
|||||||
|
|
||||||
ol.reproj.triangulation.addTriangleIfValid_(
|
ol.reproj.triangulation.addTriangleIfValid_(
|
||||||
triangulation, x0y0dst, x1y1dst, x0y1dst,
|
triangulation, x0y0dst, x1y1dst, x0y1dst,
|
||||||
transformFwd, transformInv, opt_maxTargetExtent, opt_maxSourceExtent);
|
sourceProj, targetProj, opt_maxTargetExtent, opt_maxSourceExtent);
|
||||||
ol.reproj.triangulation.addTriangleIfValid_(
|
ol.reproj.triangulation.addTriangleIfValid_(
|
||||||
triangulation, x0y0dst, x1y0dst, x1y1dst,
|
triangulation, x0y0dst, x1y0dst, x1y1dst,
|
||||||
transformFwd, transformInv, opt_maxTargetExtent, opt_maxSourceExtent);
|
sourceProj, targetProj, opt_maxTargetExtent, opt_maxSourceExtent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,11 +178,28 @@ ol.reproj.triangulation.createForExtent = function(extent, sourceProj,
|
|||||||
ol.reproj.triangulation.getSourceExtent = function(triangulation) {
|
ol.reproj.triangulation.getSourceExtent = function(triangulation) {
|
||||||
var extent = ol.extent.createEmpty();
|
var extent = ol.extent.createEmpty();
|
||||||
|
|
||||||
goog.array.forEach(triangulation, function(triangle, i, arr) {
|
if (triangulation.shiftDistance) {
|
||||||
ol.extent.extendCoordinate(extent, triangle[0][0]);
|
var shiftDistance = triangulation.shiftDistance;
|
||||||
ol.extent.extendCoordinate(extent, triangle[1][0]);
|
goog.array.forEach(triangulation, function(triangle, i, arr) {
|
||||||
ol.extent.extendCoordinate(extent, triangle[2][0]);
|
ol.extent.extendCoordinate(extent,
|
||||||
});
|
[goog.math.modulo(triangle[0][0][0] + shiftDistance, shiftDistance),
|
||||||
|
triangle[0][0][1]]);
|
||||||
|
ol.extent.extendCoordinate(extent,
|
||||||
|
[goog.math.modulo(triangle[1][0][0] + shiftDistance, shiftDistance),
|
||||||
|
triangle[1][0][1]]);
|
||||||
|
ol.extent.extendCoordinate(extent,
|
||||||
|
[goog.math.modulo(triangle[2][0][0] + shiftDistance, shiftDistance),
|
||||||
|
triangle[2][0][1]]);
|
||||||
|
});
|
||||||
|
if (extent[0] > shiftDistance / 2) extent[0] -= shiftDistance;
|
||||||
|
if (extent[2] > shiftDistance / 2) extent[2] -= shiftDistance;
|
||||||
|
} else {
|
||||||
|
goog.array.forEach(triangulation, function(triangle, i, arr) {
|
||||||
|
ol.extent.extendCoordinate(extent, triangle[0][0]);
|
||||||
|
ol.extent.extendCoordinate(extent, triangle[1][0]);
|
||||||
|
ol.extent.extendCoordinate(extent, triangle[2][0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return extent;
|
return extent;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user