Files
Aislo/resources/tester/helper_589_2_gl_frame.cjs
T
eomsangdonandClaude Opus 5.5 21afc379e4 feat(B04·B05): 하단 지도 · 평면 노선 편집 · 배수유역도 바탕 MapLibre 적용 — 공용 GL 부품 · TM 주인 카메라 · 처음 = 켬 · WebGL 못 쓰면 옛 캔버스 (PLAN 589-2 · 요구 448)
- 공용 부품 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>
2026-10-29 21:45:41 +09:00

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));