OpenLayers 3 新版本可以以不一样于服务器提供的坐标系显示来自WMS,WMTS,静态图像和许多其余来源的栅格数据。图像的地图投影的转换直接在Web浏览器中进行。任何Proj4js支持的坐标参考系统中的视图都是可能的,而且之前不兼容的图层如今能够组合和覆盖。html
能够在浏览器端实现不一样资源在不一样投影下的转换,再也不依赖于服务端处理,好比在Geoserver中指定投影类型,仍是至关给力的web
还能够看如下效果对比图 canvas
3857使用web Mercator 中国区域是方形,Reproject到4326时,就有些扁了,在相同的中心点,缩放级别下,地图的视察范围也不太一致,4326通过压扁后,能显示更多的范围。浏览器
再来一张放大后的,变形明显。服务器
以reprojection-image为例说明,使用的是三角仿射变换triangle affine transformation。ide
不一样的缩放级别1 ui
不一样的缩放级别2 spa
不一样的缩放级别3 3d
不一样的缩放级别4 调试
不一样的比例尺下会更新三角格网的大小,以刚好平分地图视口,至少两个三角
对三角格网渲染进行Degbu过滤后示意
渲染其中一部分三角
源码ol/reproj.js
, render
funcction
/** * Renders the source data into new canvas based on the triangulation. * * @param {number} width Width of the canvas. * @param {number} height Height of the canvas. * @param {number} pixelRatio Pixel ratio. * @param {number} sourceResolution Source resolution. * @param {import("./extent.js").Extent} sourceExtent Extent of the data source. * @param {number} targetResolution Target resolution. * @param {import("./extent.js").Extent} targetExtent Target extent. * @param {import("./reproj/Triangulation.js").default} triangulation * Calculated triangulation. * @param {Array<{extent: import("./extent.js").Extent, * image: (HTMLCanvasElement|HTMLImageElement|HTMLVideoElement)}>} sources * Array of sources. * @param {number} gutter Gutter of the sources. * @param {boolean=} opt_renderEdges Render reprojection edges. * @return {HTMLCanvasElement} Canvas with reprojected data. */
export function render(width, height, pixelRatio, sourceResolution, sourceExtent, targetResolution, targetExtent, triangulation, sources, gutter, opt_renderEdges) {
var context = createCanvasContext2D(Math.round(pixelRatio * width),
Math.round(pixelRatio * height));
if (sources.length === 0) {
return context.canvas;
}
context.scale(pixelRatio, pixelRatio);
var sourceDataExtent = createEmpty();
sources.forEach(function(src, i, arr) {
extend(sourceDataExtent, src.extent);
});
var canvasWidthInUnits = getWidth(sourceDataExtent);
var canvasHeightInUnits = getHeight(sourceDataExtent);
// 加载image的内部canvas
var stitchContext = createCanvasContext2D(
Math.round(pixelRatio * canvasWidthInUnits / sourceResolution),
Math.round(pixelRatio * canvasHeightInUnits / sourceResolution));
var stitchScale = pixelRatio / sourceResolution;
sources.forEach(function(src, i, arr) {
var xPos = src.extent[0] - sourceDataExtent[0];
var yPos = -(src.extent[3] - sourceDataExtent[3]);
var srcWidth = getWidth(src.extent);
var srcHeight = getHeight(src.extent);
stitchContext.drawImage(
src.image,
gutter, gutter,
src.image.width - 2 * gutter, src.image.height - 2 * gutter,
xPos * stitchScale, yPos * stitchScale,
srcWidth * stitchScale, srcHeight * stitchScale);
});
var targetTopLeft = getTopLeft(targetExtent);
// 对三角进行循环处理
triangulation.getTriangles().forEach(function(triangle, i, arr) {
/* Calculate affine transform (src -> dst) * Resulting matrix can be used to transform coordinate * from `sourceProjection` to destination pixels. * * To optimize number of context calls and increase numerical stability, * we also do the following operations: * trans(-topLeftExtentCorner), scale(1 / targetResolution), scale(1, -1) * here before solving the linear system so [ui, vi] are pixel coordinates. * * Src points: xi, yi * Dst points: ui, vi * Affine coefficients: aij * * | x0 y0 1 0 0 0 | |a00| |u0| * | x1 y1 1 0 0 0 | |a01| |u1| * | x2 y2 1 0 0 0 | x |a02| = |u2| * | 0 0 0 x0 y0 1 | |a10| |v0| * | 0 0 0 x1 y1 1 | |a11| |v1| * | 0 0 0 x2 y2 1 | |a12| |v2| */
var source = triangle.source;
var target = triangle.target;
var x0 = source[0][0], y0 = source[0][1];
var x1 = source[1][0], y1 = source[1][1];
var x2 = source[2][0], y2 = source[2][1];
var u0 = (target[0][0] - targetTopLeft[0]) / targetResolution;
var v0 = -(target[0][1] - targetTopLeft[1]) / targetResolution;
var u1 = (target[1][0] - targetTopLeft[0]) / targetResolution;
var v1 = -(target[1][1] - targetTopLeft[1]) / targetResolution;
var u2 = (target[2][0] - targetTopLeft[0]) / targetResolution;
var v2 = -(target[2][1] - targetTopLeft[1]) / targetResolution;
// 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.
var sourceNumericalShiftX = x0;
var sourceNumericalShiftY = y0;
x0 = 0;
y0 = 0;
x1 -= sourceNumericalShiftX;
y1 -= sourceNumericalShiftY;
x2 -= sourceNumericalShiftX;
y2 -= sourceNumericalShiftY;
var augmentedMatrix = [
[x1, y1, 0, 0, u1 - u0],
[x2, y2, 0, 0, u2 - u0],
[0, 0, x1, y1, v1 - v0],
[0, 0, x2, y2, v2 - v0]
];
var affineCoefs = solveLinearSystem(augmentedMatrix);
if (!affineCoefs) {
return;
}
context.save();
context.beginPath();
var centroidX = (u0 + u1 + u2) / 3;
var centroidY = (v0 + v1 + v2) / 3;
var p0 = enlargeClipPoint(centroidX, centroidY, u0, v0);
var p1 = enlargeClipPoint(centroidX, centroidY, u1, v1);
var p2 = enlargeClipPoint(centroidX, centroidY, u2, v2);
// 设置三角的切割范围,只显示这个范围内的图片,多个三角拼接起来就是总体图像
context.moveTo(p1[0], p1[1]);
context.lineTo(p0[0], p0[1]);
context.lineTo(p2[0], p2[1]);
context.clip();
// 最关键的三个方法,经过一系列转换,保证在相应的比例尺下在当前范围内
// 显示正确的Reproject图像
// 直接设置下面3个参数,很差容易能达到想要的效果
// 能够经过将相同的参数输出在本地,进行调试,观察三角内的图像
context.transform(
affineCoefs[0], affineCoefs[2], affineCoefs[1], affineCoefs[3], u0, v0);
context.translate(sourceDataExtent[0] - sourceNumericalShiftX,
sourceDataExtent[3] - sourceNumericalShiftY);
context.scale(sourceResolution / pixelRatio,
-sourceResolution / pixelRatio);
context.drawImage(stitchContext.canvas, 0, 0);
context.restore();
});
// 调试使用,是否显示三角
if (opt_renderEdges) {
context.save();
context.strokeStyle = 'black';
context.lineWidth = 1;
triangulation.getTriangles().forEach(function(triangle, i, arr) {
var target = triangle.target;
var u0 = (target[0][0] - targetTopLeft[0]) / targetResolution;
var v0 = -(target[0][1] - targetTopLeft[1]) / targetResolution;
var u1 = (target[1][0] - targetTopLeft[0]) / targetResolution;
var v1 = -(target[1][1] - targetTopLeft[1]) / targetResolution;
var u2 = (target[2][0] - targetTopLeft[0]) / targetResolution;
var v2 = -(target[2][1] - targetTopLeft[1]) / targetResolution;
context.beginPath();
context.moveTo(u1, v1);
context.lineTo(u0, v0);
context.lineTo(u2, v2);
context.closePath();
context.stroke();
});
context.restore();
}
return context.canvas;
}
复制代码
我以其中一个三角的计算参数为示例,能够看到参数仍是比较复杂的
context.moveTo(1136, 199);
context.lineTo(850, 200);
context.lineTo(1136, 401);
context.clip();
context.transform(
0.011129369548469296, 0.00006403305449769778, 0.00009456173415458175, -0.0111338055978892, 851.2500000000001, 200.00000000000063);
context.translate(-413988.7802610396,
835088.5497620346);
context.scale(351.73160173160176,
-351.73160173160176);
context.drawImage(stitchContext.canvas, 0, 0);
context.restore();
复制代码