- D03_Api 겹 받기 → map_layer_api.ts(D03_Api 가 다시 내놓음) - UI_SlopeBands · logic SlopeBands · UI_SatLight → map_slope_bands · logic/map_slope_bands · map_sat_light - 범례 줄 legendRows → map_legend.ts(받은 자료 꼴 LegendData · D03 LayerLegend 가 다시 내놓음) - glImage · glTagged · shadeImage · loadImage · WATER_KEYS · FOREST_ROAD_KINDS · hectare → map_layer_data.ts - 시험 경로 고침 — test_603(TS 자리) · test_606_1(밝기 파일) · test_584_3(범례 글 = 공용 파일도 읽음) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
99 lines
4.3 KiB
TypeScript
99 lines
4.3 KiB
TypeScript
/* =============================================================================
|
||
* map_slope_bands.ts(logic)
|
||
* 공용 지도 급경사 범위(624-3 공용으로 옮김)(PLAN 603 · 요구 466 · 467 · 468) — 기울기 % 범위 · 색 · 비율 셈(화면 · 서버 없이).
|
||
* 서버 값 그림 칸 = 기울기 % 내림(0~250 · 250 = 250 % 이상 · `D03_PlanAnalysis_Layers.slope_values_png`) ·
|
||
* 칸 v 는 가운데(v + 0.5)가 [아래, 위) 안이면 그 범위(위 = 250 이면 250 % 이상도) · 앞 범위 먼저.
|
||
* 기본 = 지식DB 15 · 35 · 40° 의 % — `임도망_편성.md:20` · `타당성평가.md:59` · `노선_배치.md:29`.
|
||
* 부르는 곳 = `map_slope_bands.ts` · `map_legend.ts` · D03 `UI_LayerLegend`.
|
||
* ========================================================================== */
|
||
|
||
export interface SlopeBand {
|
||
/** 아래 % (포함) */
|
||
low: number;
|
||
/** 위 % (뺌 · 250 = 끝까지) */
|
||
high: number;
|
||
/** 그림 색 이름(`--draw-map-*`) — `SLOPE_COLORS` 중 하나 */
|
||
color: string;
|
||
}
|
||
|
||
export const SLOPE_MAX_PCT = 250;
|
||
export const SLOPE_EMPTY = 255;
|
||
export const SLOPE_MAX_BANDS = 8;
|
||
|
||
/** 고를 색(이름 · 그림 색 값). */
|
||
export const SLOPE_COLORS: Array<{ name: string; color: string }> = [
|
||
{ name: "노랑", color: "--draw-map-landslide-3" },
|
||
{ name: "주황", color: "--draw-map-landslide-2" },
|
||
{ name: "빨강", color: "--draw-map-landslide-1" },
|
||
{ name: "연두", color: "--draw-map-landslide-4" },
|
||
{ name: "초록", color: "--draw-map-landslide-5" },
|
||
{ name: "하늘", color: "--draw-map-stream" },
|
||
{ name: "파랑", color: "--draw-map-zone-water" },
|
||
{ name: "보라", color: "--draw-map-zone-heritage" },
|
||
{ name: "분홍", color: "--draw-map-parcel" },
|
||
];
|
||
|
||
export const DEFAULT_SLOPE_BANDS: SlopeBand[] = [
|
||
{ low: 26.8, high: 70.0, color: "--draw-map-landslide-3" },
|
||
{ low: 70.0, high: 83.9, color: "--draw-map-landslide-2" },
|
||
{ low: 83.9, high: SLOPE_MAX_PCT, color: "--draw-map-landslide-1" },
|
||
];
|
||
|
||
export const pctToDeg = (pct: number): number => (Math.atan(pct / 100) * 180) / Math.PI;
|
||
|
||
/** 범위 고칠 거리 — 없으면 null(빈 값 · 0~250 밖 · 아래 ≥ 위 · 겹침 · 없음 · 너무 많음). */
|
||
export function checkBands(bands: SlopeBand[]): string | null {
|
||
if (!bands.length) return "범위가 하나는 있어야 합니다";
|
||
if (bands.length > SLOPE_MAX_BANDS) return `범위는 ${SLOPE_MAX_BANDS}개까지입니다`;
|
||
for (const [i, band] of bands.entries()) {
|
||
const at = `${i + 1}번째 범위`;
|
||
if (!Number.isFinite(band.low) || !Number.isFinite(band.high)) return `${at} — 값이 비었습니다`;
|
||
if (band.low < 0 || band.high > SLOPE_MAX_PCT) return `${at} — 0~${SLOPE_MAX_PCT} % 안으로`;
|
||
if (band.low >= band.high) return `${at} — 아래가 위보다 작아야 합니다`;
|
||
if (!SLOPE_COLORS.some((c) => c.color === band.color)) return `${at} — 색을 고르세요`;
|
||
}
|
||
const sorted = [...bands].sort((a, b) => a.low - b.low);
|
||
for (let i = 1; i < sorted.length; i++)
|
||
if (sorted[i].low < sorted[i - 1].high)
|
||
return `범위가 겹칩니다 — ${sorted[i - 1].low}~${sorted[i - 1].high} % 와 ${sorted[i].low}~${sorted[i].high} %`;
|
||
return null;
|
||
}
|
||
|
||
/** 저장 글 → 범위(깨졌거나 맞지 않으면 기본). */
|
||
export function parseBands(raw: string | null): SlopeBand[] {
|
||
try {
|
||
const got = JSON.parse(raw ?? "null") as unknown;
|
||
if (Array.isArray(got)) {
|
||
const bands = got.map((b: Partial<SlopeBand>) => ({
|
||
low: Number(b?.low),
|
||
high: Number(b?.high),
|
||
color: String(b?.color),
|
||
}));
|
||
if (!checkBands(bands)) return bands;
|
||
}
|
||
} catch {
|
||
// 깨진 값 = 기본
|
||
}
|
||
return DEFAULT_SLOPE_BANDS.map((b) => ({ ...b }));
|
||
}
|
||
|
||
/** 칸 값 v(0~255) → 범위 차례(없으면 −1). */
|
||
export function bandOf(v: number, bands: SlopeBand[]): number {
|
||
if (v > SLOPE_MAX_PCT) return -1;
|
||
const mid = v + 0.5;
|
||
return bands.findIndex(
|
||
(b) => b.low <= mid && (mid < b.high || (b.high >= SLOPE_MAX_PCT && v === SLOPE_MAX_PCT)),
|
||
);
|
||
}
|
||
|
||
/** 1 % 칸 수 → 범위별 비율(유효 칸 대비). */
|
||
export function bandRatios(counts: number[], bands: SlopeBand[]): number[] {
|
||
const total = counts.reduce((sum, n) => sum + n, 0) || 1;
|
||
const out = bands.map(() => 0);
|
||
counts.forEach((n, v) => {
|
||
const i = bandOf(v, bands);
|
||
if (i >= 0) out[i] += n;
|
||
});
|
||
return out.map((n) => n / total);
|
||
}
|