「GIS教程」使用Cesium实现马拉松动态路线图(附源码)
在之前的文章《「GIS教程」如何制作一张马拉松动态路线图/视频(无需编程及专业GIS工具)》中小编介绍了使用地图迦(https://www.mapplus.com)软件制作马拉松动态路线图的方法,那个方法简单方便,还有不少国产精美底图可以选。不过地图迦免费版本有不少限制,要想达到更好的效果需要开会员。
可能也有也不少同学不想开会员,并且也想理解下底层是如何实现的,最近有点时间,小编就尝试使用Cesium来实现了一个版本,大约能有70%的功能。由于小编之前对Cesium并不熟悉,所以本次的实现也借助了ChatGPT的能力,如果代码写的不对,也欢迎大家指正哈。
数据准备
数据还是使用之前文章《「GIS教程」如何制作一张马拉松动态路线图/视频(无需编程及专业GIS工具)》中小编手动矢量化的数据,将数据导出了两个文件,一个是用于路径的路径数据,一个是用于标记点的数据。
效果演示
目前已经上传到了B站和Youtube,欢迎大家关注!跟之前地图迦实现的效果基本差不多~
编码实现
小编根据ChatGPT的提示,从头一步步写的,使用的是NextJS框架(基于React)。
主要流程大约可以分为以下四步:
1.初始化地图
这个没什么,是使用Cesium的最基础的东西了,小编使用了Cesium的官方底图,所以这里要注册一个Cesium ion帐号,获取一个token,具体的方法参考《快速搭建一个Cesium应用示例》
简要代码如下:
Ion.defaultAccessToken = '你申请的Token';
window.CESIUM_BASE_URL = '/Cesium/';
// 修改默认位置
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(
75.0, // 西经
0.0, // 南纬
140.0, // 东经
60.0 // 北纬
);
viewerRef.current = new Cesium.Viewer('cesiumContainer', {
baseLayerPicker: true,
timeline: true,// 必须为true显示时间线组件(如不想显示可以使用样式层叠表修改display:none) 否则viewer.timeline.zoomTo会报undefined错误
homeButton: false,
fullscreenButton: false,
infoBox: false,
sceneModePicker: false,
navigationInstructionsInitiallyVisible: false,
navigationHelpButton: false,
animation: false,
shouldAnimate: true
});
setTimeout(() => {
viewerRef.current.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(pathLine[0][0], pathLine[0][1], pathLine[0][2] + 1000), // 高度为 1000 米 positions[0],
orientation: {
heading: Cesium.Math.toRadians(270), // 方位角,以正北为0度,顺时针旋转
pitch: Cesium.Math.toRadians(-90), // 俯仰角,向下为正
roll: 0.0 // 翻滚角
},
duration: 6.0, // 飞行持续时间,单位为秒
complete: function () {
console.log("飞行完成");
// 飞行动画完成,做正常操作
// ...
},
cancel: function () {
// 飞行取消时执行的回调函数
console.log("飞行取消");
},
easingFunction: Cesium.EasingFunction.QUADRATIC_OUT // 缓动函数,控制飞行速度变化
})
}, 1000)
2.添加路径、插值、动起来
第二步需要添加一下路径数据,小编把所以的路径手动处理了一下,变成了一个JS数组文件。
数据导入之后,将其转换成Cesium支持的坐标点数组。
const positions = pathLine.map(item => Cesium.Cartesian3.fromDegrees(item[0], item[1]))
手动添加的路径数据,每两个点之间的距离是不同的,这就导致如果直接用这个数组做动画,就会出现一会快速,一会慢速的效果,所以需要插值,插值之后,才能实现动态效果。插值的代码如下:
for (let i = 0; i < positions.length; i++) {
let time = Cesium.JulianDate.addSeconds(startTime, timeArr[i], new Cesium.JulianDate);
let position = positions[i];
// 添加位置,和时间对应
positionProperty.addSample(time, position);
}
这里使用的是 Cesium.SampledPositionProperty
实现的插值,详细的timeArr
的生成可以参考小编后面详细源代码。
3.动起来
这是最精髓的一步了,小编找到了两种方法: 一种是 scene.primitives.add
重复调用,另外一种是 entities + CallbackProperty
,在Callback中动态修改entity的position属性。核心代码:
//scene.primitives.add
viewer.clock.onTick.addEventListener((clock) => {
const time = clock.currentTime;
const position = positionProperty.getValue(time);
if (!position) {
return
}
// 画线
animationPositions.push(position)
if (animationPositions.length < 2) return
if (animationPositions.length > 2) {
animationPositions.shift();
}
const polyline = new Cesium.PolylineGeometry({
positions: animationPositions,
width: 12, // 线宽
});
// 创建实例
const polylineInstance = new Cesium.GeometryInstance({
geometry: polyline,
});
viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: polylineInstance,
appearance: new Cesium.PolylineMaterialAppearance({
material: Cesium.Material.fromType('PolylineGlow', {
glowPower: 0.2,
color: Cesium.Color.BLUE,
}),
}),
}));
});
//entities + CallbackProperty
viewer.clock.onTick.addEventListener((clock) => {
const time = clock.currentTime;
const position = positionProperty.getValue(time);
if (!position) {
return
}
// 画线
animationPositions.push(position)
});
4.添加标记点
解决了线生长的动画之后,最后添加下标记点就可以了。这里直接从GeoJSON中读取并渲染即可。
示例代码:
const drawPoints = () => {
// 加载 GeoJSON 数据
Cesium.GeoJsonDataSource.load('/GeoJSON/gg-poi.geojson', {
clampToGround: true
}).then(function (dataSource) {
viewerRef.current.dataSources.add(dataSource);
dataSourceRef.current = dataSource;
// 获取所有实体
const entities = dataSource.entities.values;
for (let i = 0; i < entities.length; i++) {
const entity = entities[i];
// 获取属性
const name = entity.properties.Name.getValue();
// 创建标签
entity.label = {
text: name, // 显示名称
font: '28px sans-serif',
style: Cesium.LabelStyle.FILL,
fillColor: Cesium.Color.WHITE,
pixelOffset: new Cesium.Cartesian2(0, -75), //偏移量
showBackground: true,
backgroundColor: new Cesium.Color(0.5, 0.6, 1, 1.0)
};
entity.billboard = new Cesium.BillboardGraphics({
image: '/assets/image/position.png', // 替换为你的图标路径
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
width: 60,
height: 60,
});
}
}).catch(function (error) {
console.error(error);
});
}
源码下载
目前小编把上面的两种方法都整理了出来,放在一个源码包里,需要详细全部源码的小伙伴可以下载。
关注本站微信公众号,回复“资源下载”,获取验证码。
在微信里搜索“麻辣GIS”或微信扫描右侧二维码即可关注本站微信公众号。
后话
如果有其他更好的实现方法,欢迎大家留言讨论哈。
相关阅读
声明
1.本文所分享的所有需要用户下载使用的内容(包括但不限于软件、数据、图片)来自于网络或者麻辣GIS粉丝自行分享,版权归该下载资源的合法拥有者所有,如有侵权请第一时间联系本站删除。
2.下载内容仅限个人学习使用,请切勿用作商用等其他用途,否则后果自负。