diff --git a/D02_PlanBase/D02_PlanBase_UI_Page.ts b/D02_PlanBase/D02_PlanBase_UI_Page.ts index d7320eeca..5d7ff3008 100644 --- a/D02_PlanBase/D02_PlanBase_UI_Page.ts +++ b/D02_PlanBase/D02_PlanBase_UI_Page.ts @@ -4,6 +4,7 @@ * 조작(끌기 · 지도 누름 · 경위도 입력) = sessionStorage 캐시 · [저장] · [>] = PUT 정본(서버가 TM 셈) * · [>] = 저장 성공 뒤 D03 · 자동저장 없음(CLAUDE.md 5장). * 산림청이 아니면(지정 회사 = 보기만) 끌기 · 누름 · 입력 · [저장] 없음 · [>] = 그냥 D03. + * 지도 [일반][위성](560-3) = 카카오 HYBRID · 고른 값 = 화면 취향(`aislo:pref:`) 기억. * ========================================================================== */ import "./D02_PlanBase_UI_Style.css"; @@ -18,8 +19,10 @@ import { } from "@ui/ui_template_elements"; import { createPanelCard } from "@ui/ui_template_collapsible"; import { createText } from "@ui/ui_template_text"; -import { editMap, type MapPin } from "@ui/ui_template_kakao_map"; +import { editMap, type MapPin, type MapType } from "@ui/ui_template_kakao_map"; +import { createSegment } from "@ui/ui_template_segment"; import { t } from "@ui/ui_template_locale"; +import { readByKey, writeByKey } from "../A00_Common/b_page_state"; import { mountGroupStep } from "../A00_Common/group_workflow"; import { fetchSessionUser } from "../A06_Login/A06_Login_Api_Fetch"; import { navigateTo } from "../A00_Common/router"; @@ -40,6 +43,7 @@ interface PointsView { const KOREA_CENTER: [number, number] = [127.8, 36.3]; const DIGITS = 7; // 경위도 소수 7 자리 ≈ 1 cm +const PREF_MAP_TYPE = "aislo:pref:d02.map.type"; const cacheKey = (projectId: string): string => `d02_points_${projectId}`; @@ -103,7 +107,22 @@ export async function renderD02PlanBase(root: HTMLElement): Promise { place(active, { lon, lat }); }, }); - mapHost.append(map.root); + const mapType = (readByKey(PREF_MAP_TYPE) === "HYBRID" ? "HYBRID" : "ROADMAP") as MapType; + map.setType(mapType); + const typePick = createSegment({ + options: [ + { value: "ROADMAP", label: t("D02_Map_Normal") }, + { value: "HYBRID", label: t("D02_Map_Satellite") }, + ], + value: mapType, + ariaLabel: t("D02_Map_Type"), + onChange: (value) => { + map.setType(value as MapType); + writeByKey(PREF_MAP_TYPE, value); + }, + }); + typePick.root.classList.add("d02-map__type"); + mapHost.append(map.root, typePick.root); function place(index: number, at: LonLat): void { points[index] = { lon: +at.lon.toFixed(DIGITS), lat: +at.lat.toFixed(DIGITS) }; diff --git a/D02_PlanBase/D02_PlanBase_UI_Style.css b/D02_PlanBase/D02_PlanBase_UI_Style.css index fe70a3929..433cfb9b4 100644 --- a/D02_PlanBase/D02_PlanBase_UI_Style.css +++ b/D02_PlanBase/D02_PlanBase_UI_Style.css @@ -1,8 +1,17 @@ /* D02 기준 지정(529-3 · 560-2) — 메인 지도 = 본문 높이 가득 · 좌 시점 · 종점 표 */ .d02-map { + position: relative; height: calc(100dvh - var(--app-header-h)); } +/* 560-3 바탕 [일반][위성] — 지도 위 오른쪽 위 */ +.d02-map__type { + position: absolute; + top: var(--spacing-8); + right: var(--spacing-8); + z-index: 2; +} + .d02-map .ui-kakao-map, .d02-map .ui-kakao-map__box { height: 100%; diff --git a/ui_template/ui_template_kakao_map.ts b/ui_template/ui_template_kakao_map.ts index 5b7df7254..220a9b3cd 100644 --- a/ui_template/ui_template_kakao_map.ts +++ b/ui_template/ui_template_kakao_map.ts @@ -6,6 +6,7 @@ * (.env JS 키 · 공개용 · 도메인 등록으로 막힘) · 키 없음 · 못 뜸 = 안내 한 줄 + 사진 올리기 * 길 선 = 부르는 쪽이 준 [위도, 경도] 목록 · 처음 한 번만 맞춤(153 · 그 뒤 보기 그대로) * (244-10) SDK 못 받음(도메인 미등록 401) = 지금 주소(location.origin) 등록 안내 한 줄 + * (560-3) 바탕 종류 = `setType("ROADMAP" | "HYBRID")`(위성 + 지명) · 단추는 부르는 쪽 부품 * ========================================================================== */ import "./ui_template_kakao_map.css"; @@ -29,6 +30,7 @@ interface KMap { relayout(): void; getBounds(): { contain(p: LatLng): boolean }; panTo(p: LatLng): void; + setMapTypeId(id: number): void; } interface KOverlay { setMap(map: KMap | null): void; @@ -52,8 +54,12 @@ interface KakaoMaps { addListener(target: object, type: string, fn: (e: { latLng: LatLng }) => void): void; }; CustomOverlay: new (o: { position: LatLng; content: HTMLElement; yAnchor: number }) => KOverlay; + MapTypeId: Record; } +/** 바탕 지도 종류 — 일반 · 위성 + 지명 */ +export type MapType = "ROADMAP" | "HYBRID"; + let sdk: Promise | null = null; /** SDK 한 번 싣기 — 키 없음 · 못 받음 = NO_MAP 으로 거절(다음 부름 때 다시) */ @@ -111,6 +117,8 @@ export interface EditMap { colors?: string[], focus?: string, ) => void; + /** 바탕 종류(지도가 아직 없으면 만들 때 씀) */ + setType: (type: MapType) => void; } /** 고르는 지도 — 움직일 표지 끌기 = onMove · 지도 누름 = onClick(있을 때만) · 점 없으면 center(경도, 위도) */ @@ -126,6 +134,8 @@ export function editMap(opts: { let map: KMap | null = null; let drawn: KOverlay[] = []; let fitted = false; // 153 — 처음 한 번만 맞춤 · 그 뒤 고름 · 끌기는 보기(중심 · 줌) 그대로 + let type: MapType = "ROADMAP"; + let mapTypes: KakaoMaps["MapTypeId"] | null = null; const set = ( pins: MapPin[], @@ -147,6 +157,8 @@ export function editMap(opts: { box.hidden = false; if (!map) { map = new maps.Map(box, { center: first, level: 7 }); + mapTypes = maps.MapTypeId; + map.setMapTypeId(mapTypes[type]); if (opts.onClick) maps.event.addListener(map, "click", (e) => opts.onClick!(e.latLng.getLng(), e.latLng.getLat()), @@ -222,5 +234,9 @@ export function editMap(opts: { return { root: el("div", { className: "ui-kakao-map", children: [hint, box] }), set, + setType: (next) => { + type = next; + if (map && mapTypes) map.setMapTypeId(mapTypes[next]); + }, }; } diff --git a/ui_template/ui_template_locale_de.ts b/ui_template/ui_template_locale_de.ts index 7442a766c..d01bd5cce 100644 --- a/ui_template/ui_template_locale_de.ts +++ b/ui_template/ui_template_locale_de.ts @@ -129,6 +129,9 @@ export const ui_locales_de = { D02_Lat: ["위도", "Lat"], D02_Pick_Point: ["지도에 놓을 지점 고르기", "Pick the point to place on the map"], D02_Map_Hint: ["지도를 눌러 지점을 놓습니다.", "Click the map to place a point."], + D02_Map_Type: ["바탕 지도", "Base map"], + D02_Map_Normal: ["일반", "Map"], + D02_Map_Satellite: ["위성", "Satellite"], D02_View_Only: [ "보기 전용 — 지점은 산림청만 고칩니다.", "View only — only the Forest Service edits points.",