class SphericalMercator

Constants

A

900913 properties.

D2R
EPSLN

Closures including constants and other precalculated values.

MAX_EXTENT
R2D

Attributes

ac[RW]
bc[RW]
cache[RW]
cc[RW]
options[R]
round[RW]
size[RW]
zc[RW]

Public Class Methods

gem_version() click to toggle source
# File lib/spherical_mercator/version.rb, line 2
def self.gem_version
  Gem::Version.new VERSION::STRING
end
new(opts = {}) click to toggle source

SphericalMercator constructor: precaches calculations for fast tile lookups.

# File lib/spherical_mercator.rb, line 23
def initialize(opts = {})
  @cache = {}
  @options = opts || {}

  self.size = (options[:size] || 256).to_f
  # Whether to round output values for integer zoom levels. Defaults to true.
  self.round = (options[:round].nil?) ? true : options[:round]

  if cache[size].nil?
    size = self.size
    c = cache[self.size] = {}
    c['Bc'] = []
    c['Cc'] = []
    c['zc'] = []
    c['Ac'] = []

    30.times do
      c['Ac'].push(size.to_i)
      c['Bc'].push(size / 360)
      c['Cc'].push(size / (2 * Math::PI))
      c['zc'].push((size / 2).to_i)

      size *= 2
    end
  end

  @ac = cache[self.size]['Ac']
  @bc = cache[self.size]['Bc']
  @cc = cache[self.size]['Cc']
  @zc = cache[self.size]['zc']
end

Public Instance Methods

