var SphericalMercator = (function(){ // Closures including constants and other precalculated values. var cache = {}, EPSLN = 1.0e-10, D2R = Math.PI / 180, R2D = 180 / Math.PI, // 900913 properties. A = 6378137, MAXEXTENT = 20037508.34; // SphericalMercator constructor: precaches calculations // for fast tile lookups. function SphericalMercator(options) { options = options || {}; this.size = options.size || 256; if (!cache[this.size]) { var size = this.size; var c = cache[this.size] = {}; c.Bc = []; c.Cc = []; c.zc = []; c.Ac = []; for (var d = 0; d < 30; d++) { c.Bc.push(size / 360); c.Cc.push(size / (2 * Math.PI)); c.zc.push(size / 2); c.Ac.push(size); size *= 2; } } this.Bc = cache[this.size].Bc; this.Cc = cache[this.size].Cc; this.zc = cache[this.size].zc; this.Ac = cache[this.size].Ac; }; // Convert lon lat to screen pixel value // // - `ll` {Array} `[lon, lat]` array of geographic coordinates. // - `zoom` {Number} zoom level. SphericalMercator.prototype.px = function(ll, zoom) { var d = this.zc[zoom]; var f = Math.min(Math.max(Math.sin(D2R * ll[1]), -0.9999), 0.9999); var x = Math.round(d + ll[0] * this.Bc[zoom]); var y = Math.round(d + 0.5 * Math.log((1 + f) / (1 - f)) * (-this.Cc[zoom])); (x > this.Ac[zoom]) && (x = this.Ac[zoom]); (y > this.Ac[zoom]) && (y = this.Ac[zoom]); //(x < 0) && (x = 0); //(y < 0) && (y = 0); return [x, y]; }; // Convert screen pixel value to lon lat // // - `px` {Array} `[x, y]` array of geographic coordinates. // - `zoom` {Number} zoom level. SphericalMercator.prototype.ll = function(px, zoom) { var g = (px[1] - this.zc[zoom]) / (-this.Cc[zoom]); var lon = (px[0] - this.zc[zoom]) / this.Bc[zoom]; var lat = R2D * (2 * Math.atan(Math.exp(g)) - 0.5 * Math.PI); return [lon, lat]; }; // Convert tile xyz value to bbox of the form `[w, s, e, n]` // // - `x` {Number} x (longitude) number. // - `y` {Number} y (latitude) number. // - `zoom` {Number} zoom. // - `tms_style` {Boolean} whether to compute using tms-style. // - `srs` {String} projection for resulting bbox (WGS84|900913). // - `return` {Array} bbox array of values in form `[w, s, e, n]`. SphericalMercator.prototype.bbox = function(x, y, zoom, tms_style, srs) { // Convert xyz into bbox with srs WGS84 if (tms_style) { y = (Math.pow(2, zoom) - 1) - y; } // Use +y to make sure it's a number to avoid inadvertent concatenation. var ll = [x * this.size, (+y + 1) * this.size]; // lower left // Use +x to make sure it's a number to avoid inadvertent concatenation. var ur = [(+x + 1) * this.size, y * this.size]; // upper right var bbox = this.ll(ll, zoom).concat(this.ll(ur, zoom)); // If web mercator requested reproject to 900913. if (srs === '900913') { return this.convert(bbox, '900913'); } else { return bbox; } }; // Convert bbox to xyx bounds // // - `bbox` {Number} bbox in the form `[w, s, e, n]`. // - `zoom` {Number} zoom. // - `tms_style` {Boolean} whether to compute using tms-style. // - `srs` {String} projection of input bbox (WGS84|900913). // - `@return` {Object} XYZ bounds containing minX, maxX, minY, maxY properties. SphericalMercator.prototype.xyz = function(bbox, zoom, tms_style, srs) { // If web mercator provided reproject to WGS84. if (srs === '900913') { bbox = this.convert(bbox, 'WGS84'); } var ll = [bbox[0], bbox[1]]; // lower left var ur = [bbox[2], bbox[3]]; // upper right var px_ll = this.px(ll, zoom); var px_ur = this.px(ur, zoom); // Y = 0 for XYZ is the top hence minY uses px_ur[1]. var bounds = { minX: Math.floor(px_ll[0] / this.size), minY: Math.floor(px_ur[1] / this.size), maxX: Math.floor((px_ur[0] - 1) / this.size), maxY: Math.floor((px_ll[1] - 1) / this.size) }; if (tms_style) { var tms = { minY: (Math.pow(2, zoom) - 1) - bounds.maxY, maxY: (Math.pow(2, zoom) - 1) - bounds.minY }; bounds.minY = tms.minY; bounds.maxY = tms.maxY; } return bounds; }; // Convert projection of given bbox. // // - `bbox` {Number} bbox in the form `[w, s, e, n]`. // - `to` {String} projection of output bbox (WGS84|900913). Input bbox // assumed to be the "other" projection. // - `@return` {Object} bbox with reprojected coordinates. SphericalMercator.prototype.convert = function(bbox, to) { if (to === '900913') { return this.forward(bbox.slice(0, 2)).concat(this.forward(bbox.slice(2,4))); } else { return this.inverse(bbox.slice(0, 2)).concat(this.inverse(bbox.slice(2,4))); } }; // Convert lon/lat values to 900913 x/y. SphericalMercator.prototype.forward = function(ll) { var xy = [ A * ll[0] * D2R, A * Math.log(Math.tan((Math.PI*0.25) + (0.5 * ll[1] * D2R))) ]; // if xy value is beyond maxextent (e.g. poles), return maxextent. (xy[0] > MAXEXTENT) && (xy[0] = MAXEXTENT); (xy[0] < -MAXEXTENT) && (xy[0] = -MAXEXTENT); (xy[1] > MAXEXTENT) && (xy[1] = MAXEXTENT); (xy[1] < -MAXEXTENT) && (xy[1] = -MAXEXTENT); return xy; }; // Convert 900913 x/y values to lon/lat. SphericalMercator.prototype.inverse = function(xy) { return [ (xy[0] * R2D / A), ((Math.PI*0.5) - 2.0 * Math.atan(Math.exp(-xy[1] / A))) * R2D ]; }; return SphericalMercator; })(); if (typeof module !== 'undefined' && typeof exports !== 'undefined') { module.exports = exports = SphericalMercator; }