/* 589-2 GL 바탕 따라가기 헬퍼 — `logic/B04_PreProcess_UI_MapGl_Frame.ts` 의 `glCameraOf` · * `lonLatOfNormFn` · `preparedToGeoJson` · `ringsToPolygons` 를 stdin 입력으로 돌려 JSON 으로 냄. * 입력 { meta, lonlat: [[lon, lat]], views: [[w, h, scale, ox, oy, rotDeg]], norm: [[nx, ny]], * layer, box, rings } * views 마다 = 카메라 + 점마다 [캔버스 화면 px, MapLibre 식(세계 512 px · 돌림) 화면 px] */ const path = require("path"); const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); const { load } = makeLoader({ root: ROOT, ui: () => ({ themeColor: () => "" }) }); const render = load("B04_PreProcess", "B04_PreProcess_UI_MapRender.ts"); const prep = load("B04_PreProcess", "logic", "B04_PreProcess_UI_MapRender_Prepare.ts"); const frame = load("B04_PreProcess", "logic", "B04_PreProcess_UI_MapGl_Frame.ts"); const input = JSON.parse(require("fs").readFileSync(0, "utf-8")); const { meta } = input; const norm = render.createTmNormalizer(meta); /** MapLibre 투영(웹 메르카토르 · 세계 512·2^z px · 돌림 = 화면 = R(−도) · 세계). */ function glScreen(camera, w, h, lon, lat) { const world = 512 * 2 ** camera.zoom; const X = (l) => ((l + 180) / 360) * world; const Y = (p) => (0.5 - Math.log(Math.tan(Math.PI / 4 + (p * Math.PI) / 360)) / (2 * Math.PI)) * world; const dx = X(lon) - X(camera.center[0]); const dy = Y(lat) - Y(camera.center[1]); const a = (-camera.bearing * Math.PI) / 180; const gx = dx * Math.cos(a) - dy * Math.sin(a); const gy = dx * Math.sin(a) + dy * Math.cos(a); // 지도 칸 CSS 행렬(칸 가운데 축) const [ca, cb, cc, cd] = camera.css; return [w / 2 + ca * gx + cc * gy, h / 2 + cb * gx + cd * gy]; } const out = { views: input.views.map(([w, h, scale, ox, oy, rotDeg]) => { const view = { width: w, height: h, scale, offsetX: ox, offsetY: oy }; view.mapRect = render.computeMapRect(meta, w, h); const r = (rotDeg * Math.PI) / 180; const turn = ([x, y]) => [ w / 2 + (x - w / 2) * Math.cos(r) - (y - h / 2) * Math.sin(r), h / 2 + (x - w / 2) * Math.sin(r) + (y - h / 2) * Math.cos(r), ]; const toScreen = (nx, ny) => turn(render.normalizedToScreen(view, nx, ny)); const camera = frame.glCameraOf(meta, norm, toScreen, w, h); const points = input.lonlat.map(([lon, lat]) => { const [nx, ny] = prep.normOfLonLat(norm, lon, lat); return [toScreen(nx, ny), glScreen(camera, w, h, lon, lat)]; }); // 화면 격자 → (되돌림) 정규화 → 경위도 → GL 화면 = 제자리여야 const [ox0, oy0] = toScreen(0, 0); const [ox1, oy1] = toScreen(1, 0); const [ox2, oy2] = toScreen(0, 1); const ex = [ox1 - ox0, oy1 - oy0]; const ey = [ox2 - ox0, oy2 - oy0]; const det = ex[0] * ey[1] - ex[1] * ey[0]; const toLonLat = frame.lonLatOfNormFn(norm); const screen = []; for (let i = 0; i <= 4; i += 1) for (let j = 0; j <= 4; j += 1) { const sx = (w * i) / 4; const sy = (h * j) / 4; const dx = sx - ox0; const dy = sy - oy0; const nx = (dx * ey[1] - dy * ey[0]) / det; const ny = (ex[0] * dy - ex[1] * dx) / det; const [lon, lat] = toLonLat(nx, ny); screen.push([[sx, sy], glScreen(camera, w, h, lon, lat)]); } return { camera, points, screen }; }), back: (() => { const toLonLat = frame.lonLatOfNormFn(norm); return input.norm.map(([nx, ny]) => { const [lon, lat] = toLonLat(nx, ny); return [lon, lat, ...prep.normOfLonLat(norm, lon, lat)]; }); })(), geo: frame.preparedToGeoJson(input.layer, (nx, ny) => [nx, ny], input.box), geoAll: frame.preparedToGeoJson(input.layer, (nx, ny) => [nx, ny]), polygons: frame.ringsToPolygons(input.rings), }; process.stdout.write(JSON.stringify(out));