Openlayers raster reprojection 柵格重投影原理分析

背景

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)給力的

效果圖

image

還可以看以下效果對(duì)比圖


4326
3857

3857使用web Mercator 中國(guó)區(qū)域是方形,Reproject到4326時(shí)逆瑞,就有些扁了荠藤,在相同的中心點(diǎn),縮放級(jí)別下获高,地圖的視察范圍也不太一致哈肖,4326經(jīng)過(guò)壓扁后,能顯示更多的范圍念秧。

再來(lái)一張放大后的淤井,變形明顯。

image

分析過(guò)程

reprojection-image為例說(shuō)明,使用的是三角仿射變換triangle affine transformation币狠。

三角形動(dòng)態(tài)計(jì)算游两,以正好鋪滿當(dāng)前視口

不同的縮放級(jí)別1


不同的縮放級(jí)別1

不同的縮放級(jí)別2


不同的縮放級(jí)別2

不同的縮放級(jí)別3


不同的縮放級(jí)別3

不同的縮放級(jí)別4


不同的縮放級(jí)別4

不同的比例尺下會(huì)更新三角格網(wǎng)的大小,以恰好平分地圖視口漩绵,至少兩個(gè)三角

每個(gè)三角形單獨(dú)進(jìn)行Reproject贱案,不同三角之間互不影響

對(duì)三角格網(wǎng)渲染進(jìn)行Degbu過(guò)濾后示意

渲染其中一部分三角


渲染偶數(shù)三角

這個(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();
image

其中,進(jìn)行仿射變換的轉(zhuǎn)換過(guò)程及數(shù)學(xué)原理杰妓,還需要進(jìn)一步研究藻治。

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市巷挥,隨后出現(xiàn)的幾起案子桩卵,更是在濱河造成了極大的恐慌,老刑警劉巖倍宾,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雏节,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡高职,警方通過(guò)查閱死者的電腦和手機(jī)钩乍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)怔锌,“玉大人寥粹,你說(shuō)我怎么就攤上這事变过。” “怎么了排作?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵牵啦,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我妄痪,道長(zhǎng)哈雏,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任衫生,我火速辦了婚禮裳瘪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘罪针。我一直安慰自己彭羹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布泪酱。 她就那樣靜靜地躺著派殷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪墓阀。 梳的紋絲不亂的頭發(fā)上毡惜,一...
    開(kāi)封第一講書(shū)人閱讀 49,821評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音斯撮,去河邊找鬼经伙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛勿锅,可吹牛的內(nèi)容都是我干的帕膜。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼溢十,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼垮刹!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起张弛,我...
    開(kāi)封第一講書(shū)人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤危纫,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后乌庶,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡契耿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年瞒大,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搪桂。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡透敌,死狀恐怖盯滚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情酗电,我是刑警寧澤魄藕,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站撵术,受9級(jí)特大地震影響背率,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嫩与,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一寝姿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧划滋,春花似錦饵筑、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至同窘,卻和暖如春玄帕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背塞椎。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工桨仿, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人案狠。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓服傍,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親骂铁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吹零,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容