UNPKG

s2-tools

Version:

A collection of geospatial tools primarily designed for WGS84, Web Mercator, and S2.

302 lines 10 kB
import { EARTH_CIRCUMFERENCE } from '../..'; import { degToRad, radToDeg } from '../util'; /** 900913 (Web Mercator) constant */ export const A = 6_378_137.0; /** 900913 (Web Mercator) max extent */ export const MAXEXTENT = 20_037_508.342789244; /** 900913 (Web Mercator) maximum latitude */ export const MAXLAT = 85.0511287798; /** * Given a zoom and tilesize, build mercator positional attributes * @param zoom - the zoom level * @param tileSize - in pixels * @returns - a bounding box sharing zoom size bounds */ function getZoomSize(zoom, tileSize) { const size = tileSize * Math.pow(2, zoom); return [size / 360, size / (2 * Math.PI), size / 2, size]; } /** * Convert Longitude and Latitude to a mercator pixel coordinate * @param ll - the longitude and latitude * @param zoom - the zoom level * @param antiMeridian - true if you want to use the antimeridian * @param tileSize - in pixels * @returns - the mercator pixel */ export function llToPX(ll, zoom, antiMeridian = false, tileSize = 512) { const { min, max, sin, log } = Math; const [Bc, Cc, Zc, Ac] = getZoomSize(zoom, tileSize); const expansion = antiMeridian ? 2 : 1; const d = Zc; const f = min(max(sin(degToRad(ll[1])), -0.999999999999), 0.999999999999); let x = d + ll[0] * Bc; let y = d + 0.5 * log((1 + f) / (1 - f)) * -Cc; if (x > Ac * expansion) x = Ac * expansion; if (y > Ac) y = Ac; return [x, y]; } /** * Convert mercator pixel coordinates to Longitude and Latitude * @param px - the mercator pixel * @param zoom - the zoom level * @param tileSize - in pixels * @returns - the longitude and latitude */ export function pxToLL(px, zoom, tileSize = 512) { const { atan, exp, PI } = Math; const [Bc, Cc, Zc] = getZoomSize(zoom, tileSize); const g = (px[1] - Zc) / -Cc; const lon = (px[0] - Zc) / Bc; const lat = radToDeg(2 * atan(exp(g)) - 0.5 * PI); return [lon, lat]; } /** * Convert Longitude and Latitude to a mercator x-y coordinates * @param ll - the longitude and latitude * @returns - the mercator pixel */ export function llToMerc(ll) { const { tan, log, PI } = Math; let x = degToRad(A * ll[0]); let y = A * log(tan(PI * 0.25 + degToRad(0.5 * ll[1]))); // if xy value is beyond maxextent (e.g. poles), return maxextent. if (x > MAXEXTENT) x = MAXEXTENT; if (x < -MAXEXTENT) x = -MAXEXTENT; if (y > MAXEXTENT) y = MAXEXTENT; if (y < -MAXEXTENT) y = -MAXEXTENT; return [x, y]; } /** * Convert mercator x-y coordinates to Longitude and Latitude * @param merc - the mercator pixel * @returns - the longitude and latitude */ export function mercToLL(merc) { const { atan, exp, PI } = Math; const x = radToDeg(merc[0] / A); const y = radToDeg(0.5 * PI - 2 * atan(exp(-merc[1] / A))); return [x, y]; } /** * Convert a pixel coordinate to a tile x-y coordinate * @param px - the pixel * @param tileSize - in pixels * @returns - the tile x-y */ export function pxToTile(px, tileSize = 512) { const { floor } = Math; const x = floor(px[0] / tileSize); const y = floor(px[1] / tileSize); return [x, y]; } /** * Convert a tile x-y-z to a bbox of the form `[w, s, e, n]` * @param tile - the tile * @param tileSize - in pixels * @returns - the bbox */ export function tilePxBounds(tile, tileSize = 512) { const [, x, y] = tile; const minX = x * tileSize; const minY = y * tileSize; const maxX = minX + tileSize; const maxY = minY + tileSize; return [minX, minY, maxX, maxY]; } /** * Convert a lat-lon and zoom to the tile's x-y coordinates * @param ll - the lat-lon * @param zoom - the zoom * @param tileSize - in pixels * @returns - the tile x-y */ export function llToTile(ll, zoom, tileSize = 512) { const px = llToPX(ll, zoom, false, tileSize); return pxToTile(px, tileSize); } /** * given a lon-lat and tile, find the offset in pixels * @param ll - the lon-lat * @param tile - the tile * @param tileSize - in pixels * @returns - the tile x-y */ export function llToTilePx(ll, tile, tileSize = 512) { const [zoom, x, y] = tile; const px = llToPX(ll, zoom, false, tileSize); const tileXStart = x * tileSize; const tileYStart = y * tileSize; return [(px[0] - tileXStart) / tileSize, (px[1] - tileYStart) / tileSize]; } /** * Convert a bbox of the form `[w, s, e, n]` to a bbox of the form `[w, s, e, n]` * The result can be in lon-lat (WGS84) or WebMercator (900913) * If the input is in WebMercator (900913), the outSource should be set to 'WGS84' * @param bbox - the bounding box to convert * @param outSource - the output source * @returns - the converted bbox */ export function convert(bbox, outSource) { if (outSource === 'WGS84') { const low = mercToLL([bbox[0], bbox[1]]); const high = mercToLL([bbox[2], bbox[3]]); return [low[0], low[1], high[0], high[1]]; } else { const low = llToMerc([bbox[0], bbox[1]]); const high = llToMerc([bbox[2], bbox[3]]); return [low[0], low[1], high[0], high[1]]; } } /** * Convert a tile x-y-z to a bbox of the form `[w, s, e, n]` * The result can be in lon-lat (WGS84) or WebMercator (900913) * The default result is in WebMercator (900913) * @param x - the x tile position * @param y - the y tile position * @param zoom - the zoom level * @param tmsStyle - if true, the y is inverted * @param source - the source * @param tileSize - in pixels * @returns - the bounding box in WGS84 */ export function xyzToBBOX(x, y, zoom, tmsStyle = true, source = '900913', tileSize = 512) { // Convert xyz into bbox with srs WGS84 // if tmsStyle, the y is inverted if (tmsStyle) y = Math.pow(2, zoom) - 1 - y; // Use +y to make sure it's a number to avoid inadvertent concatenation. const bl = [x * tileSize, (y + 1) * tileSize]; // bottom left // Use +x to make sure it's a number to avoid inadvertent concatenation. const tr = [(x + 1) * tileSize, y * tileSize]; // top right // to pixel-coordinates const pxBL = pxToLL(bl, zoom, tileSize); const pxTR = pxToLL(tr, zoom, tileSize); // If web mercator requested reproject to 900913. if (source === '900913') { const llBL = llToMerc(pxBL); const llTR = llToMerc(pxTR); return [llBL[0], llBL[1], llTR[0], llTR[1]]; } return [pxBL[0], pxBL[1], pxTR[0], pxTR[1]]; } /** * Convert a bbox of the form `[w, s, e, n]` to a tile's bounding box * in the form of [minX, maxX, minY, maxY] * The bbox can be in lon-lat (WGS84) or WebMercator (900913) * The default expectation is in WebMercator (900913) * @param bbox - the bounding box * @param zoom - the zoom level * @param tmsStyle - if true, the y is inverted * @param source - the source * @param tileSize - in pixels * @returns - the tile's bounding box [minX, minY, maxX, maxY] */ export function bboxToXYZBounds(bbox, zoom, tmsStyle = true, source = '900913', tileSize = 512) { const { min, max, pow, floor } = Math; let bl = [bbox[0], bbox[1]]; // bottom left let tr = [bbox[2], bbox[3]]; // top right if (source === '900913') { bl = llToMerc(bl); tr = llToMerc(tr); } const pxBL = llToPX(bl, zoom, false, tileSize); const pxTR = llToPX(tr, zoom, false, tileSize); // Y = 0 for XYZ is the top hence minY uses pxTR[1]. const x = [floor(pxBL[0] / tileSize), floor((pxTR[0] - 1) / tileSize)]; const y = [floor(pxTR[1] / tileSize), floor((pxBL[1] - 1) / tileSize)]; const bounds = [ min(...x) < 0 ? 0 : min(...x), min(...y) < 0 ? 0 : min(...y), max(...x), max(...y), ]; if (tmsStyle) { const tmsMinY = pow(2, zoom) - 1 - bounds[3]; const tmsMaxY = pow(2, zoom) - 1 - bounds[1]; bounds[1] = tmsMinY; bounds[3] = tmsMaxY; } return bounds; } /** * The circumference at a line of latitude in meters. * @param latitude - in degrees * @returns - the circumference */ function circumferenceAtLatitude(latitude) { return EARTH_CIRCUMFERENCE * Math.cos((latitude * Math.PI) / 180); } /** * Convert longitude to mercator projection X-Value * @param lng - in degrees * @returns the X-Value */ export function mercatorXfromLng(lng) { return (180 + lng) / 360; } /** * Convert latitude to mercator projection Y-Value * @param lat - in degrees * @returns the Y-Value */ export function mercatorYfromLat(lat) { const { PI, log, tan } = Math; return (180 - (180 / PI) * log(tan(PI / 4 + (lat * PI) / 360))) / 360; } /** * Convert altitude to mercator projection Z-Value * @param altitude - in meters * @param lat - in degrees * @returns the Z-Value */ export function mercatorZfromAltitude(altitude, lat) { return altitude / circumferenceAtLatitude(lat); } /** * Convert mercator projection's X-Value to longitude * @param x - in radians * @returns the longitude */ export function lngFromMercatorX(x) { return x * 360 - 180; } /** * Convert mercator projection's Y-Value to latitude * @param y - in radians * @returns the latitude */ export function latFromMercatorY(y) { const { PI, atan, exp } = Math; const y2 = 180 - y * 360; return (360 / PI) * atan(exp((y2 * PI) / 180)) - 90; } /** * Convert mercator projection's Z-Value to altitude * @param z - in meters * @param y - in radians * @returns the altitude */ export function altitudeFromMercatorZ(z, y) { return z * circumferenceAtLatitude(latFromMercatorY(y)); } /** * Determine the Mercator scale factor for a given latitude, see * https://en.wikipedia.org/wiki/Mercator_projection#Scale_factor * * At the equator the scale factor will be 1, which increases at higher latitudes. * @param lat - in degrees * @returns the scale factor */ export function mercatorLatScale(lat) { const { cos, PI } = Math; return 1 / cos((lat * PI) / 180); } //# sourceMappingURL=coords.js.map