/* 281-1 배수유역 지도 줌아웃 한계 헬퍼 — `B05_Profile_UI_Drainage_ZoomRange.ts` 를 * 그 자리에서 트랜스파일해 Node 로 돌리고 값을 JSON 으로 낸다. * `.cjs` 까닭은 `helper_b05_curve_radius_floor.cjs` 와 같다. */ const fs = require("fs"); const path = require("path"); const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); /** TS 한 장을 CommonJS 로 옮겨 실행한다. `@ui/` 는 색 함수만 쓰므로 빈 값으로 갈음한다. */ const { loadTs } = makeLoader({ root: ROOT, requireFor: (full) => (request) => { if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`)); if (request.startsWith("@ui/")) return { themeColor: () => "" }; return require(request); }, }); const zoom = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_Drainage_ZoomRange.ts")); const render = loadTs(path.join(ROOT, "B04_PreProcess", "B04_PreProcess_UI_MapRender.ts")); const { computeZoomOutLimit, clampWheelScale, ZOOM_OUT_ROUTE_FACTOR, ZOOM_OUT_FALLBACK_SCALE } = zoom; // 도엽 4000 × 3000 m — 경위도는 1 도 = 1000 m 로 단순하게 둔다. const META = { x_min: 0, x_max: 4000, y_min: 0, y_max: 3000, width_meters: 4000, height_meters: 3000, lon_min: 127, lon_max: 131, lat_min: 36, lat_max: 39, }; const W = 800; const H = 600; // 도엽과 같은 비율 — 배율 1 = 화면 폭 4000 m const toLonLat = (x, y) => [127 + x / 1000, 36 + y / 1000]; /** 배율 s 에서 화면 폭에 드는 실폭(m). */ const viewWidthM = (s) => 4000 / s; // 노선 1000 × 500 m (가운데 2000, 1500) — 2 배 = 2000 × 1000 m. const ROUTE = [ { x: 1500, y: 1250 }, { x: 2500, y: 1750 }, ]; const routeOnly = computeZoomOutLimit(META, ROUTE, [], W, H); // 유역이 노선 2 배 범위 밖(서쪽 400 m 까지)으로 나감 → 폭 2600 m. const basinOut = computeZoomOutLimit( META, ROUTE, [{ polygon_lonlat: [toLonLat(400, 1500), toLonLat(1500, 1400), toLonLat(1500, 1600)] }], W, H, ); // 유역이 2 배 범위 안 → 한계 그대로. const basinIn = computeZoomOutLimit( META, ROUTE, [{ polygon_lonlat: [toLonLat(1800, 1400), toLonLat(2200, 1400), toLonLat(2000, 1600)] }], W, H, ); // 노선이 도엽만큼 길면 2 배가 도엽보다 큼 → 도엽 전체(배율 1)에서 멈춤. const wide = computeZoomOutLimit( META, [ { x: 100, y: 100 }, { x: 3900, y: 2900 }, ], [], W, H, ); // 짧은 노선(100 m) — 2 배(200 m)가 처음 보기(노선 + 200 m 여유 양쪽)보다 좁음 → 처음 보기에 맞춤. const SHORT = [ { x: 1950, y: 1500 }, { x: 2050, y: 1500 }, ]; const short = computeZoomOutLimit(META, SHORT, [], W, H); const shortInitial = render.computeRouteView( META, { x_min: 1950, x_max: 2050, y_min: 1500, y_max: 1500 }, W, H, ).scale; // 휠 — 밀기(×0.87)를 계속하면 한계에서 멈춤 · 당기기(×1.15)는 상한에서 멈춤. let scale = 3; for (let i = 0; i < 40; i += 1) scale = clampWheelScale(scale * 0.87, scale, routeOnly, 200); const wheelOutStop = scale; for (let i = 0; i < 80; i += 1) scale = clampWheelScale(scale * 1.15, scale, routeOnly, 200); const wheelInStop = scale; // 이미 한계 밖(더 멀리)에 있으면 밀어도 더 안 빠지고 튀지도 않음. const outside = clampWheelScale(1.2 * 0.87, 1.2, routeOnly, 200); process.stdout.write( JSON.stringify({ factor: ZOOM_OUT_ROUTE_FACTOR, fallbackConst: ZOOM_OUT_FALLBACK_SCALE, routeOnly, routeOnlyViewM: viewWidthM(routeOnly), basinOutViewM: viewWidthM(basinOut), basinIn, noRoute: computeZoomOutLimit(META, [], [], W, H), noMeta: computeZoomOutLimit(null, ROUTE, [], W, H), wide, short, shortInitial, wheelOutStop, wheelInStop, outside, }), );