麻辣GIS微信平台

更多 GIS 干货

微信关注不错过

「GIS教程」使用Cesium实现马拉松动态路线图(附源码)

在之前的文章《「GIS教程」如何制作一张马拉松动态路线图/视频(无需编程及专业GIS工具)》中小编介绍了使用地图迦(https://www.mapplus.com)软件制作马拉松动态路线图的方法,那个方法简单方便,还有不少国产精美底图可以选。不过地图迦免费版本有不少限制,要想达到更好的效果需要开会员。

可能也有也不少同学不想开会员,并且也想理解下底层是如何实现的,最近有点时间,小编就尝试使用Cesium来实现了一个版本,大约能有70%的功能。由于小编之前对Cesium并不熟悉,所以本次的实现也借助了ChatGPT的能力,如果代码写的不对,也欢迎大家指正哈。

数据准备

数据还是使用之前文章《「GIS教程」如何制作一张马拉松动态路线图/视频(无需编程及专业GIS工具)》中小编手动矢量化的数据,将数据导出了两个文件,一个是用于路径的路径数据,一个是用于标记点的数据。

效果演示

B站观看

Youtube观看

目前已经上传到了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为了防止资源恶意爬取导致被和谐,此处内容被作者隐藏。
验证码:

关注本站微信公众号,回复“资源下载”,获取验证码。

在微信里搜索“麻辣GIS”或微信扫描右侧二维码即可关注本站微信公众号。

后话

如果有其他更好的实现方法,欢迎大家留言讨论哈。

相关阅读

麻辣GIS-Sailor

作者:

GIS爱好者,学GIS,更爱玩GIS。

声明

1.本文所分享的所有需要用户下载使用的内容(包括但不限于软件、数据、图片)来自于网络或者麻辣GIS粉丝自行分享,版权归该下载资源的合法拥有者所有,如有侵权请第一时间联系本站删除。

2.下载内容仅限个人学习使用,请切勿用作商用等其他用途,否则后果自负。

手机阅读
公众号关注
知识星球
手机阅读
麻辣GIS微信公众号关注
最新GIS干货
关注麻辣GIS知识星球
私享圈子
没有下文

留言板(小编看到第一时间回复)