本篇效果图:前端
注: 本人画工较差哈哈哈哈哈哈...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
进行颠倒,不推荐那种写法, 咱们就按正常的思路来便可。
通过不懈的努力咱们终于拯救了圆球
, 让咱们看看他还缺什么吧:
从上图咱们能够看出, 其实问题仍是挺多的, 好比线条之间互相遮盖, 咱们应该让这个地球不可透视, 以及暂时这个地球不可点击, 而且真实度上作的不够, 真是技术路漫漫那。
下一篇就要讲解如何在地图上打点以及为地球添加光晕等等效果, 到此时这个系列文章还不到一半哦, 射线拾取国家以及三角抛分方面的知识也会陆续付出水面, 精彩有趣的知识还在后面, 但愿与你一块儿进步。