北京到上海,Three.js旅行轨迹的可视化

兄弟,打扰一下,北京到上海,Three.js旅行轨迹的可视化
最新回答
夏了夏天

2024-09-07 00:33:33

最近从北京搬到了上海,开始了一段新的生活,算是人生中一个比较大的事件,于是特地用Three.js做了下可视化。

在这个地理信息相关的可视化的案例中,我们能学到地图怎么画、经纬度如何转成坐标值,这些是地理可视化的通用技术。

那我们就开始吧。

思路分析

Three.js画立方体、画圆柱、画不规则图形我们都画过,但是如何画一个地图呢?

其实地图也是由线、由多边形构成的,有了数据我们就能画出来,缺少的只是数据。

地图信息的描述是一个通用需求,所以有相应的国际标准,就是GeoJson,它是通过点、线、多边形来描述地理信息的。

通过指定点、线、多边形的类型、然后指定几个坐标位置,就可以描述出相应的形状。

geojson的数据可以通过geojson.io这个网站做下预览。

比如中国地图的geojson:

有了这个json,只要用Three.js画出来就行,通过线和多边形两种方式。

但是还有一个问题,geojson中记录的是经纬度信息,应该如何转成二维坐标来画呢?

这就涉及到了墨卡托转换,它就是做经纬度转二维坐标的事情。

这个转换也不用我们自己实现,可以用d3内置的墨卡托坐标转换函数来做。

这样,我们就用Three.js根据geojson来画出地图。

我们还要画一条北京到上海的曲线,这个用贝塞尔曲线画就行,知道两个端点的坐标,控制点放在中间的位置。

那怎么知道两个端点,也就是上海和北京的坐标呢?

这个可以用“百度坐标拾取系统”这个工具,点击地图的某个位置,就可以直接拿到那个位置的经纬度。然后我们做一次墨卡托转换,就拿到坐标了。

地图画出来了,旅行的曲线也画出来了,接下来调整下相机位置,从北京慢慢移动到上海就可以了。

思路理清了,我们来写下代码。

代码实现

我们要引入d3,然后使用d3的墨卡托转换功能,

constprojection=d3.geoMercator().center([116.412318,39.909843]).translate([0,0]);

中间点的坐标就是北京的经纬度,就是我们通过“百度坐标拾取工具”那里拿到的。

北京和上海的坐标位置也可以把经纬度做墨卡托转换得到:

letbeijingPosition=projection([116.412318,39.909843]);letshanghaiPosition=projection([121.495721,31.236797]);

先不着急画旅行的曲线,先来画地图吧。

先加载geojson:

constloader=newTHREE.FileLoader();loader.load('./data/china.json',(data)=>{constjsondata=JSON.parse(data);generateGeometry(jsondata);})

然后根据json的信息画地图。

遍历geojson的数据,把每个经纬度通过墨卡托转换变成坐标,然后分别用线和多边形画出来。

画多边形的时候遇到北京和上海用黄色,其他城市用蓝色。

