global-mercator
Version:
734 lines • 21.7 kB
JavaScript
"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