auto: 2026-07-26 19:14 (ESD_LAPTOP)
This commit is contained in:
@@ -121,6 +121,10 @@ def crop_national_contours(bounds_wgs84: dict, output_dir: Path) -> bool:
|
||||
logger.warning("국가 등고선 클리핑 결과가 비어 있습니다: bbox=%s", bounds_wgs84)
|
||||
return False
|
||||
|
||||
# 라벨·구분에 필요한 속성만 유지 (Timestamp 등 나머지는 JSON 직렬화 불가·전송량 낭비)
|
||||
keep = [c for c in ("CTRLN_SE", "TPGRPH_SE", "CTRLN_HG") if c in gdf.columns]
|
||||
gdf = gdf[keep + ["geometry"]]
|
||||
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
gdf_wgs84 = gdf.to_crs("EPSG:4326")
|
||||
geojson_out = output_dir / "등고선_bounds.geojson"
|
||||
@@ -163,7 +167,7 @@ def download_all_gis_vectors(prj_path: Path, bounds_meter: dict, output_dir: Pat
|
||||
request_vworld_wfs("lt_c_uq111", "용도지역도_bounds.geojson", bounds_wgs84, output_dir)
|
||||
request_vworld_wfs("lt_c_adsigg", "행정구역_시군구_bounds.geojson", bounds_wgs84, output_dir)
|
||||
request_vworld_wfs("lt_c_adri", "행정구역_읍면동_bounds.geojson", bounds_wgs84, output_dir)
|
||||
request_vworld_wfs("lt_c_gimshydro", "수계망_물줄기_bounds.geojson", bounds_wgs84, output_dir)
|
||||
# 수계망은 도엽 하천중심선(세류 포함)으로 대체되어 다운로드하지 않음 (2026-07-26 사용자 지시)
|
||||
request_vworld_wfs(
|
||||
"lt_c_kfdrssigugrade", "산사태위험등급_bounds.geojson", bounds_wgs84, output_dir
|
||||
)
|
||||
|
||||
@@ -107,7 +107,6 @@ async def get_project_geojson(project_id: UUID, layer: str) -> dict[str, Any] |
|
||||
"용도지역": "용도지역도_bounds.geojson",
|
||||
"행정구역_시군구": "행정구역_시군구_bounds.geojson",
|
||||
"행정구역_읍면동": "행정구역_읍면동_bounds.geojson",
|
||||
"수계망": "수계망_물줄기_bounds.geojson",
|
||||
"등고선": "등고선_bounds.geojson",
|
||||
"산사태": "산사태위험등급_bounds.geojson",
|
||||
**_SHEET_GEOJSON_FILES,
|
||||
@@ -182,7 +181,6 @@ async def get_vector_tile(project_id: UUID, layer: str, z: int, x: int, y: int)
|
||||
"용도지역": "용도지역도_bounds.geojson",
|
||||
"행정구역_시군구": "행정구역_시군구_bounds.geojson",
|
||||
"행정구역_읍면동": "행정구역_읍면동_bounds.geojson",
|
||||
"수계망": "수계망_물줄기_bounds.geojson",
|
||||
"등고선": "등고선_bounds.geojson",
|
||||
"산사태": "산사태위험등급_bounds.geojson",
|
||||
"임도노선": "임도노선.geojson",
|
||||
|
||||
@@ -21,6 +21,7 @@ type GeoJsonGeometry = {
|
||||
|
||||
type GeoJsonFeature = {
|
||||
geometry?: GeoJsonGeometry | null;
|
||||
properties?: Record<string, unknown> | null;
|
||||
};
|
||||
|
||||
type GeoJsonCollection = {
|
||||
@@ -30,7 +31,6 @@ type GeoJsonCollection = {
|
||||
const BACKGROUND_LAYERS = ["white", "satellite", "hybrid"] as const;
|
||||
const GIS_LAYERS = [
|
||||
"지적도",
|
||||
"수계망",
|
||||
"산사태",
|
||||
"행정구역_시군구",
|
||||
"행정구역_읍면동",
|
||||
@@ -47,19 +47,24 @@ type GisLayer = (typeof GIS_LAYERS)[number];
|
||||
|
||||
const GIS_LAYER_COLORS: Record<GisLayer, string> = {
|
||||
지적도: "#f97316",
|
||||
수계망: "#0ea5e9",
|
||||
산사태: "#ef4444",
|
||||
행정구역_시군구: "#7c3aed",
|
||||
행정구역_읍면동: "#22c55e",
|
||||
등고선: "#a16207",
|
||||
도엽_등고선: "#57534e",
|
||||
등고선: "#fdba74",
|
||||
도엽_등고선: "#a5b4fc",
|
||||
도엽_하천중심선: "#2563eb",
|
||||
도엽_표고점: "#334155",
|
||||
도엽_표고점: "#f9a8d4",
|
||||
도엽_성절토: "#f43f5e",
|
||||
도엽_옹벽석축: "#0f766e",
|
||||
도엽_유수방향: "#0891b2",
|
||||
};
|
||||
|
||||
// 등고 라벨 표기 대상 레이어와 표고 속성 키 (gpkg=CTRLN_HG, 도엽=등고수치)
|
||||
const CONTOUR_LABEL_KEYS: Partial<Record<GisLayer, string[]>> = {
|
||||
등고선: ["CTRLN_HG"],
|
||||
도엽_등고선: ["등고수치"],
|
||||
};
|
||||
|
||||
function L(key: keyof typeof ui_locales): string {
|
||||
return ui_locales[key][currentLanguageIndex];
|
||||
}
|
||||
@@ -133,6 +138,7 @@ export function createSurfaceMapViewer(): SurfaceMapViewer {
|
||||
const geoJsonLayers = new Map<GisLayer, GeoJsonCollection>();
|
||||
const activeBackgrounds = new Set<BackgroundLayer>(BACKGROUND_LAYERS);
|
||||
const activeGisLayers = new Set<GisLayer>(GIS_LAYERS);
|
||||
let showContourLabels = false;
|
||||
let scale = 1;
|
||||
let offsetX = 0;
|
||||
let offsetY = 0;
|
||||
@@ -176,7 +182,6 @@ export function createSurfaceMapViewer(): SurfaceMapViewer {
|
||||
|
||||
const gisLabels: Record<GisLayer, string> = {
|
||||
지적도: L("B04_Surface_Map_Cadastral"),
|
||||
수계망: L("B04_Surface_Map_Water"),
|
||||
산사태: L("B04_Surface_Map_Landslide"),
|
||||
행정구역_시군구: L("B04_Surface_Map_Sigungu"),
|
||||
행정구역_읍면동: L("B04_Surface_Map_Eupmyeondong"),
|
||||
@@ -194,6 +199,20 @@ export function createSurfaceMapViewer(): SurfaceMapViewer {
|
||||
);
|
||||
});
|
||||
|
||||
// 등고 라벨 보기/숨기기 (기본 숨김 — 등고선·도엽 등고선의 계곡선 수치 표기)
|
||||
const contourLabelButton = document.createElement("button");
|
||||
contourLabelButton.type = "button";
|
||||
contourLabelButton.className = "b04-map__layer-button";
|
||||
contourLabelButton.textContent = L("B04_Surface_Map_ContourLabel");
|
||||
contourLabelButton.setAttribute("aria-pressed", "false");
|
||||
contourLabelButton.addEventListener("click", () => {
|
||||
showContourLabels = !showContourLabels;
|
||||
contourLabelButton.classList.toggle("is-active", showContourLabels);
|
||||
contourLabelButton.setAttribute("aria-pressed", String(showContourLabels));
|
||||
drawVectorLayer();
|
||||
});
|
||||
gisButtons.append(contourLabelButton);
|
||||
|
||||
function updateImageTransform(): void {
|
||||
backgroundImages.forEach((image) => {
|
||||
image.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
|
||||
@@ -307,6 +326,52 @@ export function createSurfaceMapViewer(): SurfaceMapViewer {
|
||||
context.fill();
|
||||
}
|
||||
|
||||
function contourLabelAnchor(geometry: GeoJsonGeometry): [number, number] | null {
|
||||
const coords = geometry.coordinates;
|
||||
if (!Array.isArray(coords)) return null;
|
||||
const line =
|
||||
geometry.type === "LineString"
|
||||
? coords
|
||||
: geometry.type === "MultiLineString"
|
||||
? coords[0]
|
||||
: null;
|
||||
if (!Array.isArray(line) || line.length === 0) return null;
|
||||
const mid = line[Math.floor(line.length / 2)];
|
||||
if (!Array.isArray(mid) || typeof mid[0] !== "number" || typeof mid[1] !== "number") {
|
||||
return null;
|
||||
}
|
||||
return [mid[0], mid[1]];
|
||||
}
|
||||
|
||||
function drawContourLabels(
|
||||
context: CanvasRenderingContext2D,
|
||||
width: number,
|
||||
height: number,
|
||||
): void {
|
||||
context.font = "10px sans-serif";
|
||||
context.textAlign = "center";
|
||||
context.textBaseline = "middle";
|
||||
(Object.keys(CONTOUR_LABEL_KEYS) as GisLayer[]).forEach((layer) => {
|
||||
if (!activeGisLayers.has(layer)) return;
|
||||
const keys = CONTOUR_LABEL_KEYS[layer] ?? [];
|
||||
geoJsonLayers.get(layer)?.features?.forEach((feature) => {
|
||||
if (!feature.geometry) return;
|
||||
const raw = keys.map((key) => feature.properties?.[key]).find((value) => value != null);
|
||||
const elevation = typeof raw === "number" ? raw : Number(raw);
|
||||
// 계곡선(25m 배수)만 라벨 — 전체 표기 시 화면이 숫자로 뒤덮이는 것 방지
|
||||
if (!Number.isFinite(elevation) || elevation % 25 !== 0) return;
|
||||
const anchor = contourLabelAnchor(feature.geometry);
|
||||
if (!anchor) return;
|
||||
const [x, y] = toCanvasPoint(anchor[0], anchor[1], width, height);
|
||||
context.lineWidth = 3;
|
||||
context.strokeStyle = "rgba(255, 255, 255, 0.9)";
|
||||
context.strokeText(String(elevation), x, y);
|
||||
context.fillStyle = GIS_LAYER_COLORS[layer];
|
||||
context.fillText(String(elevation), x, y);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function drawGeometry(
|
||||
context: CanvasRenderingContext2D,
|
||||
geometry: GeoJsonGeometry,
|
||||
@@ -371,6 +436,7 @@ export function createSurfaceMapViewer(): SurfaceMapViewer {
|
||||
if (feature.geometry) drawGeometry(context, feature.geometry, width, height);
|
||||
});
|
||||
});
|
||||
if (showContourLabels) drawContourLabels(context, width, height);
|
||||
updateImageTransform();
|
||||
drawScaleBar(width, height);
|
||||
}
|
||||
|
||||
@@ -658,6 +658,7 @@ export const ui_locales = {
|
||||
B04_Surface_Map_Eupmyeondong: ["읍면동", "Town boundary"],
|
||||
B04_Surface_Map_Contour: ["등고선", "Contour lines"],
|
||||
B04_Surface_Map_SheetContour: ["도엽 등고선", "Sheet contours"],
|
||||
B04_Surface_Map_ContourLabel: ["등고 라벨", "Contour labels"],
|
||||
B04_Surface_Map_SheetStream: ["세류(하천중심선)", "Stream centerline"],
|
||||
B04_Surface_Map_SheetElevPoint: ["표고점", "Spot elevation"],
|
||||
B04_Surface_Map_SheetCutFill: ["성절토", "Cut/fill slope"],
|
||||
|
||||
Reference in New Issue
Block a user