成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

北京到上海,Three.js 旅行軌跡的可視化

開發 前端
最近從北京搬到了上海,開始了一段新的生活,算是人生中一個比較大的事件,于是特地用 Three.js 做了下可視化。

[[440408]]

本文轉載自微信公眾號「神光的編程秘籍」,作者神說要有光zxg。轉載本文請聯系神光的編程秘籍公眾號。

最近從北京搬到了上海,開始了一段新的生活,算是人生中一個比較大的事件,于是特地用 Three.js 做了下可視化。

在這個地理信息相關的可視化的案例中,我們能學到地圖怎么畫、經緯度如何轉成坐標值,這些是地理可視化的通用技術。

那我們就開始吧。

思路分析

Three.js 畫立方體、畫圓柱、畫不規則圖形我們都畫過,但是如何畫一個地圖呢?

其實地圖也是由線、由多邊形構成的,有了數據我們就能畫出來,缺少的只是數據。

地圖信息的描述是一個通用需求,所以有相應的國際標準,就是 GeoJson,它是通過點、線、多邊形來描述地理信息的。

通過指定點、線、多邊形的類型、然后指定幾個坐標位置,就可以描述出相應的形狀。

geojson 的數據可以通過 geojson.io 這個網站做下預覽。

比如中國地圖的 geojson:

有了這個 json,只要用 Three.js 畫出來就行,通過線和多邊形兩種方式。

但是還有一個問題,geojson 中記錄的是經緯度信息,應該如何轉成二維坐標來畫呢?

這就涉及到了墨卡托轉換,它就是做經緯度轉二維坐標的事情。

這個轉換也不用我們自己實現,可以用 d3 內置的墨卡托坐標轉換函數來做。

這樣,我們就用 Three.js 根據 geojson 來畫出地圖。

我們還要畫一條北京到上海的曲線,這個用貝塞爾曲線畫就行,知道兩個端點的坐標,控制點放在中間的位置。

那怎么知道兩個端點,也就是上海和北京的坐標呢?

這個可以用“百度坐標拾取系統”這個工具,點擊地圖的某個位置,就可以直接拿到那個位置的經緯度。然后我們做一次墨卡托轉換,就拿到坐標了。

地圖畫出來了,旅行的曲線也畫出來了,接下來調整下相機位置,從北京慢慢移動到上海就可以了。

思路理清了,我們來寫下代碼。

代碼實現

我們要引入 d3,然后使用 d3 的墨卡托轉換功能,

  1. const projection = d3.geoMercator() 
  2.     .center([116.412318,39.909843]) 
  3.     .translate([0, 0]); 

中間點的坐標就是北京的經緯度,就是我們通過“百度坐標拾取工具”那里拿到的。

北京和上海的坐標位置也可以把經緯度做墨卡托轉換得到:

  1. let beijingPosition= projection([116.412318,39.909843]); 
  2. let shanghaiPosition = projection([121.495721,31.236797]); 

先不著急畫旅行的曲線,先來畫地圖吧。

先加載 geojson:

  1. const loader = new THREE.FileLoader(); 
  2. loader.load('./data/china.json', (data) => { 
  3.     const jsondata = JSON.parse(data); 
  4.     generateGeometry(jsondata); 
  5. }) 

然后根據 json 的信息畫地圖。

遍歷 geojson 的數據,把每個經緯度通過墨卡托轉換變成坐標,然后分別用線和多邊形畫出來。

