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
- Bilibili: https://www.bilibili.com/video/BV1vKCGYzEnR/
- YouTube: https://www.youtube.com/watch?v=UpZXVVf2wec
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.