From 4092854b0bea9e1fc02f29222c6f7cd876e565a5 Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Mon, 17 Aug 2026 09:43:20 +0800
Subject: [PATCH] feat:项目基础整理

---
 apps/workbench-console/src/components/TrajectoryMap.vue |   93 +++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 86 insertions(+), 7 deletions(-)

diff --git a/apps/workbench-console/src/components/TrajectoryMap.vue b/apps/workbench-console/src/components/TrajectoryMap.vue
index 33c8f78..86c5558 100644
--- a/apps/workbench-console/src/components/TrajectoryMap.vue
+++ b/apps/workbench-console/src/components/TrajectoryMap.vue
@@ -1,6 +1,8 @@
 <script setup lang="ts">
 import { onBeforeUnmount, onMounted, ref, watch } from "vue";
+import { AimOutlined, BorderOutlined } from "@ant-design/icons-vue";
 import {
+  ArcGisMapServerImageryProvider,
   Color,
   ColorMaterialProperty,
   ConstantProperty,
@@ -8,17 +10,21 @@
   GridImageryProvider,
   HeightReference,
   PointGraphics,
+  UrlTemplateImageryProvider,
   Viewer
 } from "cesium";
 
 import { artifactUrl } from "@/api/artifacts";
 
-const props = defineProps<{ caseId: "difficult" | "normal" }>();
+const props = defineProps<{ artifactRoot: string; showSpatialContext: boolean; showFlyableZones: boolean }>();
 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;
