背景
OpenLayers 3 新版本能夠以不同于服務(wù)器提供的坐標(biāo)系顯示來(lái)自WMS,WMTS,靜態(tài)圖像和許多其他來(lái)源的柵格數(shù)據(jù)。圖像的地圖投影的轉(zhuǎn)換直接在Web瀏覽器中進(jìn)行逗嫡。任何Proj4js支持的坐標(biāo)參考系統(tǒng)中的視圖都是可能的,并且以前不兼容的圖層現(xiàn)在可以組合和覆蓋株依。
Raster Reproject意義
可以在瀏覽器端實(shí)現(xiàn)不同資源在不同投影下的轉(zhuǎn)換,不再依賴于服務(wù)端處理延窜,比如在Geoserver中指定投影類型恋腕,還是相當(dāng)給力的
效果圖
還可以看以下效果對(duì)比圖
3857使用web Mercator 中國(guó)區(qū)域是方形,Reproject到4326時(shí)逆瑞,就有些扁了荠藤,在相同的中心點(diǎn),縮放級(jí)別下获高,地圖的視察范圍也不太一致哈肖,4326經(jīng)過(guò)壓扁后,能顯示更多的范圍念秧。
再來(lái)一張放大后的淤井,變形明顯。
分析過(guò)程
以reprojection-image為例說(shuō)明,使用的是三角仿射變換triangle affine transformation币狠。
三角形動(dòng)態(tài)計(jì)算游两,以正好鋪滿當(dāng)前視口
不同的縮放級(jí)別1
不同的縮放級(jí)別2
不同的縮放級(jí)別3
不同的縮放級(jí)別4
不同的比例尺下會(huì)更新三角格網(wǎng)的大小,以恰好平分地圖視口漩绵,至少兩個(gè)三角
每個(gè)三角形單獨(dú)進(jìn)行Reproject贱案,不同三角之間互不影響
對(duì)三角格網(wǎng)渲染進(jìn)行Degbu過(guò)濾后示意
渲染其中一部分三角
這個(gè)圖能方便明白R(shí)eproject原理
- 首先劃分三角格網(wǎng)
- 根據(jù)位置,計(jì)算每個(gè)三角網(wǎng)對(duì)應(yīng)的原始raster位置和區(qū)域止吐,內(nèi)部使用數(shù)學(xué)方法宝踪,求出轉(zhuǎn)換參數(shù),對(duì)每個(gè)三角網(wǎng)進(jìn)行轉(zhuǎn)換碍扔,這里其實(shí)并沒(méi)有對(duì)每個(gè)像素進(jìn)行Reproject瘩燥,而是以三角為最小單位,進(jìn)行Reproject, 理論上只要三角足夠小蕴忆,通過(guò)積分是可以達(dá)到同樣的效果的颤芬,相比像素角度的處理效率也會(huì)高很多
- 將多個(gè)三角進(jìn)行無(wú)緣拼接
核心實(shí)現(xiàn)
三角Reproject
源碼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的內(nèi)部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);
// 對(duì)三角進(jìn)行循環(huán)處理
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);
// 設(shè)置三角的切割范圍,只顯示這個(gè)范圍內(nèi)的圖片套鹅,多個(gè)三角拼接起來(lái)就是整體圖像
context.moveTo(p1[0], p1[1]);
context.lineTo(p0[0], p0[1]);
context.lineTo(p2[0], p2[1]);
context.clip();
// 最關(guān)鍵的三個(gè)方法站蝠,通過(guò)一系列轉(zhuǎn)換,保證在相應(yīng)的比例尺下在當(dāng)前范圍內(nèi)
// 顯示正確的Reproject圖像
// 直接設(shè)置下面3個(gè)參數(shù)卓鹿,不好容易能達(dá)到想要的效果
// 可以通過(guò)將相同的參數(shù)輸出在本地菱魔,進(jìn)行調(diào)試,觀察三角內(nèi)的圖像
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();
});
// 調(diào)試使用吟孙,是否顯示三角
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;
}
我以其中一個(gè)三角的計(jì)算參數(shù)為示例澜倦,可以看到參數(shù)還是比較復(fù)雜的
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();
其中,進(jìn)行仿射變換的轉(zhuǎn)換過(guò)程及數(shù)學(xué)原理杰妓,還需要進(jìn)一步研究藻治。