我的THREE.js之旅02: 使用THREE.js加載OSM的地圖

之前爲了做個軌跡三維可視化頁面,用了vizicities.js來加載地圖,昨前大概看了下vizicities的代碼,決定自己用three.js寫個demo出來。

三維軌跡效果圖:




涉及的知識點:

  • Web墨卡託投影
  • 經緯度座標與墨卡託投影座標的轉換
  • OSM的地圖切片,切片公式


參考鏈接:

基本思路是,根據地圖切片的原理構建http請求,獲取到切片圖後作爲紋理加載到webgl中;座標的轉換過程是 經緯度->墨卡託->切片圖上的像素座標->webgl中的位置座標;

效果圖如下:(紅色球體爲我的位置)





代碼如下:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/>
        <title></title>
        <style>
            html,body {
                height: 100%;
                width: 100%;
                padding: 0;
                margin: 0;
                overflow: hidden;
            }

            #main {
                background-color: #ddd;
            }
        </style>
    </head>
    <body>
        <div id="main"></div>
    </body>
    <script src="three.js"></script>
    <script src="OrbitControls.js"></script>
    <script>
        var div = document.getElementById("main");
        var width = window.innerWidth
          , height = window.innerHeight;

        div.style.width = width + "px";
        div.style.height = height + "px";

        var fov = 70
          , ratio = width / height;

        console.log("div", width, height);

        var scene, camera, cameraControls, renderer;

        scene = new THREE.Scene();

        camera = new THREE.PerspectiveCamera(fov,ratio,1,1000);
        camera.position.x = 0;
        camera.position.y = -150;
        camera.position.z = 300;
        camera.lookAt(scene.position);

        renderer = new THREE.WebGLRenderer({
            antialias: true//抗鋸齒
        });
        renderer.setSize(width, height);
        renderer.setClearColor(0x000000);

        cameraControls = new THREE.OrbitControls(camera,renderer.domElement);
        cameraControls.target.set(0, 0, 0);
        cameraControls.maxDistance = 400;
        cameraControls.minDistance = 10;
        cameraControls.update();

        div.appendChild(renderer.domElement);

        //創建座標軸並加入到scene  
        var axes = new THREE.AxisHelper(50);
        scene.add(axes);

        var render = function() {
            cameraControls.update();
            renderer.render(scene, camera);
            requestAnimationFrame(render);
        }

        render();
        initListener();

        function initListener() {
            var onmousedown = function(event) {
                div.style.cursor = "move";
                //div.addEventListener("mousemove",onmousemove);
            };

            var onmouseup = function(event) {
                div.style.cursor = "default";
                //div.removeEventListener("mousemove",onmousemove)
            };

            div.addEventListener("mousedown", onmousedown);
            div.addEventListener("mouseup", onmouseup);
        }
        /*******************************************************************/

        //切圖在scene中的大小
        var tileSize = 50;
        //地圖切片服務地址
        var serverURL = "http://c.tile.osm.org/";
        //設置中心經緯度
        var centerLng = 0
          , centerLat = 0;

        //WGS84轉Web墨卡託
        //參考:http://www.opengsc.com/archives/137
        function LonLat2WebMercator(lng, lat) {
            var x = (lng / 180.0) * 20037508.3427892;
            var y;
            if (lat > 85.05112) {
                lat = 85.05112;
            }
            if (lat < -85.05112) {
                lat = -85.05112;
            }
            y = (Math.PI / 180.0) * lat;
            var tmp = Math.PI / 4.0 + y / 2.0;
            y = 20037508.3427892 * Math.log(Math.tan(tmp)) / Math.PI;
            var result = {
                x: x,
                y: y
            };
            return result;
        }

        //Web墨卡託轉成tile上的像素座標,返回像素座標,以及tile編號,在所在tile上的偏移
        function WebMercator2Tileimage(x, y) {
            //對於第18級地圖, 對於我國而言
            var level = 18;
            var r = 20037508.3427892;
            y = r - y;
            x = r + x;
            var size = Math.pow(2, level) * 256;
            var imgx = x * size / (r * 2);
            var imgy = y * size / (r * 2);
            //當前位置在全球切片編號
            var col = Math.floor(imgx / 256);
            var row = Math.floor(imgy / 256);
            console.log("col", col, "row", row);
            //當前位置對應於tile圖像中的位置
            var imgdx = imgx % 256;
            var imgdy = imgy % 256;

            //像素座標
            var position = {
                x: imgx,
                y: imgy
            };
            //tile編號
            var tileinfo = {
                x: col,
                y: row,
                level: 18
            };
            //在所在tile上的偏移
            var offset = {
                x: imgdx,
                y: imgdy
            };

            var result = {
                position: position,
                tileinfo: tileinfo,
                offset: offset
            };
            return result;
        }

        //經緯度到tile,再到WebGL座標
        function LonLat2WebGL(lng, lat) {
            var webMercator = LonLat2WebMercator(lng, lat);
            var tilePos = WebMercator2Tileimage(webMercator.x, webMercator.y).position;

            var centerWM = LonLat2WebMercator(centerLng, centerLat);
            var centerTP = WebMercator2Tileimage(centerWM.x, centerWM.y);
            //相對偏移修正(以centerLng,centerLat所在點tile中心點爲原點,導致的偏移)
            var x = (tilePos.x - centerTP.position.x + (centerTP.offset.x - 256 / 2)) * tileSize / 256;
            var y = (tilePos.y - centerTP.position.y + (-centerTP.offset.y + 256 / 2)) * tileSize / 256;

            var result = {
                x: x,
                y: y
            };
            return result;
        }

        /**
	* 加載一個切圖
	* @param {Object} xno tile編號x
	* @param {Object} yno tile編號y
	* @param {Object} callback
	*/
        function loadImageTile(xno, yno, callback) {
            var level = 18;
            var url = serverURL + level + "/" + xno + "/" + yno + ".png";
            var loader = new THREE.TextureLoader();
            //跨域加載圖片
            loader.crossOrigin = true;
            loader.load(url, function(texture) {
                console.log("loaded tile");
                var geometry = new THREE.PlaneGeometry(tileSize,tileSize,1);
                var material = new THREE.MeshBasicMaterial({
                    map: texture,
                    transparent: true,
                    side: THREE.DoubleSide//雙面顯示
                });
                var mesh = new THREE.Mesh(geometry,material);
                callback(mesh);
            });
        }
        /**
	* 將加載的切圖放到scene
	* @param {Object} mesh
	* @param {Object} x座標  WebGL座標
	* @param {Object} y座標
	*/
        function addTileToScene(mesh, x, y) {
            //mesh的中心位置
            mesh.position.x = x;
            mesh.position.y = y;
            scene.add(mesh);
        }
        /**
	* 輔助函數,用於計算tile應該放在何處
	* @param {Object} dx  tile間相對位置,也就是編號差
	* @param {Object} dy
	*/
        function addTileToSceneHelper(dx, dy) {
            var x = tileSize * dx;
            var y = -tileSize * dy;
            return function(mesh) {
                addTileToScene(mesh, x, y)
            }
            ;
        }
        /**
	* 加載地圖
	* @param {Object} centerX 地圖中間的切圖編號
	* @param {Object} centerY 地圖中間的切圖編號
	*/
        function loadMap(centerX, centerY) {
            var radius = 5;
            for (var i = centerX - radius; i <= centerX + radius; i++) {
                for (var j = centerY - radius; j <= centerY + radius; j++) {
                    //console.log("try to load",i,j,i-centerX,j-centerY);
                    console.log("try to load");
                    loadImageTile(i, j, addTileToSceneHelper(i - centerX, j - centerY));
                }
            }
        }
        /**
	* 標記出當前位置
        * @param {Object} x webGL座標
	* @param {Object} y
	*/
        function markCurrentPosition(x, y) {
            var geometry = new THREE.SphereGeometry(10,30,30);
            var material = new THREE.MeshBasicMaterial({
                color: 0xff0000
            });
            var mesh = new THREE.Mesh(geometry,material);
            mesh.position.x = x;
            mesh.position.y = y;
            scene.add(mesh);

        }

        function main() {
            navigator.geolocation.getCurrentPosition(function(position) {
                var lng = position.coords.longitude;
                var lat = position.coords.latitude;
                console.log("current position in world", lat, lng);
                centerLat = lat;
                centerLng = lng;

                var webMercator = LonLat2WebMercator(lng, lat);
                var tilePos = WebMercator2Tileimage(webMercator.x, webMercator.y);

                //以centerLng所在點tile中心點爲中心,加載tile
                loadMap(tilePos.tileinfo.x, tilePos.tileinfo.y);

                //標記當前位置
                var currentWebGLPos = LonLat2WebGL(lng, lat);
                markCurrentPosition(currentWebGLPos.x, currentWebGLPos.y);
            });
        }

        main();
    </script>
</html>



源碼:https://github.com/lyqandy/THREE_MAP

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