functiongenerateGeometry(jsondata){constmap=newTHREE.Group();jsondata.features.forEach((elem)=>{constprovince=newTHREE.Group();//经纬度信息constcoordinates=elem.geometry.coordinates;coordinates.forEach((multiPolygon)=>{multiPolygon.forEach((polygon)=>{//画轮廓线constline=drawBoundary(polygon);//画多边形constprovinceColor=['北京市','上海市'].includes(elem.properties.name)?'yellow':'blue';constmesh=drawExtrudeMesh(polygon,provinceColor);province.add(line);province.add(mesh);});});map.add(province);})scene.add(map);}

然后分别实现画轮廓线和画多边形:

轮廓线(Line)就是指定一系列顶点来构成几何体(Geometry),然后指定材质(Material)颜色为黄色:

functiondrawBoundary(polygon){constlineGeometry=newTHREE.Geometry();for(leti=0;i<polygon.length;i++){const[x,y]=projection(polygon[i]);lineGeometry.vertices.push(newTHREE.Vector3(x,-y,0));}constlineMaterial=newTHREE.LineBasicMaterial({color:'yellow'});returnnewTHREE.Line(lineGeometry,lineMaterial);}

现在的效果是这样的:

多边形是ExtrudeGeometry,也就是可以先画出形状(shape),然后通过拉伸变成三维的。

functiondrawExtrudeMesh(polygon,color){constshape=newTHREE.Shape();for(leti=0;i<polygon.length;i++){const[x,y]=projection(polygon[i]);if(i===0){shape.moveTo(x,-y);}shape.lineTo(x,-y);}constgeometry=newTHREE.ExtrudeGeometry(shape,{depth:0,bevelEnabled:false});constmaterial=newTHREE.MeshBasicMaterial({color,transparent:true,opacity:0.2,})returnnewTHREE.Mesh(geometry,material);}

第一个点用moveTo,后面的点用lineTo,这样连成一个多边形,然后指定厚度为0,指定侧面不需要多出一块斜面(bevel)。

这样,我们就给每个省都填充上了颜色,北京和上海是黄色,其余省是蓝色。

接下来,在北京和上海之间画一条贝塞尔曲线:

constline=drawLine(beijingPosition,shanghaiPosition);scene.add(line);

贝塞尔曲线用QuadraticBezierCurve3来画,控制点指定中间位置的点。

functiondrawLine(pos1,pos2){const[x0,y0,z0]=[...pos1,0];const[x1,y1,z1]=[...pos2,0];constgeomentry=newTHREE.Geometry();geomentry.vertices=newTHREE.QuadraticBezierCurve3(newTHREE.Vector3(-x0,-y0,z0),newTHREE.Vector3(-(x0+x1)/2,-(y0+y1)/2,-10),newTHREE.Vector3(-x1,-y1,z1),).getPoints();constmaterial=newTHREE.LineBasicMaterial({color:'white'});constline=newTHREE.Line(geomentry,material);line.rotation.y=Math.PI;returnline;}

这样,地图和旅行轨迹就都画完了:

当然,还有渲染器、相机、灯光的初始化代码:

渲染器:

constrenderer=newTHREE.WebGLRenderer();renderer.setClearColor(0x000000);renderer.setSize(window.innerWidth,window.innerHeight);document.body.appendChild(renderer.domElement);

渲染器设置背景颜色为黑色,画布大小为窗口大小。

灯光:

letambientLight=newTHREE.AmbientLight(0xffffff);scene.add(ambientLight);

灯光用环境光,也就是每个方向的明暗都一样。

相机:

letbeijingPosition=projection([116.412318,39.909843]);letshanghaiPosition=projection([121.495721,31.236797]);0

相机用透视相机,特点是近大远小,需要指定看的角度,宽高比,和远近的范围这样四个参数。

位置设置在0010的位置,在这个位置去观察000,就是北京上方的俯视图(我们做墨卡托转换的时候指定了北京为中心)。

修改了相机位置之后,看到的地图大了许多:

接下来就是一帧帧的渲染,在每帧渲染的时候移动下相机位置,这样就是从北京到上海的一个移动的效果:

letbeijingPosition=projection([116.412318,39.909843]);letshanghaiPosition=projection([121.495721,31.236797]);1

大功告成!我们来看下最终的效果吧:

代码上传到了github:https://github.com/QuarkGluonPlasma/threejs-exercize

也在这里贴一份:

letbeijingPosition=projection([116.412318,39.909843]);letshanghaiPosition=projection([121.495721,31.236797]);2

总结

地图形状的表示是基于geojson的规范,它是由点、线、多边形等信息构成的。

用Three.js或者其他绘制方式来画地图只需要加载geojson的数据,然后通过线和多边型把每一部分画出来。

画之前还要把经纬度转成坐标,这需要用到墨卡托转换。

我们用Three.js画线是通过指定一系列顶点构成Geometry,而画多边形是通过绘制一个形状,然后用ExtrudeGeometry(挤压几何体)拉伸成三维。墨卡托转换直接使用了d3的内置函数。旅行的效果是通过一帧帧的移动相机位置来实现的。

熟悉了geojson和墨卡托转换,就算是入门地理相关的可视化了。

你是否也想做一些和地理相关的可视化或者交互呢?不妨来尝试下吧。

来源:公众号「神光的编程秘籍」