+let routeSource: GeoJsonDataSource | null = null;
+let zoneSource: GeoJsonDataSource | null = null;
+let flyableZoneSource: GeoJsonDataSource | null = null;
 
 function eventColor(type: string) {
   if (type === "route_deviation") return Color.fromCssColorString("#9a5da8");
@@ -33,7 +39,15 @@
     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}`;
+    if (routeSource) viewer.dataSources.remove(routeSource, true);
+    if (zoneSource) viewer.dataSources.remove(zoneSource, true);
+    if (flyableZoneSource) viewer.dataSources.remove(flyableZoneSource, true);
+    trajectorySource = null;
+    eventSource = null;
+    routeSource = null;
+    zoneSource = null;
+    flyableZoneSource = null;
+    const root = props.artifactRoot;
     trajectorySource = await GeoJsonDataSource.load(artifactUrl(`${root}/trajectories.geojson`), { clampToGround: false });
     trajectorySource.entities.values.forEach((entity) => {
       if (entity.polyline) {
@@ -52,12 +66,78 @@
         heightReference: new ConstantProperty(HeightReference.NONE)
       });
     });
+    if (props.showSpatialContext) {
+      routeSource = await GeoJsonDataSource.load(artifactUrl(`${root}/reference_routes.geojson`), { clampToGround: false });
+      routeSource.entities.values.forEach((entity) => {
+        if (entity.polyline) {
+          entity.polyline.width = new ConstantProperty(3);
+          entity.polyline.material = new ColorMaterialProperty(Color.fromCssColorString("#444444"));
+        }
+      });
+      zoneSource = await GeoJsonDataSource.load(artifactUrl(`${root}/zones.geojson`), { clampToGround: false });
+      zoneSource.entities.values.forEach((entity) => {
+        if (entity.polygon) {
+          entity.polygon.material = new ColorMaterialProperty(Color.fromCssColorString("#e5c951").withAlpha(0.16));
+          entity.polygon.outline = new ConstantProperty(true);
+          entity.polygon.outlineColor = new ConstantProperty(Color.fromCssColorString("#9a7a00"));
+        }
+      });
+      if (props.showFlyableZones) {
+        flyableZoneSource = await GeoJsonDataSource.load(artifactUrl(`${root}/flyable_zones.geojson`), { clampToGround: false });
+        flyableZoneSource.entities.values.forEach((entity) => {
+          if (entity.polygon) {
+            entity.polygon.material = new ColorMaterialProperty(Color.fromCssColorString("#36a269").withAlpha(0.22));
+            entity.polygon.outline = new ConstantProperty(true);
+            entity.polygon.outlineColor = new ConstantProperty(Color.fromCssColorString("#167344"));
+          }
+        });
+      }
+    }
+    if (zoneSource) await viewer.dataSources.add(zoneSource);
+    if (flyableZoneSource) await viewer.dataSources.add(flyableZoneSource);
+    if (routeSource) await viewer.dataSources.add(routeSource);
     await viewer.dataSources.add(trajectorySource);
     await viewer.dataSources.add(eventSource);
-    await viewer.flyTo(trajectorySource, { duration: 0 });
+    await viewer.flyTo(props.showSpatialContext && zoneSource ? zoneSource : trajectorySource, { duration: 0 });
   } catch (error) {
     mapError.value = error instanceof Error ? error.message : "Cesium 地图加载失败";
   }
+}
+
+async function focusTrajectory() {
+  if (viewer && trajectorySource) await viewer.flyTo(trajectorySource, { duration: 0.4 });
+}
+
+async function focusZones() {
+  if (viewer && zoneSource) await viewer.flyTo(zoneSource, { duration: 0.4 });
+}
+
+async function addBaseLayers() {
+  if (!viewer) return;
+  viewer.imageryLayers.removeAll();
+  try {
+    const arcgis = await ArcGisMapServerImageryProvider.fromUrl(
+      "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer"
+    );
+    viewer.imageryLayers.addImageryProvider(arcgis);
+  } catch {
+    viewer.imageryLayers.addImageryProvider(new GridImageryProvider({ cells: 8, glowWidth: 0 }));
+  }
+  const token = (import.meta.env.VITE_TIANDITU_TOKEN ?? "").trim();
+  if (!token) return;
+  const subdomains = ["0", "1", "2", "3", "4", "5", "6", "7"];
+  viewer.imageryLayers.addImageryProvider(new UrlTemplateImageryProvider({
+    url: `https://t{s}.tianditu.gov.cn/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=${token}`,
+    subdomains,
+    maximumLevel: 18,
+    credit: "Tianditu imagery"
+  }));
+  viewer.imageryLayers.addImageryProvider(new UrlTemplateImageryProvider({
+    url: `https://t{s}.tianditu.gov.cn/DataServer?T=cva_w&x={x}&y={y}&l={z}&tk=${token}`,
+    subdomains,
+    maximumLevel: 18,
+    credit: "Tianditu labels"
+  }));
 }
 
 onMounted(async () => {
@@ -74,15 +154,14 @@
     selectionIndicator: false,
     timeline: false
   });
-  viewer.imageryLayers.removeAll();
-  viewer.imageryLayers.addImageryProvider(new GridImageryProvider({ cells: 8, glowWidth: 0 }));
+  await addBaseLayers();
   await drawCase();
 });
 
-watch(() => props.caseId, drawCase);
+watch(() => [props.artifactRoot, props.showSpatialContext, props.showFlyableZones], 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>
+  <div class="trajectory-map"><div ref="host" class="cesium-host"></div><div v-if="showSpatialContext" class="map-legend"><span><i class="legend-track"></i>实飞轨迹</span><span><i class="legend-route"></i>计划航线</span><span><i class="legend-restricted"></i>禁飞区</span><span v-if="showFlyableZones"><i class="legend-flyable"></i>适飞区</span></div><div v-if="showSpatialContext" class="map-tools"><a-tooltip title="聚焦轨迹"><a-button shape="circle" aria-label="聚焦轨迹" @click="focusTrajectory"><AimOutlined /></a-button></a-tooltip><a-tooltip title="查看禁飞区范围"><a-button shape="circle" aria-label="查看禁飞区范围" @click="focusZones"><BorderOutlined /></a-button></a-tooltip></div><a-alert v-if="mapError" class="map-error" type="warning" show-icon :message="mapError" /></div>
 </template>

--
Gitblit v1.9.3