Detect and handle triangles (tiles) crossing the dateline (projection edge)

This commit is contained in:
Petr Sloup
2015-06-02 21:39:40 +02:00
parent b221e1ac1f
commit ac76989447
3 changed files with 129 additions and 27 deletions

View File

@@ -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();
}); });

View File

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

View File

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