feat(tgu-ops-ui): display plan works and new shoots on editor map
На вкладке Редактор 2D-карта теперь показывает включения выбранного плана
и новые съёмки, добавленные в текущей сессии.
- editorApi: типы PlanSurveyMode/PlanDropMode/PlanMode и fetchPlanModes —
вызывает GET /api/current-plans/missions/{planId}/modes на pcp-mission-planing-service,
где planId TGU совпадает с missionId (выяснено по Camunda-процессу)
- TguEditorTab: загружает включения при выборе плана, строит MapPolygon[]
из SURVEY-режимов через parseWktPolygon(contourWkt), включён слой planWorks
- Tgu2DMapView: разделены pending-маркер (оранжевый, выбор точки)
и confirmed-маркеры (бирюзовые, подтверждённые съёмки из драфта);
клик на маркер съёмки открывает WorkInspector
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,7 @@ import { Tgu2DMapView } from "../tgu-map-2d/Tgu2DMapView";
|
||||
import { parseWktPolygon } from "../tgu-map-2d/geometry/wktParser";
|
||||
import type { Tgu2DMapLayersState } from "../tgu-map-2d/model/mapLayerTypes";
|
||||
import type { MapPolygon, Tgu2DMapScene } from "../tgu-map-2d/model/mapTypes";
|
||||
import { saveEditedPlan } from "./editorApi";
|
||||
import { fetchPlanModes, saveEditedPlan, type PlanMode } from "./editorApi";
|
||||
import {
|
||||
addDownlink,
|
||||
addShoot,
|
||||
@@ -28,6 +28,7 @@ import type {
|
||||
DraftHistory,
|
||||
EditorGroundStation,
|
||||
EditorMode,
|
||||
EditorShootWork,
|
||||
EditorTarget,
|
||||
EditorTimelineWindow,
|
||||
EditorVisibilityTrack,
|
||||
@@ -42,7 +43,7 @@ const HOUR_MS = 60 * 60 * 1000;
|
||||
const EDITOR_MAP_LAYERS: Tgu2DMapLayersState = {
|
||||
tracks: true,
|
||||
swath: false,
|
||||
planWorks: false,
|
||||
planWorks: true,
|
||||
stations: true,
|
||||
spacecraftMarkers: false,
|
||||
requests: true
|
||||
@@ -72,6 +73,7 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
const [applyError, setApplyError] = useState<string>();
|
||||
const [contextSpacecraftIds, setContextSpacecraftIds] = useState<Set<string>>(new Set());
|
||||
const [requestItems, setRequestItems] = useState<RequestMapItem[]>([]);
|
||||
const [planModes, setPlanModes] = useState<PlanMode[]>([]);
|
||||
const draft = history.draft;
|
||||
const stations = useMemo(() => buildStations(selectedPlan), [selectedPlan]);
|
||||
const modes: EditorMode[] = DEFAULT_EDITOR_MODES;
|
||||
@@ -92,8 +94,15 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
[selectedSpacecraftId, selectedWork, shootPasses, stations, target, window]
|
||||
);
|
||||
const mapScene = useMemo(
|
||||
() => buildEditorMapScene(selectedSpacecraftId, window, stations, requestItems),
|
||||
[selectedSpacecraftId, stations, window, requestItems]
|
||||
() => buildEditorMapScene(selectedSpacecraftId, window, stations, requestItems, planModes),
|
||||
[selectedSpacecraftId, stations, window, requestItems, planModes]
|
||||
);
|
||||
const shootTargets = useMemo(
|
||||
() =>
|
||||
draft.works
|
||||
.filter((w): w is EditorShootWork => w.kind === "shoot" && w.targetLat !== undefined && w.targetLon !== undefined)
|
||||
.map((w) => ({ id: w.id, name: w.label, lat: w.targetLat!, lon: w.targetLon! })),
|
||||
[draft.works]
|
||||
);
|
||||
const contextLanes = useMemo(
|
||||
() =>
|
||||
@@ -118,6 +127,7 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
setServiceConflicts({});
|
||||
setNotice(undefined);
|
||||
setApplyError(undefined);
|
||||
setPlanModes([]);
|
||||
}, [appliedRange, selectedPlan, selectedSpacecraftId]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -135,6 +145,18 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
return () => controller.abort();
|
||||
}, [window.fromMs, window.toMs]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedPlan) return;
|
||||
const planId = selectedPlan.planId;
|
||||
let cancelled = false;
|
||||
void fetchPlanModes(planId).then((modes) => {
|
||||
if (!cancelled) setPlanModes(modes);
|
||||
}).catch(() => {
|
||||
// silently ignore — plan modes are supplementary display data
|
||||
});
|
||||
return () => { cancelled = true; };
|
||||
}, [selectedPlan]);
|
||||
|
||||
const changeDraft = (transform: (draft: DraftHistory["draft"]) => DraftHistory["draft"]) => {
|
||||
setHistory((current) => mutate(current, transform));
|
||||
setServiceConflicts({});
|
||||
@@ -330,7 +352,14 @@ export function TguEditorTab({ selectedSpacecraftId, selectedPlan, appliedRange,
|
||||
layers={EDITOR_MAP_LAYERS}
|
||||
redrawToken={0}
|
||||
pickMode={rightMode === "shoot"}
|
||||
targets={target ? [target] : []}
|
||||
targets={shootTargets}
|
||||
pickTarget={target ?? undefined}
|
||||
selectedTargetId={selectedWork?.kind === "shoot" ? selectedWorkId : undefined}
|
||||
onTargetSelect={(id) => {
|
||||
setSelectedWorkId(id);
|
||||
setRightMode("inspect");
|
||||
setNotice(undefined);
|
||||
}}
|
||||
onPickPoint={(point) => {
|
||||
targetCounterRef.current += 1;
|
||||
setTarget({
|
||||
@@ -527,7 +556,8 @@ function buildEditorMapScene(
|
||||
spacecraftId: string | undefined,
|
||||
window: EditorTimelineWindow,
|
||||
stations: EditorGroundStation[],
|
||||
requestItems: RequestMapItem[]
|
||||
requestItems: RequestMapItem[],
|
||||
planModes: PlanMode[]
|
||||
): Tgu2DMapScene {
|
||||
const center = (window.fromMs + window.toMs) / 2;
|
||||
const mapRange = { fromMs: center - MAP_TRACK_WINDOW_MS / 2, toMs: center + MAP_TRACK_WINDOW_MS / 2 };
|
||||
@@ -557,6 +587,24 @@ function buildEditorMapScene(
|
||||
|
||||
const polygons: MapPolygon[] = [];
|
||||
let zIndex = 0;
|
||||
|
||||
for (const mode of planModes) {
|
||||
if (mode.type !== "SURVEY" || !mode.contourWkt) continue;
|
||||
const rings = parseWktPolygon(mode.contourWkt);
|
||||
for (const ring of rings) {
|
||||
if (ring.length < 3) continue;
|
||||
polygons.push({
|
||||
id: `planWork:${mode.id}:${zIndex}`,
|
||||
name: `Съёмка витка ${mode.revolution}`,
|
||||
kind: "planWork",
|
||||
layer: "planWorks",
|
||||
points: ring,
|
||||
status: "PLANNED",
|
||||
zIndex: zIndex++
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const item of requestItems) {
|
||||
const rings = parseWktPolygon(item.geometry);
|
||||
for (const ring of rings) {
|
||||
|
||||
Reference in New Issue
Block a user