X-Git-Url: https://git.openstreetmap.org/rails.git/blobdiff_plain/1d8e66016c4cdf465d06198cfbbfe76613ed3bfc..b207cf78a3e2263c59ae2b43837c2fbe5990d3a7:/lib/short_link.rb diff --git a/lib/short_link.rb b/lib/short_link.rb index afcf1ef37..424e85c10 100644 --- a/lib/short_link.rb +++ b/lib/short_link.rb @@ -5,75 +5,85 @@ # Each character encodes 3 bits of x and 3 of y, so there are extra characters # tacked on the end to make the zoom levels "work". module ShortLink - # array of 64 chars to encode 6 bits. this is almost like base64 encoding, but - # the symbolic chars are different, as base64's + and / aren't very + # the symbolic chars are different, as base64's + and / aren't very # URL-friendly. - ARRAY = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a + ['_','@'] + ARRAY = ("A".."Z").to_a + ("a".."z").to_a + ("0".."9").to_a + ["_", "~"] + + class << self + ## + # Given a string encoding a location, returns the [lon, lat, z] tuple of that + # location. + def decode(str) + x = 0 + y = 0 + z = 0 + z_offset = 0 + + # keep support for old shortlinks which use the @ character, now + # replaced by the ~ character because twitter is horribly broken + # and we can't have that. + str.tr!("@", "~") - ## - # Given a string encoding a location, returns the [lon, lat, z] tuple of that - # location. - def self.decode(str) - x = 0 - y = 0 - z = 0 - z_offset = 0 + str.each_char do |c| + t = ARRAY.index c + if t.nil? + z_offset -= 1 + else + 3.times do + x <<= 1 + x |= 1 unless (t & 32).zero? + t <<= 1 - str.each_char do |c| - t = ARRAY.index c - if t.nil? - z_offset -= 1 - else - 3.times do - x <<= 1; x = x | 1 unless (t & 32).zero?; t <<= 1 - y <<= 1; y = y | 1 unless (t & 32).zero?; t <<= 1 + y <<= 1 + y |= 1 unless (t & 32).zero? + t <<= 1 + end + z += 3 end - z += 3 end + # pack the coordinates out to their original 32 bits. + x <<= (32 - z) + y <<= (32 - z) + + # project the parameters back to their coordinate ranges. + [(x * 360.0 / (2**32)) - 180.0, + (y * 180.0 / (2**32)) - 90.0, + z - 8 - (z_offset % 3)] end - # pack the coordinates out to their original 32 bits. - x <<= (32 - z) - y <<= (32 - z) - # project the parameters back to their coordinate ranges. - [(x * 360.0 / 2**32) - 180.0, - (y * 180.0 / 2**32) - 90.0, - z - 8 - (z_offset % 3)] - end + ## + # given a location and zoom, return a short string representing it. + def encode(lon, lat, z) + code = interleave_bits(((lon + 180.0) * (2**32) / 360.0).to_i, + ((lat + 90.0) * (2**32) / 180.0).to_i) + str = "" + # add eight to the zoom level, which approximates an accuracy of + # one pixel in a tile. + ((z + 8) / 3.0).ceil.times do |i| + digit = (code >> (58 - (6 * i))) & 0x3f + str << ARRAY[digit] + end + # append characters onto the end of the string to represent + # partial zoom levels (characters themselves have a granularity + # of 3 zoom levels). + ((z + 8) % 3).times { str << "-" } - ## - # given a location and zoom, return a short string representing it. - def self.encode(lon, lat, z) - code = interleave_bits(((lon + 180.0) * 2**32 / 360.0).to_i, - ((lat + 90.0) * 2**32 / 180.0).to_i) - str = "" - # add eight to the zoom level, which approximates an accuracy of - # one pixel in a tile. - ((z + 8)/3.0).ceil.times do |i| - digit = (code >> (58 - 6 * i)) & 0x3f - str << ARRAY[digit] + str end - # append characters onto the end of the string to represent - # partial zoom levels (characters themselves have a granularity - # of 3 zoom levels). - ((z + 8) % 3).times { str << "=" } - - return str - end - private - - ## - # interleaves the bits of two 32-bit numbers. the result is known - # as a Morton code. - def self.interleave_bits(x, y) - c = 0 - 31.downto(0) do |i| - c = (c << 1) | ((x >> i) & 1) - c = (c << 1) | ((y >> i) & 1) + private + + ## + # interleaves the bits of two 32-bit numbers. the result is known + # as a Morton code. + def interleave_bits(x, y) + c = 0 + 31.downto(0) do |i| + c = (c << 1) | ((x >> i) & 1) + c = (c << 1) | ((y >> i) & 1) + end + c end - c end - end