As an alternative to the MapPlus solution presented in our previous article 《Creating Dynamic Marathon Route Visualizations with MapPlus: A No-Code GIS Solution》, this guide demonstrates a Cesium-based implementation for dynamic route visualization. Developed with ChatGPT assistance and requiring no commercial subscriptions, this approach achieves approximately 70% of MapPlus functionality.

Data Preparation

Utilizes manually digitized route data from the previous tutorial:

  • Path geometry (line features)
  • Point-of-interest markers

Demo Videos

Implementation Workflow

1. Map Initialization

Ion.defaultAccessToken = 'YOUR_CESIUM_ION_TOKEN';
window.CESIUM_BASE_URL = '/Cesium/';

// Configure default view extent (China)
Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(75.0, 0.0, 140.0, 60.0);

// Initialize viewer
viewerRef.current = new Cesium.Viewer('cesiumContainer', {
  baseLayerPicker: true,
  timeline: true,
  homeButton: false,
  fullscreenButton: false
});

// Camera flight animation
viewerRef.current.camera.flyTo({
  destination: Cesium.Cartesian3.fromDegrees(startLon, startLat, 1000),
  orientation: {
    heading: Cesium.Math.toRadians(270),
    pitch: Cesium.Math.toRadians(-90),
    roll: 0.0
  },
  duration: 6.0
});

2. Path Processing

Convert route coordinates to Cesium positions:

const positions = pathLine.map(item => 
  Cesium.Cartesian3.fromDegrees(item[0], item[1])
);

3. Animation Techniques

Method A: Primitive-based Animation

viewer.clock.onTick.addEventListener((clock) => {
  const time = clock.currentTime;
  const position = positionProperty.getValue(time);
  
  // Update polyline geometry
  animationPositions.push(position);
  if (animationPositions.length > 2) animationPositions.shift();
  
  const polyline = new Cesium.PolylineGeometry({
    positions: animationPositions,
    width: 12
  });
  
  viewer.scene.primitives.add(new Cesium.Primitive({
    geometryInstances: new Cesium.GeometryInstance({ geometry: polyline }),
    appearance: new Cesium.PolylineMaterialAppearance({
      material: Cesium.Material.fromType('PolylineGlow', {
        glowPower: 0.2,
        color: Cesium.Color.BLUE
      })
    })
  }));
});

Method B: Entity-based Animation

viewer.clock.onTick.addEventListener((clock) => {
  const position = positionProperty.getValue(clock.currentTime);
  animationPositions.push(position);
});

4. Marker Placement

Cesium.GeoJsonDataSource.load('/GeoJSON/gg-poi.geojson')
  .then(dataSource => {
    viewer.dataSources.add(dataSource);
    
    dataSource.entities.values.forEach(entity => {
      const name = entity.properties.Name.getValue();
      
      entity.label = {
        text: name,
        font: '28px sans-serif',
        fillColor: Cesium.Color.WHITE,
        pixelOffset: new Cesium.Cartesian2(0, -75)
      };
      
      entity.billboard = new Cesium.BillboardGraphics({
        image: '/assets/image/position.png',
        width: 60,
        height: 60
      });
    });
  });

Alternative implementations and optimization suggestions are welcomed via comments.