## # Encodes and decodes locations from Morton-coded "quad tile" strings. Each # variable-length string encodes to a precision of one pixel per tile (roughly, # since this computation is done in lat/lon coordinates, not mercator). # 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 # URL-friendly. 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!("@", "~") 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 y <<= 1 y |= 1 unless (t & 32).zero? t <<= 1 end z += 3 end 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 << "-" } str end 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 end end