畫多邊形的時候遇到北京和上海用黃色,其他城市用藍色。

  1. function generateGeometry(jsondata) { 
  2.   const map = new THREE.Group(); 
  3.      
  4.   jsondata.features.forEach((elem) => { 
  5.     const province = new THREE.Group(); 
  6.  
  7.     // 經緯度信息 
  8.     const coordinates = elem.geometry.coordinates; 
  9.     coordinates.forEach((multiPolygon) => { 
  10.       multiPolygon.forEach((polygon) => { 
  11.         // 畫輪廓線 
  12.         const line = drawBoundary(polygon); 
  13.  
  14.         // 畫多邊形 
  15.         const provinceColor = ['北京市''上海市'].includes(elem.properties.name) ? 'yellow' : 'blue'
  16.         const mesh = drawExtrudeMesh(polygon, provinceColor); 
  17.  
  18.         province.add(line); 
  19.         province.add(mesh); 
  20.       }); 
  21.     }); 
  22.  
  23.     map.add(province); 
  24.   }) 
  25.  
  26.   scene.add(map); 

然后分別實現畫輪廓線和畫多邊形:

輪廓線(Line)就是指定一系列頂點來構成幾何體(Geometry),然后指定材質(Material)顏色為黃色:

  1. function drawBoundary(polygon) { 
  2.     const lineGeometry = new THREE.Geometry(); 
  3.  
  4.     for (let i = 0; i < polygon.length; i++) { 
  5.       const [x, y] = projection(polygon[i]); 
  6.       lineGeometry.vertices.push(new THREE.Vector3(x, -y, 0)); 
  7.     } 
  8.  
  9.     const lineMaterial = new THREE.LineBasicMaterial({  
  10.       color: 'yellow' 
  11.     }); 
  12.  
  13.     return new THREE.Line(lineGeometry, lineMaterial); 

現在的效果是這樣的:

多邊形是 ExtrudeGeometry,也就是可以先畫出形狀(shape),然后通過拉伸變成三維的。

  1. function drawExtrudeMesh(polygon, color) { 
  2.     const shape = new THREE.Shape(); 
  3.  
  4.     for (let i = 0; i < polygon.length; i++) { 
  5.       const [x, y] = projection(polygon[i]); 
  6.  
  7.       if (i === 0) { 
  8.         shape.moveTo(x, -y); 
  9.       } 
  10.  
  11.       shape.lineTo(x, -y); 
  12.     } 
  13.  
  14.     const geometry = new THREE.ExtrudeGeometry(shape, { 
  15.       depth: 0, 
  16.       bevelEnabled: false 
  17.     }); 
  18.  
  19.     const material = new THREE.MeshBasicMaterial({ 
  20.       color, 
  21.       transparent: true
  22.       opacity: 0.2, 
  23.     }) 
  24.  
  25.     return new THREE.Mesh(geometry, material); 

第一個點用 moveTo,后面的點用 lineTo,這樣連成一個多邊形,然后指定厚度為 0,指定側面不需要多出一塊斜面(bevel)。

這樣,我們就給每個省都填充上了顏色,北京和上海是黃色,其余省是藍色。

接下來,在北京和上海之間畫一條貝塞爾曲線:

  1. const line = drawLine(beijingPosition, shanghaiPosition); 
  2. scene.add(line); 

貝塞爾曲線用 QuadraticBezierCurve3 來畫,控制點指定中間位置的點。

  1. function drawLine(pos1, pos2) { 
  2.   const [x0, y0, z0] = [...pos1, 0]; 
  3.   const [x1, y1, z1] = [...pos2, 0]; 
  4.  
  5.   const geomentry = new THREE.Geometry(); 
  6.   geomentry.vertices = new THREE.QuadraticBezierCurve3( 
  7.       new THREE.Vector3(-x0, -y0, z0), 
  8.       new THREE.Vector3(-(x0 + x1) / 2, -(y0 + y1) / 2, -10), 
  9.       new THREE.Vector3(-x1, -y1, z1), 
  10.   ).getPoints(); 
  11.  
  12.   const material = new THREE.LineBasicMaterial({color: 'white'}); 
  13.  
  14.   const line = new THREE.Line(geomentry, material); 
  15.   line.rotation.y = Math.PI; 
  16.  
  17.   return line; 

這樣,地圖和旅行軌跡就都畫完了:

當然,還有渲染器、相機、燈光的初始化代碼:

渲染器:

  1. const renderer = new THREE.WebGLRenderer(); 
  2. renderer.setClearColor(0x000000); 
  3. renderer.setSize(window.innerWidth, window.innerHeight); 
  4. document.body.appendChild(renderer.domElement); 

渲染器設置背景顏色為黑色,畫布大小為窗口大小。

燈光:

  1. let ambientLight = new THREE.AmbientLight(0xffffff); 
  2. scene.add(ambientLight); 

燈光用環境光,也就是每個方向的明暗都一樣。

相機:

  1. const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); 
  2. camera.position.set(0, 0, 10); 
  3. camera.lookAt(scene.position); 

相機用透視相機,特點是近大遠小,需要指定看的角度,寬高比,和遠近的范圍這樣四個參數。

位置設置在 0 0 10 的位置,在這個位置去觀察 0 0 0,就是北京上方的俯視圖(我們做墨卡托轉換的時候指定了北京為中心)。

修改了相機位置之后,看到的地圖大了許多:

接下來就是一幀幀的渲染,在每幀渲染的時候移動下相機位置,這樣就是從北京到上海的一個移動的效果:

  1. function render() { 
  2.     if(camera.position.x < shanghaiPosition[0]) { 
  3.         camera.position.x += 0.1; 
  4.     }   
  5.     if(camera.position.y > -shanghaiPosition[1]) { 
  6.         camera.position.y -= 0.2; 
  7.     } 
  8.     renderer.render(scene, camera); 
  9.     requestAnimationFrame(render); 

大功告成!我們來看下最終的效果吧:

代碼上傳到了 github: https://github.com/QuarkGluonPlasma/threejs-exercize

也在這里貼一份:

  1. <!DOCTYPE html> 
  2. <html lang="en"
  3.   <head> 
  4.     <meta charset="UTF-8" /> 
  5.     <meta http-equiv="X-UA-Compatible" content="IE=edge" /> 
  6.     <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 
  7.     <title>map-travel</title> 
  8.     <style> 
  9.       html body { 
  10.         height: 100%; 
  11.         width: 100%; 
  12.         margin: 0; 
  13.         padding: 0; 
  14.         overflow: hidden; 
  15.       } 
  16.     </style> 
  17.   </head> 
  18.   <body> 
  19.     <script src="./js/three.js"></script> 
  20.     <script src="./js/d3.js"></script> 
  21.     <script> 
  22.       const scene = new THREE.Scene(); 
  23.  
  24.       const renderer = new THREE.WebGLRenderer(); 
  25.       renderer.setClearColor(0x000000); 
  26.       renderer.setSize(window.innerWidth, window.innerHeight); 
  27.       document.body.appendChild(renderer.domElement); 
  28.  
  29.       const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); 
  30.       camera.position.set(0, 0, 10); 
  31.       camera.lookAt(scene.position); 
  32.  
  33.       let ambientLight = new THREE.AmbientLight(0xffffff); 
  34.       scene.add(ambientLight); 
  35.  
  36.       function create() { 
  37.           const loader = new THREE.FileLoader(); 
  38.           loader.load('./data/china.json', (data) => { 
  39.             const jsondata = JSON.parse(data); 
  40.             generateGeometry(jsondata); 
  41.           }) 
  42.       } 
  43.  
  44.       const projection = d3.geoMercator() 
  45.             .center([116.412318,39.909843]) 
  46.             .translate([0, 0]); 
  47.  
  48.       let beijingPosition= projection([116.412318,39.909843]); 
  49.       let shanghaiPosition = projection([121.495721,31.236797]); 
  50.  
  51.       function drawBoundary(polygon) { 
  52.         const lineGeometry = new THREE.Geometry(); 
  53.  
  54.         for (let i = 0; i < polygon.length; i++) { 
  55.           const [x, y] = projection(polygon[i]); 
  56.           lineGeometry.vertices.push(new THREE.Vector3(x, -y, 0)); 
  57.         } 
  58.  
  59.         const lineMaterial = new THREE.LineBasicMaterial({  
  60.           color: 'yellow' 
  61.         }); 
  62.  
  63.         return new THREE.Line(lineGeometry, lineMaterial); 
  64.       } 
  65.  
  66.       function drawExtrudeMesh(polygon, color) { 
  67.         const shape = new THREE.Shape(); 
  68.  
  69.         for (let i = 0; i < polygon.length; i++) { 
  70.           const [x, y] = projection(polygon[i]); 
  71.  
  72.           if (i === 0) { 
  73.             shape.moveTo(x, -y); 
  74.           } 
  75.  
  76.           shape.lineTo(x, -y); 
  77.         } 
  78.  
  79.         const geometry = new THREE.ExtrudeGeometry(shape, { 
  80.           depth: 0, 
  81.           bevelEnabled: false 
  82.         }); 
  83.  
  84.         const material = new THREE.MeshBasicMaterial({ 
  85.           color, 
  86.           transparent: true
  87.           opacity: 0.2, 
  88.         }) 
  89.          
  90.         return new THREE.Mesh(geometry, material); 
  91.       } 
  92.  
  93.       function generateGeometry(jsondata) { 
  94.           const map = new THREE.Group(); 
  95.  
  96.           jsondata.features.forEach((elem) => { 
  97.             const province = new THREE.Group(); 
  98.  
  99.             const coordinates = elem.geometry.coordinates; 
  100.             coordinates.forEach((multiPolygon) => { 
  101.               multiPolygon.forEach((polygon) => { 
  102.                 const line = drawBoundary(polygon); 
  103.  
  104.                 const provinceColor = ['北京市''上海市'].includes(elem.properties.name) ? 'yellow' : 'blue'
  105.                 const mesh = drawExtrudeMesh(polygon, provinceColor); 
  106.                  
  107.                 province.add(line); 
  108.                 province.add(mesh); 
  109.               }); 
  110.             }); 
  111.  
  112.             map.add(province); 
  113.           }) 
  114.  
  115.           scene.add(map); 
  116.           const line = drawLine(beijingPosition, shanghaiPosition); 
  117.           scene.add(line); 
  118.  
  119.       } 
  120.  
  121.       function render() { 
  122.         if(camera.position.x < shanghaiPosition[0]) { 
  123.             camera.position.x += 0.1; 
  124.         }   
  125.         if(camera.position.y > -shanghaiPosition[1]) { 
  126.             camera.position.y -= 0.2; 
  127.         } 
  128.         renderer.render(scene, camera); 
  129.         requestAnimationFrame(render); 
  130.       } 
  131.  
  132.       function drawLine(pos1, pos2) { 
  133.           const [x0, y0, z0] = [...pos1, 0]; 
  134.           const [x1, y1, z1] = [...pos2, 0]; 
  135.  
  136.           const geomentry = new THREE.Geometry(); 
  137.           geomentry.vertices = new THREE.QuadraticBezierCurve3( 
  138.               new THREE.Vector3(-x0, -y0, z0), 
  139.               new THREE.Vector3(-(x0 + x1) / 2, -(y0 + y1) / 2, -10), 
  140.               new THREE.Vector3(-x1, -y1, z1), 
  141.           ).getPoints(); 
  142.  
  143.           const material = new THREE.LineBasicMaterial({color: 'white'}); 
  144.  
  145.           const line = new THREE.Line(geomentry, material); 
  146.           line.rotation.y = Math.PI; 
  147.  
  148.           return line; 
  149.       } 
  150.  
  151.       create(); 
  152.       render(); 
  153.     </script> 
  154.   </body> 
  155. </html> 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

總結

地圖形狀的表示是基于 geojson 的規范,它是由點、線、多邊形等信息構成的。

用 Three.js 或者其他繪制方式來畫地圖只需要加載 geojson 的數據,然后通過線和多邊型把每一部分畫出來。

畫之前還要把經緯度轉成坐標,這需要用到墨卡托轉換。

我們用 Three.js 畫線是通過指定一系列頂點構成 Geometry,而畫多邊形是通過繪制一個形狀,然后用 ExtrudeGeometry(擠壓幾何體) 拉伸成三維。墨卡托轉換直接使用了 d3 的內置函數。旅行的效果是通過一幀幀的移動相機位置來實現的。

熟悉了 geojson 和墨卡托轉換,就算是入門地理相關的可視化了。

你是否也想做一些和地理相關的可視化或者交互呢?不妨來嘗試下吧。

 

責任編輯:武曉燕 來源: 神光的編程秘籍
相關推薦

2021-11-27 10:42:01

Three.js3D可視化AudioContex

2025-06-17 08:15:00

VTK.jsThree.js3D

2021-12-07 13:44:43

Three.js3D可視化3D 游戲

2019-11-29 09:30:37

Three.js3D前端

2017-05-08 11:41:37

WebGLThree.js

2021-11-22 06:14:45

Three.js3D 渲染花瓣雨

2020-03-11 14:39:26

數據可視化地圖可視化地理信息

2024-07-18 06:58:36

2021-04-23 16:40:49

Three.js前端代碼

2025-05-15 08:45:00

開源前端手勢

2022-07-15 13:09:33

Three.js前端

2021-12-03 07:27:30

全景瀏覽Three.js

2022-01-16 19:23:25

Three.js粒子動畫群星送福

2025-03-13 10:54:18

2017-10-14 13:54:26

數據可視化數據信息可視化

2022-08-26 09:15:58

Python可視化plotly

2009-04-21 14:26:41

可視化監控IT管理摩卡

2021-04-21 12:04:47

JS引擎流程

2022-03-07 09:20:00

JavaScripThree.jsNFT

2022-07-08 10:39:09

Three.js元宇宙VR
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 夜夜爽99久久国产综合精品女不卡 | 久久精品国产亚洲夜色av网站 | 免费看黄视频网站 | 欧美国产中文字幕 | 国产一二区视频 | 日韩精品视频在线播放 | 欧美久久久久久 | 国产精品成人69xxx免费视频 | 九九99久久 | 欧美日韩在线视频一区 | 亚洲三级在线观看 | 欧美在线视频一区二区 | 国产精品中文字幕在线播放 | 91精品国产乱码久久久久久 | 91视频.| 亚洲成人一级 | 中文字幕视频在线观看免费 | 一区二区av | 美女精品一区 | 国产三级一区二区 | 日韩视频区 | 中文字幕在线免费 | 99精品视频在线观看免费播放 | 午夜视频一区二区三区 | 亚洲国产成人在线视频 | 区一区二区三在线观看 | 欧美激情精品久久久久久变态 | 日韩欧美在线一区二区 | 天堂一区 | av中文字幕在线 | 中文字幕专区 | 午夜免费福利片 | 久久久久国产精品一区二区 | 午夜影晥 | 亚洲国产成人av | 天堂一区二区三区 | 国产成人免费在线 | 国产一区二区小视频 | 自拍视频国产 | 99视频免费| 日韩在线观看一区 |