UNPKG

global-mercator

Version:
734 lines 21.7 kB
"use strict"; const Debug = require('debug'); const lodash_1 = require('lodash'); exports.debug = { error: Debug('global-mercator:error'), log: Debug('global-mercator:log'), warning: Debug('global-mercator:warning'), }; /** * Converts given lat/lon in WGS84 Datum to XY in Spherical Mercator EPSG:900913 * * @name latLngToMeters * @param {number} lat * @param {number} lng * @param {number} zoom (Optional) * @returns {Meters} * @example * latLngToMeters({lat: 45, lng: 90}) * //=> Meters { mx: 10018754.171394622, my: 5621521.486192067 } */ function latLngToMeters(init) { return mercator.latLngToMeters(init); } exports.latLngToMeters = latLngToMeters; /** * Converts XY point from Spherical Mercator EPSG:900913 to lat/lng in WGS84 Datum * * @name metersToLatLng * @param {number} mx * @param {number} my * @param {number} zoom (Optional) * @returns {LatLng} * @example * metersToLatLng({mx: 10000000, my: 5500000}) * //=> LatLng { lat: 44.2228902348575, lng: 89.83152841195214 } */ function metersToLatLng(init) { return mercator.metersToLatLng(init); } exports.metersToLatLng = metersToLatLng; /** * Converts EPSG:900913 to pyramid pixel coordinates in given zoom level * * @name metersToPixels * @param {number} mx * @param {number} my * @param {number} zoom * @returns {Pixels} * @example * metersToPixels({mx: 10000000, my: 5500000, zoom: 13}) * //=> Pixels { px: 1571882.5818671728, py: 1336394.6200269451, zoom: 13 } */ function metersToPixels(init) { return mercator.metersToPixels(init); } exports.metersToPixels = metersToPixels; /** * Returns Tile for given latlng coordinates * * @name latLngToTile * @param {number} lat * @param {number} lng * @param {number} zoom * @returns {Tile} * @example * latLngToTile({lat: 45, lng: 90, zoom: 5}) * //=> Tile { tx: 23, ty: 20, zoom: 5 } */ function latLngToTile(init) { return mercator.latLngToTile(init); } exports.latLngToTile = latLngToTile; /** * Returns Google Tile for given latlng coordinates * * @name latLngToGoogle * @param {number} lat * @param {number} lng * @returns {Google} Google Tile * @example * latLngToGoogle({lat: 45, lng: 90, zoom: 5}) * //=> Google { x: 23, y: 11, zoom: 5 } */ function latLngToGoogle(init) { return mercator.latLngToGoogle(init); } exports.latLngToGoogle = latLngToGoogle; /** * Returns Tile for given mercator coordinates * * @name metersToTile * @param {number} mx * @param {number} my * @returns {Tile} * @example * metersToTile({mx: 10000000, my: 5500000, zoom: 5}) * //=> Tile { tx: 23, ty: 20, zoom: 5 } */ function metersToTile(init) { return mercator.metersToTile(init); } exports.metersToTile = metersToTile; /** * Converts pixel coordinates in given zoom level of pyramid to EPSG:900913 * * @name pixelsToMeters * @param {number} px * @param {number} py * @param {number} zoom * @returns {Meters} */ function pixelsToMeters(init) { return mercator.pixelsToMeters(init); } exports.pixelsToMeters = pixelsToMeters; /** * Returns a Tile covering region in given pixel coordinates * * @name pixelsToTile * @param {number} px * @param {number} py * @param {number} zoom * @returns {Tile} */ function pixelsToTile(init) { return mercator.pixelsToTile(init); } exports.pixelsToTile = pixelsToTile; /** * Returns bounds of the given Tile in EPSG:900913 coordinates * * @name tileBounds * @param {number} tx * @param {number} ty * @param {number} zoom * @returns {Array<number>} bbox extent in [minX, minY, maxX, maxY] order */ function tileBounds(init) { return mercator.tileBounds(init); } exports.tileBounds = tileBounds; /** * Returns bounds of the given Tile in EPSG:900913 coordinates * * @name tileLatLonBounds * @param {number} tx * @param {number} ty * @param {number} zoom * @returns {Array<number>} bbox extent in [minX, minY, maxX, maxY] order */ function tileLatLonBounds(init) { return mercator.tileLatLonBounds(init); } exports.tileLatLonBounds = tileLatLonBounds; /** * Converts Google Tile system in Mercator bounds (Meters) * * @name googleBounds * @param {number} x * @param {number} y * @param {number} zoom * @returns {Array<number>} bbox extent in [minX, minY, maxX, maxY] order */ function googleBounds(init) { return mercator.googleBounds(init); } exports.googleBounds = googleBounds; /** * Converts Google Tile system in LatLng bounds (degrees) * * @name googleLatLonBounds * @param {number} x * @param {number} y * @param {number} zoom * @returns {Array<number>} bbox extent in [minX, minY, maxX, maxY] order */ function googleLatLonBounds(init) { return mercator.googleLatLonBounds(init); } exports.googleLatLonBounds = googleLatLonBounds; /** * Converts TMS Tile coordinates to Google Tile coordinates * * @name tileGoogle * @param {number} tx * @param {number} ty * @param {number} zoom * @returns {Array<number>} bbox extent in [minX, minY, maxX, maxY] order */ function tileGoogle(init) { return mercator.tileGoogle(init); } exports.tileGoogle = tileGoogle; /** * Converts Google Tile coordinates to TMS Tile coordinates * * @name googleTile * @param {number} x * @param {number} y * @param {number} zoom * @returns {Tile} */ function googleTile(init) { return mercator.googleTile(init); } exports.googleTile = googleTile; /** * Converts Google Tile coordinates to Microsoft QuadKey * * @name googleQuadKey * @param {number} x * @param {number} y * @param {number} zoom * @returns {quadkey} */ function googleQuadKey(init) { return mercator.googleQuadKey(init); } exports.googleQuadKey = googleQuadKey; /** * Converts TMS Tile coordinates to Microsoft QuadKey * * @name tileQuadKey * @param {number} tx * @param {number} ty * @param {number} zoom * @returns {quadkey} */ function tileQuadKey(init) { return mercator.tileQuadKey(init); } exports.tileQuadKey = tileQuadKey; /** * Converts QuadKey to TMS Tile coordinates * * @name quadKeyTile * @param {string} quadkey * @returns {Tile} */ function quadKeyTile(quadkey) { return mercator.quadKeyTile(quadkey); } exports.quadKeyTile = quadKeyTile; /** * Converts QuadKey to Google Tile * * @name quadKeyGoogle * @param {string} quadkey * @returns {Google} */ function quadKeyGoogle(quadkey) { return mercator.quadKeyGoogle(quadkey); } exports.quadKeyGoogle = quadKeyGoogle; /** * Converts bounds from LatLng to Meters * * @name boundsLatLngToMeters * @param {Array<number>} bbox extent in [minX, minY, maxX, maxY] order * @returns {Array<number>} bounds */ function boundsLatLngToMeters(bounds) { return mercator.boundsLatLngToMeters(bounds); } exports.boundsLatLngToMeters = boundsLatLngToMeters; /** * Validate Undefined * @name validateUndefined * @param {string} name * @param {Object} items * @example * validateUndefined('Meters', Object) */ function validateUndefined(items, name) { for (let key of lodash_1.keys(items)) { if (lodash_1.isUndefined(items[key])) { const message = (name) ? `${name} <${key}> is required` : `<${key}> is required`; exports.debug.error(message); throw new Error(message); } } } exports.validateUndefined = validateUndefined; /** * Validates Tile * @name validateTile * @example * const tile = validateTile({tx: 60, ty: 80, zoom: 5}) * //= {tx: 60, ty: 80, zoom: 5} */ function validateTile(init, name = 'Tile') { const { tx, ty, zoom } = init; validateZoom(zoom, 'Tile'); if (tx < 0) { const message = `${name} <tx> must not be less than 0`; exports.debug.error(message); throw new Error(message); } else if (ty < 0) { const message = `${name} <ty> must not be less than 0`; exports.debug.error(message); throw new Error(message); } return init; } exports.validateTile = validateTile; /** * Validates Zoom * @name validateZoom * @example * const zoom = validateZoom(12) * //= 12 */ function validateZoom(zoom, name) { if (zoom < 1) { const message = (name) ? `${name} <zoom> cannot be less than 1` : '<zoom> cannot be less than 1'; exports.debug.error(message); throw new Error(message); } else if (zoom > 23) { const message = (name) ? `${name} <zoom> cannot be greater than 23` : '<zoom> cannot be greater than 23'; exports.debug.error(message); throw new Error(message); } return zoom; } exports.validateZoom = validateZoom; /** * Validates Pixels * @name validatePixels * @example * const pixels = validatePixels([-115, 44]) * //= [-115, 44] */ function validatePixels(init) { if (init.length < 2 || init.length >= 3) { const message = 'Pixels must be an Array of 2 numbers'; exports.debug.error(message); throw new Error(message); } let [px, py] = init; if (px % 1 !== 0) { px = px - px % 1; const message = `Pixels [px] has been modified to ${px}`; exports.debug.warning(message); } if (py % 1 !== 0) { py = py - py % 1; const message = `Pixels [py] has been modified to ${py}`; exports.debug.warning(message); } return [px, py]; } exports.validatePixels = validatePixels; /** * Validates Meters * @name validateMeters * @example * const meters = validateMeters([-115, 44]) * //= [-115, 44] */ function validateMeters(init) { if (init.length < 2 || init.length >= 3) { const message = 'Meters must be an Array of 2 numbers'; exports.debug.error(message); throw new Error(message); } const max = 20037508.342789244; const min = -20037508.342789244; let [mx, my] = init; if (my > max) { const message = `Meters [my] cannot be greater than ${max}`; exports.debug.error(message); throw new Error(message); } if (my < min) { const message = `Meters [my] cannot be less than ${min}`; exports.debug.error(message); throw new Error(message); } if (mx > max) { const message = `Meters [mx] cannot be greater than ${max}`; exports.debug.error(message); throw new Error(message); } if (mx < min) { const message = `Meters [mx] cannot be less than ${min}`; exports.debug.error(message); throw new Error(message); } return [mx, my]; } exports.validateMeters = validateMeters; /** * Validates LatLng * @name validateLatLng * @example * validateLatLng([-115, 44]) * //= [-115, 44] */ function validateLatLng(init) { const [lng, lat] = validateLngLat([init[1], init[0]]); return [lat, lng]; } exports.validateLatLng = validateLatLng; /** * Validates LngLat * @name validateLngLat * @example * validateLngLat([-115, 44]) * //= [-115, 44] */ function validateLngLat(init) { if (init.length < 2 || init.length >= 3) { const message = 'LatLng must be an Array of 2 numbers'; exports.debug.error(message); throw new Error(message); } let [lng, lat] = init; if (lat < -90 || lat > 90) { const message = 'LatLng [lat] must be within -90 to 90 degrees'; exports.debug.error(message); throw new Error(message); } else if (lng < -180 || lng > 180) { const message = 'LatLng [lng] must be within -180 to 180 degrees'; exports.debug.error(message); throw new Error(message); } if (lat > 85) { const message = 'LatLng [lat] has been modified to 85'; exports.debug.warning(message); lat = 85; } if (lat < -85) { const message = 'LatLng [lat] has been modified to -85'; exports.debug.warning(message); lat = -85; } return [lng, lat]; } exports.validateLngLat = validateLngLat; /** * Validates bounds * @name bounds * @example * const bounds = validateBounds([ -75, 44, -74, 45 ]) * //= [ -75, 44, -74, 45 ] */ function validateBounds(init) { if (init.length !== 4) { const message = '[bounds] must be an Array of 4 numbers'; exports.debug.error(message); throw new Error(message); } return [...init]; } exports.validateBounds = validateBounds; class Google { constructor(init) { const { x, y, zoom } = init; this.x = x; this.y = y; this.zoom = zoom; validateUndefined(this, 'Google'); } } exports.Google = Google; class Tile { constructor(init) { const { tx, ty, zoom } = init; this.tx = tx; this.ty = ty; this.zoom = zoom; validateUndefined(this, 'Tile'); validateTile(this); } } exports.Tile = Tile; class Pixels { constructor(init) { const { px, py, zoom } = init; this.px = px; this.py = py; if (!lodash_1.isUndefined(zoom)) { this.zoom = zoom; } validateUndefined(this, 'Pixels'); validatePixels([px, py]); } } exports.Pixels = Pixels; class Meters { constructor(init) { const { mx, my, zoom } = init; this.mx = mx; this.my = my; if (!lodash_1.isUndefined(zoom)) { this.zoom = zoom; } validateUndefined(this, 'Meters'); validateMeters([mx, my]); } } exports.Meters = Meters; class LatLng { constructor(init) { const { lng, lat } = init; this.lat = lat; this.lng = lng; if (!lodash_1.isUndefined(init.zoom)) { this.zoom = init.zoom; } validateUndefined(this, 'LatLng'); validateLatLng([lat, lng]); } } exports.LatLng = LatLng; class GlobalMercator { constructor(TileSize = 256) { this.name = 'GlobalMercator'; this.boundsLatLngToMeters = (bounds) => { const min = this.latLngToMeters({ lat: bounds[1], lng: bounds[0] }); const max = this.latLngToMeters({ lat: bounds[3], lng: bounds[2] }); return [min.mx, min.my, max.mx, max.my]; }; this.TileSize = TileSize; this.initialResolution = 2 * Math.PI * 6378137 / this.TileSize; this.originShift = 2 * Math.PI * 6378137 / 2.0; } Resolution(zoom) { if (lodash_1.isUndefined(zoom)) { const message = '<zoom> is required'; exports.debug.error(message); throw new Error(message); } return this.initialResolution / Math.pow(2, zoom); } latLngToMeters(init) { const { lat, lng, zoom } = new LatLng(init); let mx = lng * this.originShift / 180.0; let my = Math.log(Math.tan((90 + lat) * Math.PI / 360.0)) / (Math.PI / 180.0); my = my * this.originShift / 180.0; return new Meters({ mx: mx, my: my, zoom: zoom }); } metersToLatLng(init) { const { mx, my, zoom } = new Meters(init); let lng = (mx / this.originShift) * 180.0; let lat = (my / this.originShift) * 180.0; lat = 180 / Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180.0)) - Math.PI / 2.0); return new LatLng({ lat: lat, lng: lng, zoom: zoom }); } metersToPixels(init) { const { mx, my, zoom } = new Meters(init); const res = this.Resolution(zoom); const px = (mx + this.originShift) / res; const py = (my + this.originShift) / res; return new Pixels({ px: px, py: py, zoom: zoom }); } latLngToTile(init) { const meters = this.latLngToMeters(init); const pixels = this.metersToPixels(meters); return this.pixelsToTile(pixels); } latLngToGoogle(init) { if (init.zoom === 0) { return new Google({ x: 0, y: 0, zoom: 0 }); } const tile = this.latLngToTile(init); return this.tileGoogle(tile); } metersToTile(init) { if (init.zoom === 0) { return new Tile({ tx: 0, ty: 0, zoom: 0 }); } const Pixels = this.metersToPixels(new Meters(init)); return this.pixelsToTile(Pixels); } pixelsToMeters(init) { const { px, py, zoom } = new Pixels(init); const res = this.Resolution(zoom); const mx = px * res - this.originShift; const my = py * res - this.originShift; return new Meters({ mx: mx, my: my, zoom: zoom }); } pixelsToTile(init) { if (init.zoom === 0) { return new Tile({ tx: 0, ty: 0, zoom: 0 }); } const { px, py, zoom } = new Pixels(init); let tx = Math.ceil(px / this.TileSize) - 1; let ty = Math.ceil(py / this.TileSize) - 1; if (tx < 0) { tx = 0; } if (ty < 0) { ty = 0; } return new Tile({ tx: tx, ty: ty, zoom: zoom }); } tileBounds(init) { const { tx, ty, zoom } = new Tile(init); let min = this.pixelsToMeters({ px: tx * this.TileSize, py: ty * this.TileSize, zoom: zoom }); let max = this.pixelsToMeters({ px: (tx + 1) * this.TileSize, py: (ty + 1) * this.TileSize, zoom: zoom }); return validateBounds([min.mx, min.my, max.mx, max.my]); } tileLatLonBounds(init) { if (init.zoom === 0) { return [-180, -85.05112877980659, 180, 85.05112877980659]; } const { tx, ty, zoom } = new Tile(init); const [mx1, my1, mx2, my2] = this.tileBounds({ tx: tx, ty: ty, zoom: zoom }); const min = this.metersToLatLng({ mx: mx1, my: my1, zoom: zoom }); const max = this.metersToLatLng({ mx: mx2, my: my2, zoom: zoom }); return validateBounds([min.lng, min.lat, max.lng, max.lat]); } googleBounds(init) { const Tile = this.googleTile(init); return this.tileBounds(Tile); } googleLatLonBounds(init) { const Tile = this.googleTile(init); return this.tileLatLonBounds(Tile); } tileGoogle(init) { if (init.zoom === 0) { return new Google({ x: 0, y: 0, zoom: 0 }); } const { tx, ty, zoom } = new Tile(init); const x = tx; const y = (Math.pow(2, zoom) - 1) - ty; return new Google({ x: x, y: y, zoom: zoom }); } googleTile(init) { const { x, y, zoom } = new Google(init); const tx = x; const ty = Math.pow(2, zoom) - y - 1; return new Tile({ tx: tx, ty: ty, zoom: zoom }); } googleQuadKey(init) { const Tile = this.googleTile(init); return this.tileQuadKey(Tile); } tileQuadKey(init) { // Zoom 0 does not exist for QuadKey if (init.zoom === 0) { return ''; } let { tx, ty, zoom } = new Tile(init); let quadkey = ''; ty = (Math.pow(2, zoom) - 1) - ty; lodash_1.range(zoom, 0, -1).map(i => { let digit = 0; let mask = 1 << (i - 1); if ((tx & mask) !== 0) { digit += 1; } if ((ty & mask) !== 0) { digit += 2; } quadkey = quadkey.concat(digit); }); return quadkey; } quadKeyTile(quadkey) { const Google = this.quadKeyGoogle(quadkey); return this.googleTile(Google); } quadKeyGoogle(quadkey) { let x = 0; let y = 0; const zoom = quadkey.length; lodash_1.range(zoom, 0, -1).map(i => { let mask = 1 << (i - 1); switch (parseInt(quadkey[zoom - i], 0)) { case 0: break; case 1: x += mask; break; case 2: y += mask; break; case 3: x += mask; y += mask; break; default: throw new Error('Invalid QuadKey digit sequence'); } }); return new Google({ x: x, y: y, zoom: zoom }); } } const mercator = new GlobalMercator(); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = { metersToPixels: metersToPixels, metersToLatLng: metersToLatLng, metersToTile: metersToTile, pixelsToTile: pixelsToTile, pixelsToMeters: pixelsToMeters, latLngToMeters: latLngToMeters, latLngToGoogle: latLngToGoogle, tileBounds: tileBounds, tileLatLonBounds: tileLatLonBounds, tileGoogle: tileGoogle, tileQuadKey: tileQuadKey, quadKeyGoogle: quadKeyGoogle, quadKeyTile: quadKeyTile, googleBounds: googleBounds, googleLatLonBounds: googleLatLonBounds, googleQuadKey: googleQuadKey, }; /* istanbul ignore next */ if (require.main === module) { // const bounds = boundsLatLngToMeters([ -75.01464843750001, 44.99588261816546, -74.97070312499999, 45.02695045318546 ]) // console.log(bounds) // const meters = pixelsToMeters({ px: 611669, py: 1342753, zoom: 13 }) // console.log(meters) // console.log(metersToPixels(meters)) // console.log(metersToLatLng(meters)) // gdalwarp -of GTiff -te -8581121.501851652 -1353354.7654779512 -8575634.45283096 -1349909.177990999 lima_imagery.mbtiles lima_imagery.tif // console.log(validateLatLng([-120, 45, 1])) // validateMeters([200000, 999150000]) // validateMeters([200000, 150000, 1]) // validatePixels([200000, 150000, 1]) // validateUndefined({x: null}) // console.log(metersToLatLng({mx: 10018754.171394622, my: 5621521.486192067})) // console.log(metersToPixels({mx: 10000000, my: 5500000, zoom: 13})) // console.log(metersToPixels({mx: 3000, my: 4000})) console.log(latLngToMeters({ lat: 23, lng: 23 })); } //# sourceMappingURL=index.js.map