feat(D 예정노선): 올린 임도망 묶음 쌓기 · 목록(고르기 · 지움) · 지도 밝은 분홍 + 검은 테 (PLAN 605-1 · 605-2 · 요구 476-1)
- 서버 — 올릴 때마다 묶음 하나(`D03_PlanAnalysis/임도망도/<번호>`) · 옛 한 파일 꼴 = 묶음 legacy · 합친 값 + 묶음 줄 · 목록 GET · 묶음 DELETE · 20 묶음까지 - 화면 — 위치 칸 목록(묶음 줄 · 펼치면 선 줄 · 고름 = 지도 강조 + 맞춤 · 지움 = 물음 뒤) · 색 주황 → 밝은 분홍(`--draw-map-forest-road-file`) - 시험 584_3 — 묶음 쌓기 · 지움 · legacy · 20 묶음 · 화면 정의 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,7 @@ import {
|
||||
fetchPlanSoils,
|
||||
fetchPlanZeroOrder,
|
||||
fetchPlanZones,
|
||||
deletePlanForestRoad,
|
||||
uploadPlanForestRoads,
|
||||
type PlanForestRoads,
|
||||
} from "./D03_PlanAnalysis_Api";
|
||||
@@ -69,6 +70,7 @@ import {
|
||||
readPref,
|
||||
shadeImage,
|
||||
type PlanLayerKey,
|
||||
type RoadPick,
|
||||
} from "./D03_PlanAnalysis_UI_LayerDefs";
|
||||
import { layerCounts, legendRows, type Prepared } from "./D03_PlanAnalysis_UI_LayerLegend";
|
||||
import { createCanvasLayers } from "./D03_PlanAnalysis_UI_LayerPaint";
|
||||
@@ -110,6 +112,10 @@ export interface PlanLayers {
|
||||
load: () => Promise<void>;
|
||||
/** 임도망도 올리기(왼쪽 「위치」 칸) — 받으면 그 겹을 켬 · 못 받으면 알림 + null. */
|
||||
uploadRoads: (files: File[]) => Promise<PlanForestRoads | null>;
|
||||
/** 올린 묶음 하나 지움(605-1) — 지도에서 바로 빠짐 · 못 지우면 알림 + 거짓. */
|
||||
deleteRoad: (bundle: string) => Promise<boolean>;
|
||||
/** 올린 묶음 · 선 고름(605-2) — 지도 강조(꺼 있던 겹은 켬) · null = 강조 없앰. */
|
||||
pickRoad: (pick: RoadPick | null) => void;
|
||||
/** 검증 창구 — 켬끔 · 받은 겹 · 겹별 수(지물 · 넓이 m²). */
|
||||
state: () => {
|
||||
on: Record<PlanLayerKey, boolean>;
|
||||
@@ -294,11 +300,7 @@ export function createPlanLayers(opts: {
|
||||
const at = meta;
|
||||
const job = uploadPlanForestRoads(projectId, files).then((info) => {
|
||||
if (meta !== at) return info;
|
||||
if (at)
|
||||
data.forestRoadFile = { layer: prepareLayer(info.collection, createNormalizer(at)), info };
|
||||
gl.setData("forestRoadFile", info.collection);
|
||||
on.forestRoadFile = true;
|
||||
writeByKey(PREF_KEY, JSON.stringify(on));
|
||||
applyRoads(info);
|
||||
showToast(
|
||||
`임도망도 받음 — 범위 안 ${info.count}가닥 ${(info.length_m / 1000).toFixed(2)} km (파일 ${info.total}가닥)`,
|
||||
"success",
|
||||
@@ -328,6 +330,80 @@ export function createPlanLayers(opts: {
|
||||
}
|
||||
}
|
||||
|
||||
/** 올린 임도망 자료를 바꿔 그림(올림 · 지움) — 켜 둠 · null = 하나도 안 남음(끔). */
|
||||
function applyRoads(info: PlanForestRoads | null): void {
|
||||
if (info) {
|
||||
if (meta)
|
||||
data.forestRoadFile = {
|
||||
layer: prepareLayer(info.collection, createNormalizer(meta)),
|
||||
info,
|
||||
};
|
||||
gl.setData("forestRoadFile", info.collection);
|
||||
} else {
|
||||
delete data.forestRoadFile;
|
||||
gl.setData("forestRoadFile", null);
|
||||
roadPick = null;
|
||||
gl.setRoadPick(null);
|
||||
}
|
||||
on.forestRoadFile = !!info;
|
||||
writeByKey(PREF_KEY, JSON.stringify(on));
|
||||
}
|
||||
|
||||
async function deleteRoad(bundle: string): Promise<boolean> {
|
||||
try {
|
||||
const info = await deletePlanForestRoad(projectId, bundle);
|
||||
if (roadPick?.bundle === bundle) pickRoad(null);
|
||||
applyRoads(info);
|
||||
showToast(
|
||||
info
|
||||
? `임도망도 묶음 지움 — 남은 ${info.bundles.length}묶음`
|
||||
: "임도망도 묶음 지움 — 남은 것 없음",
|
||||
"success",
|
||||
);
|
||||
return true;
|
||||
} catch (error) {
|
||||
showToast(
|
||||
`임도망도 묶음을 지우지 못함 — ${error instanceof Error ? error.message : String(error)}`,
|
||||
"error",
|
||||
);
|
||||
return false;
|
||||
} finally {
|
||||
sync();
|
||||
map.redrawBase();
|
||||
}
|
||||
}
|
||||
|
||||
/** 고른 묶음 · 선만 담은 캔버스 자료(옛 그림용) — 자료 · 고름이 같으면 그대로. */
|
||||
let roadPick: RoadPick | null = null;
|
||||
let pickMemo: { from: unknown; pick: RoadPick; layer: PreparedLayer } | null = null;
|
||||
function pickLayer(): PreparedLayer | null {
|
||||
const got = data.forestRoadFile;
|
||||
if (!roadPick || !got || !meta) return null;
|
||||
if (pickMemo?.from !== got || pickMemo.pick !== roadPick) {
|
||||
const mine = roadPick;
|
||||
const features = (got.info.collection.features ?? []).filter((f) => {
|
||||
const props = f.properties ?? {};
|
||||
return (
|
||||
props["__bundle"] === mine.bundle &&
|
||||
(mine.line === undefined || props["__line"] === mine.line)
|
||||
);
|
||||
});
|
||||
pickMemo = {
|
||||
from: got,
|
||||
pick: mine,
|
||||
layer: prepareLayer({ ...got.info.collection, features }, createNormalizer(meta)),
|
||||
};
|
||||
}
|
||||
return pickMemo.layer;
|
||||
}
|
||||
|
||||
function pickRoad(pick: RoadPick | null): void {
|
||||
roadPick = pick;
|
||||
gl.setRoadPick(pick);
|
||||
if (pick && !on.forestRoadFile) toggle("forestRoadFile");
|
||||
else map.redrawBase();
|
||||
}
|
||||
|
||||
/** 받은 범위 지문 — 등고 · 세류 주소에 붙여 브라우저 보관함이 옛 범위를 안 줌. */
|
||||
const haveTag = (): string => (opts.have() ?? []).map((v) => Math.round(v)).join("_");
|
||||
const sheetLayer = (layer: string, extra = ""): Promise<GeoJsonCollection> =>
|
||||
@@ -538,6 +614,7 @@ export function createPlanLayers(opts: {
|
||||
on,
|
||||
data,
|
||||
meta: () => meta,
|
||||
roadPick: pickLayer,
|
||||
});
|
||||
|
||||
/** GL 이면 캔버스 바탕 겹은 쉼 — 등고 주곡선 간격(화면 안 가닥 수)만 셈. */
|
||||
@@ -581,6 +658,8 @@ export function createPlanLayers(opts: {
|
||||
layers,
|
||||
load,
|
||||
uploadRoads,
|
||||
deleteRoad,
|
||||
pickRoad,
|
||||
state: () => ({
|
||||
on: { ...on },
|
||||
loaded: LAYERS.map((def) => def.key).filter(loadedOf),
|
||||
|
||||
Reference in New Issue
Block a user