bbox(x, y, zoom, tms_style = false, srs = 'WGS84') click to toggle source

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]`.

# File lib/spherical_mercator.rb, line 127
def bbox(x, y, zoom, tms_style = false, srs = 'WGS84')
  # Convert xyz into bbox with srs WGS84
  y = ((2**zoom) - 1) - y if tms_style

  lower_left = [x * @size, (y + 1) * @size] # lower left
  upper_right = [(x + 1) * @size, y * @size] # upper right

  bbox = ll(lower_left, zoom).concat(ll(upper_right, zoom))

  # If web mercator requested reproject to 900913.
  if srs == '900913'
    convert(bbox, '900913')
  else
    bbox
  end
end
convert(bbox, to = 'WGS84') click to toggle source

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.

# File lib/spherical_mercator.rb, line 190
def convert(bbox, to = 'WGS84')
  if to == '900913'
    forward(bbox.slice(0, 2)).concat(forward(bbox.slice(2, 4)))
  else
    inverse(bbox.slice(0, 2)).concat(inverse(bbox.slice(2, 4)))
  end
end
float?(value) click to toggle source

function isFloat(n)

# File lib/spherical_mercator.rb, line 56
def float?(value)
  number = Float(value)
  number % 1 != 0
rescue ArgumentError
  false
end
forward(lon_lat) click to toggle source

Convert lon/lat values to 900913 x/y.

# File lib/spherical_mercator.rb, line 199
def forward(lon_lat)
  xy = [
    A * lon_lat[0] * D2R,
    A * Math.log(Math.tan((Math::PI * 0.25) + (0.5 * lon_lat[1] * D2R)))
  ]

  # if xy value is beyond maxextent (e.g. poles), return maxextent.
  (xy[0] > MAX_EXTENT) && (xy[0] = MAX_EXTENT)
  (xy[0] < -MAX_EXTENT) && (xy[0] = -MAX_EXTENT)
  (xy[1] > MAX_EXTENT) && (xy[1] = MAX_EXTENT)
  (xy[1] < -MAX_EXTENT) && (xy[1] = -MAX_EXTENT)
  xy
end
inverse(xy) click to toggle source

Convert 900913 x/y values to lon/lat.

# File lib/spherical_mercator.rb, line 214
def inverse(xy)
  [
    (xy[0] * R2D / A),
    ((Math::PI * 0.5) - 2.0 * Math.atan(Math.exp(-xy[1] / A))) * R2D
  ]
end
ll(px, zoom) click to toggle source

Convert screen pixel value to lon lat

  • `px` {Array} `[x, y]` array of geographic coordinates.

  • `zoom` {Number} zoom level.

# File lib/spherical_mercator.rb, line 101
def ll(px, zoom)
  if float?(zoom)
    size = @size * (2**zoom)
    bc = (size / 360)
    cc = (size / (2 * Math::PI))
    zc = size / 2
    g = (px[1] - zc) / -cc
    lon = (px[0] - zc) / bc
    lat = R2D * (2 * Math.atan(Math.exp(g)) - 0.5 * Math::PI)
    [lon, lat]
  else
    g = (px[1] - @zc[zoom]) / (-@cc[zoom])
    lon = (px[0] - @zc[zoom]) / @bc[zoom]
    lat = R2D * (2 * Math.atan(Math.exp(g)) - 0.5 * Math::PI)
    [lon, lat]
  end
end
px(lon_lat, zoom) click to toggle source

Convert lon lat to screen pixel value

  • `ll` {Array} `[lon, lat]` array of geographic coordinates.

  • `zoom` {Number} zoom level.

# File lib/spherical_mercator.rb, line 67
def px(lon_lat, zoom)
  if float?(zoom) || !self.round
    size = @size * (2**zoom)
    d = size / 2
    bc = (size / 360)
    cc = (size / (2 * Math::PI))
    ac = size
    f = [[Math.sin(D2R * lon_lat[1]), -0.9999].max, 0.9999].min
    x = d + lon_lat[0] * bc
    y = d + 0.5 * Math.log((1 + f) / (1 - f)) * -cc
    (x > ac) && (x = ac)
    (y > ac) && (y = ac)
    # (x < 0) && (x = 0)
    # (y < 0) && (y = 0)
    [x, y]
  else
    d = @zc[zoom]
    # JS: Math.min(Math.max(Math.sin(D2R * ll[1]), -0.9999), 0.9999)
    f = [[Math.sin(D2R * lon_lat[1]), -0.9999].max, 0.9999].min
    x = (d + lon_lat[0] * @bc[zoom]).round
    y = (d + 0.5 * Math.log((1 + f) / (1 - f)) * (-@cc[zoom])).round
    (x > @ac[zoom]) && (x = @ac[zoom])
    (y > @ac[zoom]) && (y = @ac[zoom])

    # (x < 0) && (x = 0)
    # (y < 0) && (y = 0)
    [x, y]
  end
end
xyz(bbox, zoom, tms_style = false, srs = 'WGS84') click to toggle source

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

# File lib/spherical_mercator.rb, line 151
def xyz(bbox, zoom, tms_style = false, srs = 'WGS84')
  # If web mercator provided reproject to WGS84.
  bbox = convert(bbox, 'WGS84') if srs == '900913'

  lower_left = [bbox[0], bbox[1]] # lower left
  upper_right = [bbox[2], bbox[3]] # upper right
  px_ll = px(lower_left, zoom)
  px_ur = px(upper_right, zoom)

  # Y = 0 for XYZ is the top hence minY uses px_ur[1].
  x = [(px_ll[0] / @size).floor, ((px_ur[0] - 1) / @size).floor]
  y = [(px_ur[1] / @size).floor, ((px_ll[1] - 1) / @size).floor]

  bounds = {
    minX: x.min < 0 ? 0 : x.min, # JS: Math.min.apply(Math, x)
    minY: y.min < 0 ? 0 : y.min,
    maxX: x.max,
    maxY: y.max
  }

  if tms_style
    tms = {
      minY: ((2**zoom) - 1) - bounds[:maxY],
      maxY: ((2**zoom) - 1) - bounds[:minY]
    }

    bounds[:minY] = tms[:minY]
    bounds[:maxY] = tms[:maxY]
  end

  bounds
end