Class: PlusCodes::OpenLocationCode
- Inherits:
-
Object
- Object
- PlusCodes::OpenLocationCode
- Defined in:
- lib/plus_codes/open_location_code.rb
Overview
- OpenLocationCode
-
implements the Google Open Location Code(Plus+Codes) algorithm.
Instance Method Summary collapse
-
#decode(code) ⇒ CodeArea
Decodes the given plus+codes in to a [CodeArea].
-
#encode(latitude, longitude, code_length = PAIR_CODE_LENGTH) ⇒ String
Encodes given latitude and longitude with the optionally provided code length.
-
#full?(code) ⇒ TrueClass, FalseClass
Checks if the given plus+codes is in full format.
-
#recover_nearest(short_code, reference_latitude, reference_longitude) ⇒ String
Finds the full plus+codes from given short plus+codes, reference latitude and longitude.
-
#short?(code) ⇒ TrueClass, FalseClass
Checks if the given plus+codes is in short format.
-
#shorten(code, latitude, longitude) ⇒ String
Shortens the given full plus+codes by provided reference latitude and longitude.
-
#valid?(code) ⇒ TrueClass, FalseClass
Validates the given plus+codes.
Instance Method Details
#decode(code) ⇒ CodeArea
Decodes the given plus+codes in to a [CodeArea].
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/plus_codes/open_location_code.rb', line 109 def decode(code) raise ArgumentError, "Passed Open Location Code is not a valid full code: #{code}" unless full?(code) # Strip out separator character (we've already established the code is # valid so the maximum is one), padding characters and convert to upper # case. code = code.gsub(SEPARATOR, '') code = code.gsub(/#{PADDING}+/, '') code = code.upcase # Decode the lat/lng pair component. code_area = decode_pairs(code[0...[code.length, PAIR_CODE_LENGTH].min]) # If there is a grid refinement component, decode that. return code_area if code.length <= PAIR_CODE_LENGTH grid_area = decode_grid(code[PAIR_CODE_LENGTH..-1]) CodeArea.new(code_area.latitude_lo + grid_area.latitude_lo, code_area.longitude_lo + grid_area.longitude_lo, code_area.latitude_lo + grid_area.latitude_hi, code_area.longitude_lo + grid_area.longitude_hi, code_area.code_length + grid_area.code_length) end |
#encode(latitude, longitude, code_length = PAIR_CODE_LENGTH) ⇒ String
Encodes given latitude and longitude with the optionally provided code length.
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/plus_codes/open_location_code.rb', line 87 def encode(latitude, longitude, code_length = PAIR_CODE_LENGTH) if code_length < 2 || (code_length < SEPARATOR_POSITION && code_length.odd?) raise ArgumentError, "Invalid Open Location Code length: #{code_length}" end latitude = clip_latitude(latitude) longitude = normalize_longitude(longitude) if latitude == 90 latitude = latitude - compute_latitude_precision(code_length).to_f end code = encode_pairs(latitude, longitude, [code_length, PAIR_CODE_LENGTH].min) # If the requested length indicates we want grid refined codes. if code_length > PAIR_CODE_LENGTH code += encode_grid(latitude, longitude, code_length - PAIR_CODE_LENGTH) end code end |
#full?(code) ⇒ TrueClass, FalseClass
Checks if the given plus+codes is in full format.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/plus_codes/open_location_code.rb', line 63 def full?(code) return false unless valid?(code) # If it's short, it's not full. return false if short?(code) # Work out what the first latitude character indicates for latitude. first_lat_value = DECODE[code[0].ord] * ENCODING_BASE # The code would decode to a latitude of >= 90 degrees. return false if first_lat_value >= LATITUDE_MAX * 2 if code.length > 1 # Work out what the first longitude character indicates for longitude. first_lng_value = DECODE[code[1].ord] * ENCODING_BASE # The code would decode to a longitude of >= 180 degrees. return false if first_lng_value >= LONGITUDE_MAX * 2 end true end |
#recover_nearest(short_code, reference_latitude, reference_longitude) ⇒ String
Finds the full plus+codes from given short plus+codes, reference latitude and longitude.
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/plus_codes/open_location_code.rb', line 138 def recover_nearest(short_code, reference_latitude, reference_longitude) unless short?(short_code) if full?(short_code) return short_code else raise ArgumentError, "ValueError: Passed short code is not valid: #{short_code}" end end # Ensure that latitude and longitude are valid. reference_latitude = clip_latitude(reference_latitude) reference_longitude = normalize_longitude(reference_longitude) # Clean up the passed code. short_code = short_code.upcase # Compute the number of digits we need to recover. padding_length = SEPARATOR_POSITION - short_code.index(SEPARATOR) # The resolution (height and width) of the padded area in degrees. resolution = 20 ** (2 - (padding_length / 2)) # Distance from the center to an edge (in degrees). area_to_edge = resolution / 2.0 # Now round down the reference latitude and longitude to the resolution. rounded_latitude = (reference_latitude / resolution).floor * resolution rounded_longitude = (reference_longitude / resolution).floor * resolution # Use the reference location to pad the supplied short code and decode it. code_area = decode( encode(rounded_latitude, rounded_longitude).slice(0, padding_length) + short_code) # How many degrees latitude is the code from the reference? If it is more # than half the resolution, we need to move it east or west. degrees_difference = code_area.latitude_center - reference_latitude if degrees_difference > area_to_edge # If the center of the short code is more than half a cell east, # then the best match will be one position west. code_area.latitude_center -= resolution elsif degrees_difference < -area_to_edge # If the center of the short code is more than half a cell west, # then the best match will be one position east. code_area.latitude_center += resolution end # How many degrees longitude is the code from the reference? degrees_difference = code_area.longitude_center - reference_longitude if degrees_difference > area_to_edge code_area.longitude_center -= resolution elsif degrees_difference < -area_to_edge code_area.longitude_center += resolution end encode(code_area.latitude_center, code_area.longitude_center, code_area.code_length) end |
#short?(code) ⇒ TrueClass, FalseClass
Checks if the given plus+codes is in short format.
53 54 55 56 57 |
# File 'lib/plus_codes/open_location_code.rb', line 53 def short?(code) return false unless valid?(code) # If there are less characters than expected before the SEPARATOR. code.index(SEPARATOR) >= 0 && code.index(SEPARATOR) < SEPARATOR_POSITION end |
#shorten(code, latitude, longitude) ⇒ String
Shortens the given full plus+codes by provided reference latitude and longitude.
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/plus_codes/open_location_code.rb', line 197 def shorten(code, latitude, longitude) raise ArgumentError, "ValueError: Passed code is not valid and full: #{code}" unless full?(code) raise ArgumentError, "ValueError: Cannot shorten padded codes: #{code}" unless code.index(PADDING).nil? code = code.upcase code_area = decode(code) if code_area.code_length < MIN_TRIMMABLE_CODE_LEN raise RangeError, "ValueError: Code length must be at least #{MIN_TRIMMABLE_CODE_LEN}" end # Ensure that latitude and longitude are valid. latitude = clip_latitude(latitude) longitude = normalize_longitude(longitude) # How close are the latitude and longitude to the code center. range = [(code_area.latitude_center - latitude).abs, (code_area.longitude_center - longitude).abs].max i = PAIR_RESOLUTIONS.length - 2 while i >= 1 do # Check if we're close enough to shorten. The range must be less than 1/2 # the resolution to shorten at all, and we want to allow some safety, so # use 0.3 instead of 0.5 as a multiplier. return code[(i + 1) * 2..-1] if range < (PAIR_RESOLUTIONS[i] * 0.3) # Trim it. i -= 1 end code end |
#valid?(code) ⇒ TrueClass, FalseClass
Validates the given plus+codes.
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/plus_codes/open_location_code.rb', line 15 def valid?(code) return false if code.nil? || code.length <= 1 separator_index = code.index(SEPARATOR) # There must be a single separator at an even index and position should be < SEPARATOR_POSITION. return false if separator_index.nil? || separator_index != code.rindex(SEPARATOR) || separator_index > SEPARATOR_POSITION || separator_index.odd? # We can have an even number of padding characters before the separator, # but then it must be the final character. if code.include?(PADDING) return false if code.start_with?(PADDING) pad_match = code.scan(/#{PADDING}+/) return false unless pad_match.one? padding = pad_match[0] return false if padding.length.odd? return false if padding.length > SEPARATOR_POSITION - 2 return false if code[-2] != PADDING return false if code[-1] != SEPARATOR end # If there are characters after the separator, make sure there isn't just # one of them (not legal). return false if code.length - separator_index - 1 == 1 # Check code contains only valid characters. code.chars.each do |ch| return false if ch.ord >= DECODE.length || DECODE[ch.ord] < -1 end true end |