本篇效果圖:
前端
注: 本人畫工較差哈哈哈哈哈哈...vue
geojson
基本概念 本篇咱們要繪製一個矢量地球, 那咱們先要知道矢量地球是由什麼組成的, 好比說要繪製'中國', 那麼咱們只要知道中國邊界上全部的點的座標, 再逐一把這些點連接起來就是一箇中國的輪廓了, 因爲每一個點相距很近因此雖然咱們是用直線連接但依然能夠造成圓滑的球面效果, 簡單理解geojson
就是這樣一組數據, 它裏面有繪製各個國家輪廓所需的全部的點
的信息, 深刻理解你會發現geojson
裏面還有各類分組信息, 但咱們本篇主要講繪製最基本的國家輪廓就不展開討論了, 讓咱們先繪製一款平面地圖。
這是我以前寫過的一篇詳細介紹geojson的文章,有興趣的同窗能夠去了解下, 會有助於你更好的理解地圖: 記一次前端"揭開繪製地圖的神祕面紗"分享會。
本章設計的數學知識都是初級的, 再日後會涉及到矩陣
之類的知識, 到時候我也會用最通俗的方式解釋給你聽, 毫不止於概念而是最通俗的方式方便你理解, 本篇後面會有詳細的經緯度轉xyz
的講解與圖解。git
這裏的概念很基礎也很重要, 若是不熟悉的話要仔細看哦。github
經度是地球上一個地點離一根被稱爲本初子午線的南北方向走線以東或以西的度數。本初子午線的經度是0°,地球上其它地點的經度是向東到180°或向西到180°, 作爲本初子午線的那條線是人選出來的, 每15°一個時區(時區引發的bug我在以前分享過: 時區相關bug)。
如圖所示, 在計算機裏面是用正負數
來區東經與西經, 東經爲正數
西經爲負數
, 度數範圍是[-180, 180]
。
web
過橢球面上某點做法線,該點法線與赤道平面的線面角,其數值在0至90度之間。位於赤道以北的點的緯度叫北緯,記爲N;位於赤道以南的點的緯度稱南緯,記爲S。
如圖所示, 在計算機裏面是用正負數
來區北緯與南緯, 北緯爲正數
南緯爲負數
, 度數範圍是[-90, 90]
。json
在地球上任何地點,只要有只表,有根竹竿,一根捲尺,就可知道當地經緯度。但表必須與該國標準時校對, 具體方法在百度百科有興趣的能夠作下實驗。segmentfault
大郎不要怕咱們畢業這麼久也不用背誦了, 只要知道怎麼用就行, 咱們一塊兒來複習一下:
數組
名稱 | 公式 |
---|---|
sin(∠A) | a/c |
cos(∠A) | b/c |
tan(∠A) | a/b |
geojson
裏面存儲的數據是經緯度, 因此等下咱們要用他把經緯度轉換成座標, 固然geojson
也能夠直接儲存座標。 即兩條射線從圓心向圓周射出,造成一個夾角和夾角正對的一段弧。當這段弧長正好等於圓的半徑時,兩條射線的夾角的弧度爲1。
svg
若是你想固然認爲直接在前端代碼裏寫入Math.sin(30)
就會輸出0.5
那你就錯了。
由於在咱們的Math
運算裏面, 須要輸入的是弧度, 這就是爲啥我上面要複述弧度的概念, 因此想求sin(30)
咱們要這樣寫Math.sin(30 * Math.PI / 180)
。
函數
1弧度
是對應弧長爲半徑的角度, 已知一個圓的周長是 2πr
。2πr
÷ r
個弧度, 也就是2π
弧度爲一個圓。2π
÷360
, 也就是 π/180
。sin(30°)
就是Math.sin(30 * Math.PI / 180)
學到這裏我默認你已經瞭解了geojson
的相關概念, 裏面一個國家可能有多個輪廓而且互相不接壤, 咱們要把它們處理成數組, 也就是這句country.geometry.type === "Polygon"
。/cc_map_3d_pro/src/components/cc_map.vue
import worldGeo from "../assets/geojson/world.geo"; .../ initEarth() { const R = envConifg.r; worldGeo.features.forEach((country) => { if (country.geometry.type === "Polygon") { country.geometry.coordinates = [country.geometry.coordinates]; } var line = countryLine(R, country.geometry.coordinates); this.scene.add(line); }); },
世界的geojson
能夠在個人項目裏找到github查看。
半徑, 點位
傳給了countryLine
方法來處理, 而且把他們都加入到了環境
裏面, 後面會講
把圖形對象都分別放入不一樣的組裏面, 這裏先這樣不擴散知識點。點
繪線
/cc_map_3d_pro/src/utils/countryLine.js
這個方法裏咱們專門繪製國家的輪廓。
import * as THREE from 'three'; function countryLine(R, polygonArr) { let group = new THREE.Group(); polygonArr.forEach(polygon => { let pointArr = []; polygon[0].forEach(elem => { pointArr.push(elem[0], elem[1], 0) }); group.add(line(pointArr)); }); return group; }
new THREE.Group()
在three.js
中文網接摘下來的原話, 它幾乎和Object3D
是相同的,其目的是使得組中對象在語法上的結構更加清晰。
假設我如今生成兩個正方體geometry
, 分別命名爲a 與 b, 那麼我不用下面的寫法
this.scene.add(a); this.scene.add(b);
而是能夠建立一個組:
const group = new THREE.Group(); group.add(a) group.add(b) this.scene.add(group);
再詳細的咱們後續章節會詳細聊
。
line
的方法, 這個方法是此次的一個重要的知識點。line
方法)function line(pointArr) { let geometry = new THREE.BufferGeometry(); let vertices = new Float32Array(pointArr); let attribue = new THREE.BufferAttribute(vertices, 3); geometry.attributes.position = attribue; let material = new THREE.LineBasicMaterial({ color: 0x00aaaa //線條顏色 }); let line = new THREE.LineLoop(geometry, material); return line; }
pointArr
; 由countryLine
方法可知, 這裏是[x1, y1, z1, x2, y2, z2, x3, y3, z3]
這樣的一系列座標, 你可能感受這種形式不太符合js
的思想, 可是它符合webgl
或是svg
的思想, 關於webgl
的知識後續會在講解着色器
的時候會讓你明白的, 如今不用太深研究由於這裏學問很深。
new THREE.BufferGeometry()
; 使用BufferGeometry
能夠有效減小向GPU
傳輸上述數據所需的開銷, 一個國家平均有幾百組點
, 因此再用普通的Geometry
會變的很卡, 你們放心後續在 優化
相關的篇幅裏面我會統一講一遍, 這裏你能夠暫時理解爲一種three.js
轉換的數據流
。
new Float32Array()
; js原生
知識: Float32Array
類型數組表明的是平臺字節順序爲32位的浮點數型數組(對應於 C 浮點數據類型), Float32Array
在數據量較大時性更更好一些, 而且更符合webgl
的參數標準, 關於這類TypedArray
是個大課題, 詳細的我會在着色器
章節好好聊聊 。
new THREE.BufferAttribute()
; 這個類用於存儲與BufferGeometry相關聯的 attribute(例如頂點位置向量,面片索引,法向量,顏色值,UV座標以及任何自定義 attribute), 利用 BufferAttribute
能夠更高效的向GPU傳遞數據。
x1, y1, z1
一組, x2, y2, z2
一組, 以此類推。new THREE.LineBasicMaterial()
; 基礎線條材質, 也就是專業繪製線條的, 能夠調節顏色與粗細, 以及線頭
的樣子。
geometry.attributes.position = attribue
這個寫法看起來很粗魯
, 它的意思就是把圖形的位置信息, 替換成咱們處理好的數組, 也就是爲圖形設置每一個點的位置。
new THREE.LineLoop()
; 環線
也就是首尾相連的線, 就向咱們每次建立一個矩形同樣, 這個方法建立了一條環線。
因爲咱們把z軸
的數值都傳的0, 因此纔會出現下圖這種平面地圖
也有很多庫
是直接作這種平面地球
的, 但咱們的系列文章是要學習圓形地球的, 因此咱們要把經緯度座標
轉換成球面座標
。
咱們就從x y z
逐一開始研究。
y軸
y軸
其實只與緯度
有關, 最簡單就能夠求出來以下圖所示:
r
, 如今咱們要求這個點距離zy平面
的距離。
這個點的x與z
不必定爲0, 因此做垂線不必定落在x軸
上, 可是無論如何做垂線這條線段與xz平面
的夾角是不會變的, 而這個夾角就是緯度
, 因此由此可知咱們已知斜邊的長度爲r
, 三角函數sin(緯度) = 對邊 / 斜邊
, 對邊
就是y軸
的數值, 咱們把使用左右都乘以r
, 最終得出:
sin(緯度) 乘 r = y
x軸
x軸須要點計算咱們一步一步來, 每步都有圖解:
上面演示的是, 咱們能夠把這個點
當作是一個立方體
, 一個已知對角線長度爲r
的立方體, 接下來咱們就能夠把這個立方體單獨拿出來研究, 能夠脫離這個座標系了。
現已知立方體對角線長度爲r
, 高爲y軸
座標, 接下來使用緯度
求出底面對角線。
咱們採用與求y軸
差很少的方式求出x1
的長度:
cos(緯度) 乘 r = x1
由圖可知經度
是下方沿yz平面
展開的對角線的角度, 咱們能夠用sin
的對邊比斜邊求出長度。
sin(經度) * x1 = x
把x1的公式帶入進來:
x = sin(經度) 乘 cos(緯度)
z軸
與x相同的原理,只是這裏咱們用cos
的臨邊比斜邊。
z = cos(經度) 乘 cos(緯度)
實戰的時候別忘了, 先把經緯度轉成弧度
。
// 經緯度轉座標 function lon2xyz(R, longitude, latitude) { const lon = longitude * Math.PI / 180; const lat = latitude * Math.PI / 180; const x = R * Math.cos(lat) * Math.sin(lon); const y = R * Math.sin(lat); const z = R * Math.cos(lon) * Math.cos(lat); return { x, y, z }; } export default lon2xyz;
一些其餘教程會要求 經度
取反, 同時把x與z
進行顛倒,不推薦那種寫法, 咱們就按正常的思路來便可。
通過不懈的努力咱們終於拯救了圓球
, 讓咱們看看他還缺什麼吧:
從上圖咱們能夠看出, 其實問題仍是挺多的, 好比線條之間互相遮蓋, 咱們應該讓這個地球不可透視, 以及暫時這個地球不可點擊, 而且真實度上作的不夠, 真是技術路漫漫那。
下一篇就要講解如何在地圖上打點以及爲地球添加光暈等等效果, 到此時這個系列文章還不到一半哦, 射線拾取國家以及三角拋分方面的知識也會陸續付出水面, 精彩有趣的知識還在後面, 但願與你一塊兒進步。