- overlay 파괴/재생성 대신 layers 비움으로 전환 - globe ship 레이어 visibility 즉시 토글 (projectionBusy 우회) - fleet circles fill vertex 초과 수정 (steps 72→36/24) - globe scrollZoom easing 경고 수정 - projection 비영속화 (항상 mercator 시작) - globe 레이어 준비 전까지 3D 토글 비활성화 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
62 lines
1.8 KiB
TypeScript
62 lines
1.8 KiB
TypeScript
import { DEG2RAD, RAD2DEG, EARTH_RADIUS_M } from '../constants';
|
|
|
|
export const clampNumber = (value: number, minValue: number, maxValue: number) =>
|
|
Math.max(minValue, Math.min(maxValue, value));
|
|
|
|
export function wrapLonDeg(lon: number) {
|
|
const v = ((lon + 180) % 360 + 360) % 360;
|
|
return v - 180;
|
|
}
|
|
|
|
export function destinationPointLngLat(
|
|
from: [number, number],
|
|
bearingDeg: number,
|
|
distanceMeters: number,
|
|
): [number, number] {
|
|
const [lonDeg, latDeg] = from;
|
|
const lat1 = latDeg * DEG2RAD;
|
|
const lon1 = lonDeg * DEG2RAD;
|
|
const brng = bearingDeg * DEG2RAD;
|
|
const dr = Math.max(0, distanceMeters) / EARTH_RADIUS_M;
|
|
if (!Number.isFinite(dr) || dr === 0) return [lonDeg, latDeg];
|
|
|
|
const sinLat1 = Math.sin(lat1);
|
|
const cosLat1 = Math.cos(lat1);
|
|
const sinDr = Math.sin(dr);
|
|
const cosDr = Math.cos(dr);
|
|
|
|
const lat2 = Math.asin(sinLat1 * cosDr + cosLat1 * sinDr * Math.cos(brng));
|
|
const lon2 =
|
|
lon1 +
|
|
Math.atan2(
|
|
Math.sin(brng) * sinDr * cosLat1,
|
|
cosDr - sinLat1 * Math.sin(lat2),
|
|
);
|
|
|
|
const outLon = wrapLonDeg(lon2 * RAD2DEG);
|
|
const outLat = clampNumber(lat2 * RAD2DEG, -85.0, 85.0);
|
|
return [outLon, outLat];
|
|
}
|
|
|
|
export function circleRingLngLat(center: [number, number], radiusMeters: number, steps = 36): [number, number][] {
|
|
// 반경이 지구 둘레의 1/4 (≈10,000km)을 넘으면 클램핑
|
|
const r = clampNumber(radiusMeters, 0, EARTH_RADIUS_M * Math.PI * 0.5);
|
|
|
|
const ring: [number, number][] = [];
|
|
for (let i = 0; i <= steps; i++) {
|
|
const a = (i / steps) * Math.PI * 2;
|
|
const pt = destinationPointLngLat(center, a * RAD2DEG, r);
|
|
ring.push(pt);
|
|
}
|
|
// 고리 닫기 보정
|
|
if (ring.length > 1) {
|
|
ring[ring.length - 1] = ring[0];
|
|
}
|
|
return ring;
|
|
}
|
|
|
|
export function normalizeAngleDeg(value: number, offset = 0): number {
|
|
const v = value + offset;
|
|
return ((v % 360) + 360) % 360;
|
|
}
|