Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014utrjfoW831Vc5wJUd13kL
131 lines
4.2 KiB
JavaScript
131 lines
4.2 KiB
JavaScript
/* 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 ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||
|
||
const ROOT = path.join(__dirname, "..", "..");
|
||
const cache = new Map();
|
||
|
||
/** TS 한 장을 CommonJS 로 옮겨 실행한다. `@ui/` 는 색 함수만 쓰므로 빈 값으로 갈음한다. */
|
||
function loadTs(file) {
|
||
const full = path.resolve(file);
|
||
if (cache.has(full)) return cache.get(full);
|
||
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
|
||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||
}).outputText;
|
||
const box = { exports: {} };
|
||
cache.set(full, box.exports);
|
||
const localRequire = (request) => {
|
||
if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`));
|
||
if (request.startsWith("@ui/")) return { themeColor: () => "" };
|
||
return require(request);
|
||
};
|
||
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
||
cache.set(full, box.exports);
|
||
return box.exports;
|
||
}
|
||
|
||
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,
|
||
}),
|
||
);
|