# 一、绘制地球

​ 关于绘制地球,一开始是想找一张地球图片当做纹理绘制在球体上,之后想做用户通过鼠标选中国家并产生高光,想让纹理的一部分发光想不到怎么做,后面发现了 geojson。可以获取地球的 geojson 数据绘制一个地球。可以使用 echarts 绘制一个地球,然后使用 canvasTexture 贴在球上。至于如何选中国家,可以让用户的鼠标射出一条射线,然后与球体做碰撞检测,获取碰撞点,将点的空间坐标转化为经纬度,然后根据经纬度在 echarts 中获取对应的国家,在 echarts 中高亮,然后监听 echarts 渲染完成事件,再将地球的纹理换成 echarts 渲染完成的图片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import mapData from './world.json'
const canvas = document.getElementById('world')
const chart = echarts.init(canvas)
echarts.registerMap('world',{geoJSON:mapData})
const option ={
geo:[{
type: 'map',
map: 'world',
}]
}
chart.setOption(option)
this.object.material.map = new THREE.CanvasTexture(canvas)
this.object.material.needsUpdate = true //如果不加这个就没有效果,我猜是echarts是先生成canvas,然后再绘制,绘制是异步的,此时获取的canvas没有数据,所以需要这个设置为true
this.renderer.render(this.scene, this.camera)

​ 但是绘制效果不尽如人意。后面再了解到了 echarts 在 5.3 版本后支持不同的投影方式,geojson 数据是经纬度,可以通过不同的转化绘制出不同形状的地图。其中有一种投影方式是 geoOrthographic。也就是将地球投影到一个平面的方法。假设使投影面与地球相切,然后将地球投影到平面上,d3-geo 提供了不同的投影方式。

1
2
3
4
5
6
7
8
9
10
11
12
import { geoOrthographic } from 'd3-geo'
const projection = geoOrthographic()
const option ={
geo:[{
type: 'map',
map: 'world',
projection: {
project: (point) =>projection(point),
unproject: (point) => projection.invert(point)
}
}]
}

​ 效果不错,或许不需要球。毕竟用户最终看到的就是圆面。好消息是成功将地球可视化中移除了地球。接下来就是如何让这个平面旋转了。

# 二、让地球旋转

​ 上面的地球还有一个问题就是国家与国家间会连在一起。

1
2
3
4
5
6
7
8
9
geo:[{
type: 'map',
map: 'world',
projection: {
project: (point) =>projection(point),
unproject: (point) => projection.invert(point),
stream:projection.stream //估计是上面那个是把所有国家都投影了,但其实能看到的之后的国家范围在180经度。
}
}]
1
2
3
4
5
6
7
8
9
10
function changerotate(){
let [x,y,z] = projection.rotate()
x+=1
y+=2
z+=3
projection.rotate([x,y,z])
chart.setOption(option)
requestAnimationFrame(changerotate)
}
requestAnimationFrame(changerotate)

​ 分别测试让地球绕着 xyz 轴旋转,结果发现这个 xy 轴与一般认为的 xy 轴不一样,反过来了。接下来就是监听用户鼠标变化左右移动就是旋转 x 轴,上下移动就是旋转 y 轴。然后在调用 echarts 的 setOption 时出现了问题。 setOption should not be called during main process. 这个函数无法在主进程中调用。大概是渲染没结束就继续渲染,然后出错了。可以用 requestAnimation 或者 echarts 提供了一个渲染完成后的事件,在渲染完成后再继续渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
startRotateChange(e){
this.x = e.clientX
this.y = e.clientY
if(this.earthRotateChange) return
this.earthRotateChange = true
},
rotateChange(e){
if(!this.earthRotateChange) return
this.rotateX = e.clientX
this.rotateY = e.clientY
if(!this.rotateAnimation) requestAnimationFrame(this.changerotate)
this.rotateAnimation = true
},
stopRotateChange(){
if(!this.earthRotateChange) return
this.earthRotateChange = false
this.rotateAnimation = false
cancelAnimationFrame(this.animationId)
},
changerotate(){
let [x,y,z] = this.projection.rotate()
let dx = this.rotateX - this.x //鼠标移动后的x坐标
let dy = this.rotateY - this.y //鼠标移动后的y坐标
this.x = this.rotateX
this.y = this.rotateY
x +=dx
y -=dy
this.projection.rotate([x,y,z])
this.chart.setOption(this.option)
this.animationId = requestAnimationFrame(this.changerotate)
}

# 三、给地球上颜色

​ 给陆地加上陆地的颜色,给海洋加上海洋的颜色。陆地还好说,直接给绘制的区域加上颜色即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
geo:[{
type: 'map',
map: 'world',
projection: {
project: (point) =>projection(point),
unproject: (point) => projection.invert(point),
stream:projection.stream
},
nameMap:{
'China':'中国'
},
itemStyle:{
color:'#c26e00'
}
}]

​ 至于海洋吗,地球除了陆地就是海洋,将 canvas 画布变成原型,然后添加一个海洋色的背景颜色即可。

​ 添加的时候发现这个绘制的区域怎么比实际的区域小这么多。使用 zoom 放大绘制区域发现当 zoom 等于 1.25 时,绘制区域与画布一样大。1.25,让我想起了半年前做截图功能的时候遇到了 window.devicePixelRatio,我显示器的 dpr 正好就是 1.25 难怪。简单的讲画布展示的像素是绘制的 1.25 倍,所以最后展示的区域便是绘制的 1.25 倍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const devicePixelRatio = window.devicePixelRatio
const option ={
backgroundColor:'#4f42b5',
geo:[{
type: 'map',
map: 'world',
projection: {
project: (point) =>projection(point),
unproject: (point) => projection.invert(point),
stream:projection.stream
},
nameMap:{
'China':'中国'
},
itemStyle:{
color:'#c26e00'
},
zoom:devicePixelRatio
}]
}