通過 OpenLayers 加載CAD導出位圖 和 math.js 構造的仿射變換實現地理座標系到任意CAD圖上像素座標系的互轉

WebGIS開發過程中會遇到這樣一種情況:需要使用OpenLayers加載一個未校準的CAD導出的位圖;並且還需要通過經緯度座標數據在這個位圖上做一些標記,還需要能通過在OpenLayers取得的圖上要素的像素座標獲知實際的經緯度。

總結起來就是兩個需求:

  1. 加載位圖
  2. 經緯度座標與像素座標互轉

分析:

由於從CAD導出的位圖並不帶有定位信息,所以需要通過仿射變換將圖上的像素座標轉換到地理座標。

即:(左邊爲某廠的衛星地圖,右邊爲該廠的CAD導出位圖,最終實現效果就是用OpenLayers加載位圖,並實現座標轉換)

關於求解仿射變換的過程請見這裏。主要的算法思想如下:

Arcgis中就帶有了仿射變換的計算模塊,OpenLayers沒有仿射變換計算的能力,所以使用math.js這個數學庫來進行實現。

代碼:

算法在上面的截圖已經有了,直接用相應的API實現就好:

        //定義仿射變換函數
        function affineTransform(point, from, to) {
            if (from.length != to.length) return;
            //根據參數構造仿射變換所需的矩陣            
            var X = [];
            var Y = [];
            var I = [];
            var U = [];
            var V = [];
            from.forEach((item, index) => {
                X.push(item[0]);
                Y.push(item[1]);
                I.push(1);
                U.push([to[index][0]])
                V.push([to[index][1]])
            })
            //開始最小二乘法的計算過程
            var XYIt = [X, Y, I];
            var resultINV = math.inv(math.multiply(XYIt, math.transpose(XYIt)))
            var resultMulti = math.multiply(resultINV, XYIt);
            var vec1 = math.multiply(resultMulti, U)
            var vec2 = math.multiply(resultMulti, V)
            //使用vec1和vec2計算轉換後的座標
            return [vec1[0][0] * point[0] + point[1] * vec1[1][0] + vec1[2][0], vec2[0][0] * point[0] + point[1] * vec2[1][0] + vec2[2][0]]
        }

 CAD導出的位圖直接使用ImageStatic加載,並自定義一個像素座標系:


        //定義地圖的像素座標四至
        var extent = [0, 0, 4000, 2000];

        //定義地圖的投影座標系,像素座標
        var projection = new ol.proj.Projection({
            code: 'factory-image',
            units: 'pixels',
            extent: extent
        });

        //初始化地圖
        var map = new ol.Map({
            layers: [
                new ol.layer.Image({
                    source: new ol.source.ImageStatic({
                        url: './data/10-9.png',
                        projection: projection,
                        imageExtent: extent
                    })
                })
            ],
            target: 'map',
            view: new ol.View({
                projection: projection,
                center: ol.extent.getCenter(extent),
                zoom: 2,
                maxZoom: 8
            })
        });

概略分別獲取圖上廠區四角的座標,圖片像素座標是用potoshop量取的,經緯度座標是在google地圖上拾取的:


        var upperLeft = [119.071450, 39.309006];
        var lowerLeft = [119.074536, 39.305893];
        var upperRight = [119.075858, 39.311641];
        var lowerRight = [119.078934, 39.308527];

        var upperLeftPixel = [959, 1897];
        var lowerLeftPixel = [959, 112];
        var upperRightPixel = [2924, 1897];
        var lowerRightPixel = [2924, 112];

 包括使用旗杆座標打點測試的完整代碼:

<!DOCTYPE html>
<html>

<head>
    <title>廠區地圖計算</title>
    <link rel="stylesheet" href="https://openlayers.org/en/v3.20.1/css/ol.css" type="text/css">
    <script src="https://unpkg.com/[email protected]/dist/math.js"></script>
    <script src="https://openlayers.org/en/v3.20.1/build/ol.js"></script>

</head>
<style>

</style>

<body>
    <div id="map" class="map"></div>
    <script>

        //定義仿射變換函數
        function affineTransform(point, from, to) {
            if (from.length != to.length) return;
            var X = [];
            var Y = [];
            var I = [];
            var U = [];
            var V = [];
            from.forEach((item, index) => {
                X.push(item[0]);
                Y.push(item[1]);
                I.push(1);
                U.push([to[index][0]])
                V.push([to[index][1]])
            })
            var XYIt = [X, Y, I];
            var resultINV = math.inv(math.multiply(XYIt, math.transpose(XYIt)))
            var resultMulti = math.multiply(resultINV, XYIt);
            var vec1 = math.multiply(resultMulti, U)
            var vec2 = math.multiply(resultMulti, V)
            return [vec1[0][0] * point[0] + point[1] * vec1[1][0] + vec1[2][0], vec2[0][0] * point[0] + point[1] * vec2[1][0] + vec2[2][0]]
        }

        //Google座標

        var upperLeft = [119.071450, 39.309006];
        var lowerLeft = [119.074536, 39.305893];
        var upperRight = [119.075858, 39.311641];
        var lowerRight = [119.078934, 39.308527];

        var upperLeftPixel = [959, 1897];
        var lowerLeftPixel = [959, 112];
        var upperRightPixel = [2924, 1897];
        var lowerRightPixel = [2924, 112];



        //定義地圖的像素座標四至
        var extent = [0, 0, 4000, 2000];

        //定義地圖的投影座標系,像素座標
        var projection = new ol.proj.Projection({
            code: 'factory-image',
            units: 'pixels',
            extent: extent
        });

        //初始化地圖
        var map = new ol.Map({
            layers: [
                new ol.layer.Image({
                    source: new ol.source.ImageStatic({
                        url: './data/10-9.png',
                        projection: projection,
                        imageExtent: extent
                    })
                })
            ],
            target: 'map',
            view: new ol.View({
                projection: projection,
                center: ol.extent.getCenter(extent),
                zoom: 2,
                maxZoom: 8
            })
        });

        //這裏用旗杆的座標演示座標轉換的使用
        var flagPole = [119.077710, 39.309195];

        var flagPolePixel = affineTransform(
            flagPole, 
        [upperLeft, lowerLeft, upperRight, lowerRight], 
        [upperLeftPixel, lowerLeftPixel, upperRightPixel, lowerRightPixel]
        )

        var p=affineTransform(
            flagPolePixel, [upperLeftPixel, lowerLeftPixel, upperRightPixel, lowerRightPixel],
        [upperLeft, lowerLeft, upperRight, lowerRight]
        
        )
        console.log(flagPole);
        console.log(p);
        var f = new ol.Feature(new ol.geom.Point(flagPolePixel));

        var vSource = new ol.source.Vector();
        var vLayer = new ol.layer.Vector({
            source: vSource
        })

        vSource.addFeature(f);

        f.setStyle(
            new ol.style.Style({
                image: new ol.style.Icon({
                    src: './data/icon.png',
                    anchor: [0.5, 1],
                    scale: 0.3

                }),

            })
        )

        map.addLayer(vLayer);
        map.getView().fit(extent, map.getSize())
        map.render()


    </script>
</body>

</html>

這裏使用旗杆的座標進行了轉換和逆轉換,並在console裏輸出,結果如下:

使用圖上像素座標轉換的經緯度,在Google地圖上標記旗杆的位置如下:

 經過多次計算和實地對比,精度差距大約在1米以內,比較符合實際需要。


我在企鵝家的課堂和CSDN學院都開通了《OpenLayers實例詳解》課程,歡迎報名學習。搜索關鍵字OpenLayers就能看到。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章