- map_layer_list = 겹 스물셋 · 처음 값 · 페이지 표(d03 · b05 보일 겹 · 처음 켬 · 취향 열쇠 — A② · A⑨) - map_layer_specs = GL 겹 꼴 · 차례(D03 UI_MapGl 에서 옮김 · 선 겹 꼴은 LINE_LAYERS 한 벌) - map_layer_bar = 겹 단추 줄 · 켬끔 · 취향 · 그래픽 카드 단추 / map_legend = 범례 칸 · 조각 겹 범례 - map_forest_layers = D03_UI_ForestLayers 옮김(이름만) - D03 UI_Layers 683 → 639 줄 · UI_MapGl 423 → 115 줄 · UI_LayerDefs 는 캔버스 도움 + 옛 이름 다시 내놓음 - 시험 길 고침(553-4 · 581-2 · 584-1 · 584-3 · 606-1 · 615 · 622-2) — 옮긴 파일 자리 · 553-4 겹 목록은 622-2 때 낡은 것(국유림 임도 · 경제림 · 생태자연도 빠짐) 같이 고침 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
164 lines
6.0 KiB
TypeScript
164 lines
6.0 KiB
TypeScript
/* =============================================================================
|
||
* D03_PlanAnalysis_UI_LayerDefs.ts
|
||
* D03 바탕 겹 받기 · 옛 캔버스 도움(PLAN 569-2 · 581-1 · 581-2 · 584-1 · 584-3 · 586-2 · 624-2) — 임도 짐작 구분 · 물 면 ·
|
||
* 면 겹 채우기 · 선 긋기 · 그림 겹 놓기 · GL 바탕 자료(지물 색 붙이기 · 그림 귀퉁이 · 음영 그늘 그림).
|
||
* 겹 목록 · 처음 값 · 켬끔 취향 · 선 겹 꼴 · 보호구역 색 = 공용 `A00_Common/map/map_layer_list`(624-2).
|
||
* 부르는 곳 = `D03_PlanAnalysis_UI_Layers.ts`(받기 · 그리기 차례 · 범례).
|
||
* ========================================================================== */
|
||
|
||
import type { MapLayerKey } from "../A00_Common/map/map_layer_list";
|
||
import {
|
||
drawPreparedLayer,
|
||
normalizedToScreen,
|
||
type GeoJsonCollection,
|
||
type PreparedLayer,
|
||
type ViewState,
|
||
} from "../B04_PreProcess/B04_PreProcess_UI_MapRender";
|
||
import type { PlanRaster } from "./D03_PlanAnalysis_Api";
|
||
import type { ScreenMap } from "./D03_PlanAnalysis_UI_Map";
|
||
import type { GlImage } from "./D03_PlanAnalysis_UI_MapGl";
|
||
|
||
/** 겹 목록 · 페이지 표 = 공용 `map_layer_list`(624-2) — 옛 이름으로 다시 내놓음(캔버스 · 조각 · 임도망 목록이 읽음). */
|
||
export type PlanLayerKey = MapLayerKey;
|
||
export {
|
||
LINE_KEYS,
|
||
LINE_LAYERS,
|
||
ZONE_COLORS,
|
||
type RoadPick,
|
||
} from "../A00_Common/map/map_layer_list";
|
||
|
||
/** 도로 중 기존 임도로 짐작하는 구분. */
|
||
export const FOREST_ROAD_KINDS = new Set(["소로", "미분류"]);
|
||
/** 물 면 = 도엽 수계(E) 면 셋. */
|
||
export const WATER_KEYS = ["실폭하천", "수계면E003", "수계면E005"];
|
||
|
||
export const hectare = (m2: number): string => `${(m2 / 10000).toFixed(1)} ha`;
|
||
|
||
/** 면 겹 채움 — 지물마다 한 길(evenodd = 구멍) · 화면 밖 지물 거름(돌린 지도 귀퉁이까지 여유). */
|
||
export function fillLayer(
|
||
context: CanvasRenderingContext2D,
|
||
layer: PreparedLayer,
|
||
view: ViewState,
|
||
fill: string,
|
||
alpha: number,
|
||
edge?: { color: string; width: number; dash?: number[] },
|
||
): void {
|
||
const [ox, oy] = normalizedToScreen(view, 0, 0);
|
||
const [ex, ey] = normalizedToScreen(view, 1, 1);
|
||
const ax = ex - ox;
|
||
const ay = ey - oy;
|
||
const pad = Math.max(view.width, view.height) / 2;
|
||
context.fillStyle = fill;
|
||
if (edge) {
|
||
context.strokeStyle = edge.color;
|
||
context.lineWidth = edge.width;
|
||
context.setLineDash(edge.dash ?? []);
|
||
}
|
||
for (const feature of layer.features) {
|
||
if (
|
||
feature.maxX * ax + ox < -pad ||
|
||
feature.minX * ax + ox > view.width + pad ||
|
||
feature.maxY * ay + oy < -pad ||
|
||
feature.minY * ay + oy > view.height + pad
|
||
)
|
||
continue;
|
||
context.beginPath();
|
||
for (const { coords } of feature.parts) {
|
||
context.moveTo(coords[0] * ax + ox, coords[1] * ay + oy);
|
||
for (let i = 2; i < coords.length; i += 2)
|
||
context.lineTo(coords[i] * ax + ox, coords[i + 1] * ay + oy);
|
||
context.closePath();
|
||
}
|
||
context.globalAlpha = alpha;
|
||
context.fill("evenodd");
|
||
if (edge) {
|
||
context.globalAlpha = 0.9;
|
||
context.stroke();
|
||
}
|
||
}
|
||
context.setLineDash([]);
|
||
}
|
||
|
||
export const loadImage = (src: string): Promise<HTMLImageElement> =>
|
||
new Promise((done, fail) => {
|
||
const image = new Image();
|
||
image.onload = () => done(image);
|
||
image.onerror = () => fail(new Error("그림을 읽지 못했습니다"));
|
||
image.src = src;
|
||
});
|
||
|
||
/** 선 겹 긋기(점 솎음 `dot` · `everyM` = 등고 간격 거름). */
|
||
export function stroke(
|
||
context: CanvasRenderingContext2D,
|
||
map: ScreenMap,
|
||
layer: PreparedLayer,
|
||
color: string,
|
||
width: number,
|
||
everyM?: number,
|
||
): void {
|
||
context.strokeStyle = color;
|
||
context.lineWidth = width;
|
||
drawPreparedLayer(context, layer, map.view, "dot", everyM);
|
||
}
|
||
|
||
/** 그림 겹(작업 좌표계 m 상자 · 위 = 북). */
|
||
export function drawRaster(
|
||
context: CanvasRenderingContext2D,
|
||
map: ScreenMap,
|
||
raster: { raster: PlanRaster; image: HTMLImageElement },
|
||
): void {
|
||
// 세 귀퉁이로 놓음 — TM 격자는 경위도 바탕에 대해 수렴각만큼 돌아 있음(586-0)
|
||
const [x0, y0, x1, y1] = raster.raster.box_m;
|
||
const [left, top] = map.toScreen([x0, y1]);
|
||
const [right, rightY] = map.toScreen([x1, y1]);
|
||
const [bottomX, bottom] = map.toScreen([x0, y0]);
|
||
const { naturalWidth: w, naturalHeight: h } = raster.image;
|
||
context.transform(
|
||
(right - left) / w,
|
||
(rightY - top) / w,
|
||
(bottomX - left) / h,
|
||
(bottom - top) / h,
|
||
left,
|
||
top,
|
||
);
|
||
context.drawImage(raster.image, 0, 0);
|
||
}
|
||
|
||
/** GL 자료 — 지물마다 그릴 색(`_color`) · 덧 속성을 붙인 사본. */
|
||
export function glTagged(
|
||
collection: GeoJsonCollection,
|
||
color: (props: Record<string, unknown>) => string,
|
||
extra: Record<string, unknown> = {},
|
||
): GeoJsonCollection {
|
||
return {
|
||
...collection,
|
||
features: (collection.features ?? []).map((feature) => ({
|
||
...feature,
|
||
properties: { ...feature.properties, ...extra, _color: color(feature.properties ?? {}) },
|
||
})),
|
||
};
|
||
}
|
||
|
||
/** GL 그림 자리 — 서버 경위도 귀퉁이(왼쪽 아래부터 돎) → 왼쪽 위부터 · 없으면 null. */
|
||
export function glImage(raster: PlanRaster, url = raster.image): GlImage | null {
|
||
const box = raster.box;
|
||
return box?.length === 4 ? { url, corners: [box[3], box[2], box[1], box[0]] } : null;
|
||
}
|
||
|
||
/** 음영 그늘 그림 — 위성 위 「곱하기 0.75」와 같은 검은 반투명(밝기 g → 투명도 0.75 · (1 − g)). */
|
||
export function shadeImage(image: HTMLImageElement): string {
|
||
const canvas = document.createElement("canvas");
|
||
canvas.width = image.naturalWidth;
|
||
canvas.height = image.naturalHeight;
|
||
const context = canvas.getContext("2d")!;
|
||
context.drawImage(image, 0, 0);
|
||
const pixels = context.getImageData(0, 0, canvas.width, canvas.height);
|
||
const px = pixels.data;
|
||
for (let i = 0; i < px.length; i += 4) {
|
||
px[i + 3] = Math.round(0.75 * (255 - px[i]) * (px[i + 3] / 255));
|
||
px[i] = px[i + 1] = px[i + 2] = 0;
|
||
}
|
||
context.putImageData(pixels, 0, 0);
|
||
return canvas.toDataURL("image/png");
|
||
}
|