export interface LonLatPoint { lon: number lat: number } export interface WebMercatorPoint { x: number y: number } export interface WorldTilePoint { x: number y: number } const MAX_LATITUDE = 85.05112878 const EARTH_RADIUS = 6378137 function clampLatitude(lat: number): number { return Math.max(-MAX_LATITUDE, Math.min(MAX_LATITUDE, lat)) } export function lonLatToWebMercator(point: LonLatPoint): WebMercatorPoint { const latitude = clampLatitude(point.lat) const lonRad = point.lon * Math.PI / 180 const latRad = latitude * Math.PI / 180 return { x: EARTH_RADIUS * lonRad, y: EARTH_RADIUS * Math.log(Math.tan(Math.PI / 4 + latRad / 2)), } } export function webMercatorToLonLat(point: WebMercatorPoint): LonLatPoint { return { lon: point.x / EARTH_RADIUS * 180 / Math.PI, lat: (2 * Math.atan(Math.exp(point.y / EARTH_RADIUS)) - Math.PI / 2) * 180 / Math.PI, } } export function lonLatToWorldTile(point: LonLatPoint, zoom: number): WorldTilePoint { const latitude = clampLatitude(point.lat) const scale = Math.pow(2, zoom) const latRad = latitude * Math.PI / 180 return { x: (point.lon + 180) / 360 * scale, y: (1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2 * scale, } } export function worldTileToLonLat(point: WorldTilePoint, zoom: number): LonLatPoint { const scale = Math.pow(2, zoom) const lon = point.x / scale * 360 - 180 const latRad = Math.atan(Math.sinh(Math.PI * (1 - 2 * point.y / scale))) return { lon, lat: latRad * 180 / Math.PI, } }