- 공용 부품 ui_template_gl_map(지도 세우기 · 자료 · 켬끔 · 색 다시 · 카메라) — D03 GL 껍데기를 올림 · D03 은 겹 꼴만(바뀜 없음) - TM 주인 따라가기(B04_PreProcess_UI_MapGl · logic/_MapGl_Frame) — 화면 가운데 1차 맞춤 닮음꼴 카메라 + 남는 늘림(웹 메르카토르 가로세로 0.43 %)은 지도 칸 CSS 행렬 · 칸 둘레 여유 8 px - B04 하단 지도 — 위성 조각(줌인 선명) · 백지도 · 혼합 · 도엽 · 지적 · 행정 · 등고선 겹 GL · 등고 글 · 배수유역 · 계획선 · 관 = 위 캔버스 - B05 배수유역도 — 위성 · 세부유역 채움(짝홀 다각형) · 분수령 · 도엽 GL · 번호 · 상류 세류 · 관 · 강도 = 위 캔버스 - B05 평면 노선 편집 — 바탕칠 · LAS · 도엽 등고선(간격 거름 · 1 m 선) · 하천(LAS 상자 자름) GL · 노선 둘레 띠 = 지도 칸 clip-path · 고른 등고선 · 글 · 노선 · 꼭짓점 = 위 캔버스 - 켬끔 칸(GPU · 세 화면 한 취향 aislo:pref:map.canvas) · WebGL 못 씀 · 잃음 = 옛 캔버스 + 알림 · 누름 · 끌기 판정 손 안 댐 - B04 층 단추 글 = MapLayers gisLayerLabel(지도 본체 700 줄) - 지킴 시험 test_589_2_gl_frame(카메라 대 캔버스 0.5 px · 칸 행렬 늘이기만 · 되돌림 1 mm · 상자 자르기 · 짝홀 다각형) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
85 lines
3.8 KiB
JavaScript
85 lines
3.8 KiB
JavaScript
/* 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));
|