]> git.openstreetmap.org Git - rails.git/blob - lib/short_link.rb
Fix rubocop lint issues
[rails.git] / lib / short_link.rb
1 ##
2 # Encodes and decodes locations from Morton-coded "quad tile" strings. Each
3 # variable-length string encodes to a precision of one pixel per tile (roughly,
4 # since this computation is done in lat/lon coordinates, not mercator).
5 # Each character encodes 3 bits of x and 3 of y, so there are extra characters
6 # tacked on the end to make the zoom levels "work".
7 module ShortLink
8   # array of 64 chars to encode 6 bits. this is almost like base64 encoding, but
9   # the symbolic chars are different, as base64's + and / aren't very
10   # URL-friendly.
11   ARRAY = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a + ['_', '~']
12
13   ##
14   # Given a string encoding a location, returns the [lon, lat, z] tuple of that
15   # location.
16   def self.decode(str)
17     x = 0
18     y = 0
19     z = 0
20     z_offset = 0
21
22     # keep support for old shortlinks which use the @ character, now
23     # replaced by the ~ character because twitter is horribly broken
24     # and we can't have that.
25     str.gsub!("@", "~")
26
27     str.each_char do |c|
28       t = ARRAY.index c
29       if t.nil?
30         z_offset -= 1
31       else
32         3.times do
33           x <<= 1; x |= 1 unless (t & 32).zero?; t <<= 1
34           y <<= 1; y |= 1 unless (t & 32).zero?; t <<= 1
35         end
36         z += 3
37       end
38     end
39     # pack the coordinates out to their original 32 bits.
40     x <<= (32 - z)
41     y <<= (32 - z)
42
43     # project the parameters back to their coordinate ranges.
44     [(x * 360.0 / 2**32) - 180.0,
45      (y * 180.0 / 2**32) - 90.0,
46      z - 8 - (z_offset % 3)]
47   end
48
49   ##
50   # given a location and zoom, return a short string representing it.
51   def self.encode(lon, lat, z)
52     code = interleave_bits(((lon + 180.0) * 2**32 / 360.0).to_i,
53                            ((lat +  90.0) * 2**32 / 180.0).to_i)
54     str = ""
55     # add eight to the zoom level, which approximates an accuracy of
56     # one pixel in a tile.
57     ((z + 8) / 3.0).ceil.times do |i|
58       digit = (code >> (58 - 6 * i)) & 0x3f
59       str << ARRAY[digit]
60     end
61     # append characters onto the end of the string to represent
62     # partial zoom levels (characters themselves have a granularity
63     # of 3 zoom levels).
64     ((z + 8) % 3).times { str << "-" }
65
66     str
67   end
68
69   private
70
71   ##
72   # interleaves the bits of two 32-bit numbers. the result is known
73   # as a Morton code.
74   def self.interleave_bits(x, y)
75     c = 0
76     31.downto(0) do |i|
77       c = (c << 1) | ((x >> i) & 1)
78       c = (c << 1) | ((y >> i) & 1)
79     end
80     c
81   end
82 end