<script setup lang="ts">
|
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
|
import {
|
Color,
|
ColorMaterialProperty,
|
ConstantProperty,
|
GeoJsonDataSource,
|
GridImageryProvider,
|
HeightReference,
|
PointGraphics,
|
Viewer
|
} from "cesium";
|
|
import { artifactUrl } from "@/api/artifacts";
|
|
const props = defineProps<{ caseId: "difficult" | "normal" }>();
|
const host = ref<HTMLElement>();
|
const mapError = ref<string | null>(null);
|
let viewer: Viewer | null = null;
|
let trajectorySource: GeoJsonDataSource | null = null;
|
let eventSource: GeoJsonDataSource | null = null;
|
|
function eventColor(type: string) {
|
if (type === "route_deviation") return Color.fromCssColorString("#9a5da8");
|
if (type === "restricted_zone") return Color.fromCssColorString("#bc861a");
|
if (type === "gathering") return Color.fromCssColorString("#176b50");
|
return Color.fromCssColorString("#c6533f");
|
}
|
|
async function drawCase() {
|
if (!viewer) return;
|
try {
|
mapError.value = null;
|
if (trajectorySource) viewer.dataSources.remove(trajectorySource, true);
|
if (eventSource) viewer.dataSources.remove(eventSource, true);
|
const root = `shared/outputs/15-trajectory-analysis/${props.caseId}`;
|
trajectorySource = await GeoJsonDataSource.load(artifactUrl(`${root}/trajectories.geojson`), { clampToGround: false });
|
trajectorySource.entities.values.forEach((entity) => {
|
if (entity.polyline) {
|
entity.polyline.width = new ConstantProperty(4);
|
entity.polyline.material = new ColorMaterialProperty(Color.fromCssColorString("#176b50"));
|
}
|
});
|
eventSource = await GeoJsonDataSource.load(artifactUrl(`${root}/events.geojson`), { clampToGround: false });
|
eventSource.entities.values.forEach((entity) => {
|
const type = String(entity.properties?.event_type?.getValue() ?? "stop");
|
entity.point = new PointGraphics({
|
pixelSize: new ConstantProperty(12),
|
color: new ConstantProperty(eventColor(type)),
|
outlineColor: new ConstantProperty(Color.WHITE),
|
outlineWidth: new ConstantProperty(2),
|
heightReference: new ConstantProperty(HeightReference.NONE)
|
});
|
});
|
await viewer.dataSources.add(trajectorySource);
|
await viewer.dataSources.add(eventSource);
|
await viewer.flyTo(trajectorySource, { duration: 0 });
|
} catch (error) {
|
mapError.value = error instanceof Error ? error.message : "Cesium 地图加载失败";
|
}
|
}
|
|
onMounted(async () => {
|
if (!host.value) return;
|
viewer = new Viewer(host.value, {
|
animation: false,
|
baseLayerPicker: false,
|
fullscreenButton: false,
|
geocoder: false,
|
homeButton: false,
|
infoBox: false,
|
navigationHelpButton: false,
|
sceneModePicker: false,
|
selectionIndicator: false,
|
timeline: false
|
});
|
viewer.imageryLayers.removeAll();
|
viewer.imageryLayers.addImageryProvider(new GridImageryProvider({ cells: 8, glowWidth: 0 }));
|
await drawCase();
|
});
|
|
watch(() => props.caseId, drawCase);
|
onBeforeUnmount(() => viewer?.destroy());
|
</script>
|
|
<template>
|
<div class="trajectory-map"><div ref="host" class="cesium-host"></div><a-alert v-if="mapError" class="map-error" type="warning" show-icon :message="mapError" /></div>
|
</template>
|