open-location-code 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/plus_codes.rb +26 -0
- data/lib/plus_codes/code_area.rb +38 -0
- data/lib/plus_codes/open_location_code.rb +264 -0
- data/test/plus_codes_test.rb +90 -0
- metadata +119 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 378b33bb5f5bdf0ec82c286db7e7b54b762f2723
|
4
|
+
data.tar.gz: b2f0ded83b4ed9c914a0220c177ae6bb3e43adf5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 052d9fb0aeeab2fd7969ae25b00824813e9f63b4877d7e73d5a5aa75d5d51c17fc1eeb51482c1f08f8cfa3d50ca3793ca2e44263c225c9244b9e5fa541f94c51
|
7
|
+
data.tar.gz: 34f7c50ad9dd924e7afaa441ab3cdefe80b32de655b97cdb17f5c06d3c698738855e5c80af8d3c08f1cdce82bb494e23b22580933893d8a77cfb686fe4fa5f47
|
data/lib/plus_codes.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# Plus+Codes is a Ruby implementation of Google Open Location Code(Plus+Codes).
|
2
|
+
#
|
3
|
+
# @author We-Ming Wu
|
4
|
+
module PlusCodes
|
5
|
+
|
6
|
+
# A separator used to separate the code into two parts.
|
7
|
+
SEPARATOR = '+'.freeze
|
8
|
+
|
9
|
+
# The max number of characters can be placed before the separator.
|
10
|
+
SEPARATOR_POSITION = 8
|
11
|
+
|
12
|
+
# The character used to pad a code
|
13
|
+
PADDING = '0'.freeze
|
14
|
+
|
15
|
+
# The character set used to encode coordinates.
|
16
|
+
CODE_ALPHABET = '23456789CFGHJMPQRVWX'.freeze
|
17
|
+
|
18
|
+
# ASCII lookup table.
|
19
|
+
DECODE = (CODE_ALPHABET.chars + [PADDING, SEPARATOR]).reduce([]) do |ary, c|
|
20
|
+
ary[c.ord] = CODE_ALPHABET.index(c)
|
21
|
+
ary[c.downcase.ord] = CODE_ALPHABET.index(c)
|
22
|
+
ary[c.ord] ||= -1
|
23
|
+
ary
|
24
|
+
end.freeze
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module PlusCodes
|
2
|
+
|
3
|
+
# [CodeArea] contains coordinates of a decoded Open Location Code(Plus+Codes).
|
4
|
+
# The coordinates include the latitude and longitude of the lower left and
|
5
|
+
# upper right corners and the center of the bounding box for the area the
|
6
|
+
# code represents.
|
7
|
+
#
|
8
|
+
# @author We-Ming Wu
|
9
|
+
class CodeArea
|
10
|
+
attr_accessor :south_latitude, :west_longitude, :latitude_height,
|
11
|
+
:longitude_width, :latitude_center, :longitude_center
|
12
|
+
|
13
|
+
# Creates a [CodeArea].
|
14
|
+
#
|
15
|
+
# @param south_latitude [Numeric] the latitude of the SW corner in degrees
|
16
|
+
# @param west_longitude [Numeric] the longitude of the SW corner in degrees
|
17
|
+
# @param latitude_height [Numeric] the height from the SW corner in degrees
|
18
|
+
# @param longitude_width [Numeric] the width from the SW corner in degrees
|
19
|
+
# @return [CodeArea] a code area which contains the coordinates
|
20
|
+
def initialize(south_latitude, west_longitude, latitude_height, longitude_width)
|
21
|
+
@south_latitude = south_latitude
|
22
|
+
@west_longitude = west_longitude
|
23
|
+
@latitude_height = latitude_height
|
24
|
+
@longitude_width = longitude_width
|
25
|
+
@latitude_center = south_latitude + latitude_height / 2.0
|
26
|
+
@longitude_center = west_longitude + longitude_width / 2.0
|
27
|
+
end
|
28
|
+
|
29
|
+
def north_latitude
|
30
|
+
@south_latitude + @latitude_height
|
31
|
+
end
|
32
|
+
|
33
|
+
def east_longitude
|
34
|
+
@west_longitude + @longitude_width
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,264 @@
|
|
1
|
+
require_relative '../plus_codes'
|
2
|
+
require_relative '../plus_codes/code_area'
|
3
|
+
|
4
|
+
module PlusCodes
|
5
|
+
|
6
|
+
# [OpenLocationCode] implements the Google Open Location Code(Plus+Codes) algorithm.
|
7
|
+
#
|
8
|
+
# @author We-Ming Wu
|
9
|
+
class OpenLocationCode
|
10
|
+
|
11
|
+
# Determines if a string is a valid sequence of Open Location Code(Plus+Codes) characters.
|
12
|
+
#
|
13
|
+
# @param code [String] a plus+codes
|
14
|
+
# @return [TrueClass, FalseClass] true if the code is valid, false otherwise
|
15
|
+
def valid?(code)
|
16
|
+
valid_length?(code) &&
|
17
|
+
valid_separator?(code) &&
|
18
|
+
valid_padding?(code) &&
|
19
|
+
valid_character?(code)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Determines if a string is a valid short Open Location Code(Plus+Codes).
|
23
|
+
#
|
24
|
+
# @param code [String] a plus+codes
|
25
|
+
# @return [TrueClass, FalseClass] true if the code is short, false otherwise
|
26
|
+
def short?(code)
|
27
|
+
valid?(code) && code.index(SEPARATOR) < SEPARATOR_POSITION
|
28
|
+
end
|
29
|
+
|
30
|
+
# Determines if a string is a valid full Open Location Code(Plus+Codes).
|
31
|
+
#
|
32
|
+
# @param code [String] a plus+codes
|
33
|
+
# @return [TrueClass, FalseClass] true if the code is full, false otherwise
|
34
|
+
def full?(code)
|
35
|
+
valid?(code) && !short?(code)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Converts a latitude and longitude into a Open Location Code(Plus+Codes).
|
39
|
+
#
|
40
|
+
# @param latitude [Numeric] a latitude in degrees
|
41
|
+
# @param longitude [Numeric] a longitude in degrees
|
42
|
+
# @param code_length [Integer] the number of characters in the code, this excludes the separator
|
43
|
+
# @return [String] a plus+codes
|
44
|
+
def encode(latitude, longitude, code_length = 10)
|
45
|
+
raise ArgumentError,
|
46
|
+
"Invalid Open Location Code(Plus+Codes) length: #{code_length}" if invalid_length?(code_length)
|
47
|
+
|
48
|
+
latitude = clip_latitude(latitude)
|
49
|
+
longitude = normalize_longitude(longitude)
|
50
|
+
latitude -= precision_by_length(code_length) if latitude == 90
|
51
|
+
|
52
|
+
lat = (latitude + 90).to_r
|
53
|
+
lng = (longitude + 180).to_r
|
54
|
+
|
55
|
+
digit = 0
|
56
|
+
code = ''
|
57
|
+
while digit < code_length
|
58
|
+
lat, lng = narrow_region(digit, lat, lng)
|
59
|
+
digit, lat, lng = build_code(digit, code, lat, lng)
|
60
|
+
code << SEPARATOR if (digit == SEPARATOR_POSITION)
|
61
|
+
end
|
62
|
+
|
63
|
+
digit < SEPARATOR_POSITION ? padded(code) : code
|
64
|
+
end
|
65
|
+
|
66
|
+
# Decodes an Open Location Code(Plus+Codes) into a [CodeArea].
|
67
|
+
#
|
68
|
+
# @param code [String] a plus+codes
|
69
|
+
# @return [CodeArea] a code area which contains the coordinates
|
70
|
+
def decode(code)
|
71
|
+
raise ArgumentError,
|
72
|
+
"Open Location Code(Plus+Codes) is not a valid full code: #{code}" unless full?(code)
|
73
|
+
|
74
|
+
code = code.gsub(SEPARATOR, '')
|
75
|
+
code = code.gsub(/#{PADDING}+/, '')
|
76
|
+
code = code.upcase
|
77
|
+
|
78
|
+
south_latitude = -90.0
|
79
|
+
west_longitude = -180.0
|
80
|
+
|
81
|
+
lat_resolution = 400.to_r
|
82
|
+
lng_resolution = 400.to_r
|
83
|
+
|
84
|
+
digit = 0
|
85
|
+
while digit < code.length
|
86
|
+
if digit < 10
|
87
|
+
lat_resolution /= 20
|
88
|
+
lng_resolution /= 20
|
89
|
+
south_latitude += lat_resolution * DECODE[code[digit].ord]
|
90
|
+
west_longitude += lng_resolution * DECODE[code[digit + 1].ord]
|
91
|
+
digit += 2
|
92
|
+
else
|
93
|
+
lat_resolution /= 5
|
94
|
+
lng_resolution /= 4
|
95
|
+
row = DECODE[code[digit].ord] / 4
|
96
|
+
column = DECODE[code[digit].ord] % 4
|
97
|
+
south_latitude += lat_resolution * row
|
98
|
+
west_longitude += lng_resolution * column
|
99
|
+
digit += 1
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
CodeArea.new(south_latitude, west_longitude, lat_resolution, lng_resolution)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Recovers a full Open Location Code(Plus+Codes) from a short code and a reference location.
|
107
|
+
#
|
108
|
+
# @param short_code [String] a plus+codes
|
109
|
+
# @param reference_latitude [Numeric] a reference latitude in degrees
|
110
|
+
# @param reference_longitude [Numeric] a reference longitude in degrees
|
111
|
+
# @return [String] a plus+codes
|
112
|
+
def recover_nearest(short_code, reference_latitude, reference_longitude)
|
113
|
+
return short_code if full?(short_code)
|
114
|
+
raise ArgumentError,
|
115
|
+
"Open Location Code(Plus+Codes) is not valid: #{short_code}" unless short?(short_code)
|
116
|
+
|
117
|
+
ref_lat = clip_latitude(reference_latitude)
|
118
|
+
ref_lng = normalize_longitude(reference_longitude)
|
119
|
+
|
120
|
+
prefix_len = SEPARATOR_POSITION - short_code.index(SEPARATOR)
|
121
|
+
code = prefix_by_reference(ref_lat, ref_lng, prefix_len) << short_code
|
122
|
+
code_area = decode(code)
|
123
|
+
|
124
|
+
area_range = precision_by_length(prefix_len)
|
125
|
+
area_edge = area_range / 2
|
126
|
+
|
127
|
+
latitude = code_area.latitude_center
|
128
|
+
latitude_diff = latitude - ref_lat
|
129
|
+
if (latitude_diff > area_edge)
|
130
|
+
latitude -= area_range
|
131
|
+
elsif (latitude_diff < -area_edge)
|
132
|
+
latitude += area_range
|
133
|
+
end
|
134
|
+
|
135
|
+
longitude = code_area.longitude_center
|
136
|
+
longitude_diff = longitude - ref_lng
|
137
|
+
if (longitude_diff > area_edge)
|
138
|
+
longitude -= area_range
|
139
|
+
elsif (longitude_diff < -area_edge)
|
140
|
+
longitude += area_range
|
141
|
+
end
|
142
|
+
|
143
|
+
encode(latitude, longitude, code.length - SEPARATOR.length)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Removes four, six or eight digits from the front of an Open Location Code(Plus+Codes) given a reference location.
|
147
|
+
#
|
148
|
+
# @param code [String] a plus+codes
|
149
|
+
# @param latitude [Numeric] a latitude in degrees
|
150
|
+
# @param longitude [Numeric] a longitude in degrees
|
151
|
+
# @return [String] a short plus+codes
|
152
|
+
def shorten(code, latitude, longitude)
|
153
|
+
raise ArgumentError,
|
154
|
+
"Open Location Code(Plus+Codes) is a valid full code: #{code}" unless full?(code)
|
155
|
+
raise ArgumentError,
|
156
|
+
"Cannot shorten padded codes: #{code}" unless code.index(PADDING).nil?
|
157
|
+
|
158
|
+
code_area = decode(code)
|
159
|
+
lat_diff = (latitude - code_area.latitude_center).abs
|
160
|
+
lng_diff = (longitude - code_area.longitude_center).abs
|
161
|
+
max_diff = [lat_diff, lng_diff].max
|
162
|
+
[8, 6, 4].each do |removal_len|
|
163
|
+
area_edge = precision_by_length(removal_len + 2) / 2
|
164
|
+
return code[removal_len..-1] if max_diff < area_edge
|
165
|
+
end
|
166
|
+
|
167
|
+
code.upcase
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
def prefix_by_reference(latitude, longitude, prefix_len)
|
173
|
+
precision = precision_by_length(prefix_len)
|
174
|
+
rounded_latitude = (latitude / precision).floor * precision
|
175
|
+
rounded_longitude = (longitude / precision).floor * precision
|
176
|
+
encode(rounded_latitude, rounded_longitude)[0...prefix_len]
|
177
|
+
end
|
178
|
+
|
179
|
+
def narrow_region(digit, latitude, longitude)
|
180
|
+
if digit == 0
|
181
|
+
latitude /= 20
|
182
|
+
longitude /= 20
|
183
|
+
elsif digit < 10
|
184
|
+
latitude *= 20
|
185
|
+
longitude *= 20
|
186
|
+
else
|
187
|
+
latitude *= 5
|
188
|
+
longitude *= 4
|
189
|
+
end
|
190
|
+
[latitude, longitude]
|
191
|
+
end
|
192
|
+
|
193
|
+
def build_code(digit_count, code, latitude, longitude)
|
194
|
+
lat_digit = latitude.to_i
|
195
|
+
lng_digit = longitude.to_i
|
196
|
+
if digit_count < 10
|
197
|
+
code << CODE_ALPHABET[lat_digit]
|
198
|
+
code << CODE_ALPHABET[lng_digit]
|
199
|
+
[digit_count + 2, latitude - lat_digit, longitude - lng_digit]
|
200
|
+
else
|
201
|
+
code << CODE_ALPHABET[4 * lat_digit + lng_digit]
|
202
|
+
[digit_count + 1, latitude - lat_digit, longitude - lng_digit]
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def valid_length?(code)
|
207
|
+
!code.nil? && code.length >= 2 + SEPARATOR.length && code.split(SEPARATOR).last.length != 1
|
208
|
+
end
|
209
|
+
|
210
|
+
def valid_separator?(code)
|
211
|
+
separator_idx = code.index(SEPARATOR)
|
212
|
+
code.count(SEPARATOR) == 1 && separator_idx <= SEPARATOR_POSITION && separator_idx.even?
|
213
|
+
end
|
214
|
+
|
215
|
+
def valid_padding?(code)
|
216
|
+
if code.include?(PADDING)
|
217
|
+
return false if code.start_with?(PADDING)
|
218
|
+
return false if code[-2..-1] != PADDING + SEPARATOR
|
219
|
+
|
220
|
+
paddings = code.scan(/#{PADDING}+/)
|
221
|
+
return false if !paddings.one? || paddings[0].length.odd?
|
222
|
+
return false if paddings[0].length > SEPARATOR_POSITION - 2
|
223
|
+
end
|
224
|
+
true
|
225
|
+
end
|
226
|
+
|
227
|
+
def valid_character?(code)
|
228
|
+
code.chars.each { |ch| return false if DECODE[ch.ord].nil? }
|
229
|
+
true
|
230
|
+
end
|
231
|
+
|
232
|
+
def invalid_length?(code_length)
|
233
|
+
code_length < 2 || (code_length < SEPARATOR_POSITION && code_length.odd?)
|
234
|
+
end
|
235
|
+
|
236
|
+
def padded(code)
|
237
|
+
code << PADDING * (SEPARATOR_POSITION - code.length) << SEPARATOR
|
238
|
+
end
|
239
|
+
|
240
|
+
def precision_by_length(code_length)
|
241
|
+
if code_length <= 10
|
242
|
+
precision = 20 ** ((code_length / -2).to_i + 2)
|
243
|
+
else
|
244
|
+
precision = (20 ** -3) / (5 ** (code_length - 10))
|
245
|
+
end
|
246
|
+
precision.to_r
|
247
|
+
end
|
248
|
+
|
249
|
+
def clip_latitude(latitude)
|
250
|
+
[90.0, [-90.0, latitude].max].min
|
251
|
+
end
|
252
|
+
|
253
|
+
def normalize_longitude(longitude)
|
254
|
+
until longitude < 180
|
255
|
+
longitude -= 360
|
256
|
+
end
|
257
|
+
until longitude >= -180
|
258
|
+
longitude += 360
|
259
|
+
end
|
260
|
+
longitude
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require_relative '../lib/plus_codes/open_location_code'
|
3
|
+
|
4
|
+
class PlusCodesTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@test_data_folder_path = File.join(File.dirname(__FILE__), '..', '..', 'test_data')
|
8
|
+
@olc = PlusCodes::OpenLocationCode.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_validity
|
12
|
+
read_csv_lines('validityTests.csv').each do |line|
|
13
|
+
cols = line.split(',')
|
14
|
+
code = cols[0]
|
15
|
+
is_valid = cols[1] == 'true'
|
16
|
+
is_short = cols[2] == 'true'
|
17
|
+
is_full = cols[3] == 'true'
|
18
|
+
is_valid_olc = @olc.valid?(code)
|
19
|
+
is_short_olc = @olc.short?(code)
|
20
|
+
is_full_olc = @olc.full?(code)
|
21
|
+
result = is_valid_olc == is_valid && is_short_olc == is_short && is_full_olc == is_full
|
22
|
+
assert_true(result)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_encode_decode
|
27
|
+
read_csv_lines('encodingTests.csv').each do |line|
|
28
|
+
cols = line.split(',')
|
29
|
+
code_area = @olc.decode(cols[0])
|
30
|
+
if cols[0].index('0')
|
31
|
+
code = @olc.encode(cols[1].to_f, cols[2].to_f, cols[0].index('0'))
|
32
|
+
else
|
33
|
+
code = @olc.encode(cols[1].to_f, cols[2].to_f, cols[0].length - 1)
|
34
|
+
end
|
35
|
+
assert_equal(cols[0], code)
|
36
|
+
assert_true((code_area.south_latitude - cols[3].to_f).abs < 0.001)
|
37
|
+
assert_true((code_area.west_longitude - cols[4].to_f).abs < 0.001)
|
38
|
+
assert_true((code_area.north_latitude - cols[5].to_f).abs < 0.001)
|
39
|
+
assert_true((code_area.east_longitude - cols[6].to_f).abs < 0.001)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_shorten
|
44
|
+
read_csv_lines('shortCodeTests.csv').each do |line|
|
45
|
+
cols = line.split(',')
|
46
|
+
code = cols[0]
|
47
|
+
lat = cols[1].to_f
|
48
|
+
lng = cols[2].to_f
|
49
|
+
short_code = cols[3]
|
50
|
+
short = @olc.shorten(code, lat, lng)
|
51
|
+
assert_equal(short_code, short)
|
52
|
+
expanded = @olc.recover_nearest(short, lat, lng)
|
53
|
+
assert_equal(code, expanded)
|
54
|
+
end
|
55
|
+
@olc.shorten('9C3W9QCJ+2VX', 60.3701125, 10.202665625)
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_longer_encoding_with_special_case
|
59
|
+
assert_equal('CFX3X2X2+X2RRRRJ', @olc.encode(90.0, 1.0, 15));
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_exceptions
|
63
|
+
assert_raise ArgumentError do
|
64
|
+
@olc.encode(20, 30, 1)
|
65
|
+
end
|
66
|
+
assert_raise ArgumentError do
|
67
|
+
@olc.recover_nearest('9C3W9QCJ-2VX', 51.3708675, -1.217765625)
|
68
|
+
end
|
69
|
+
@olc.recover_nearest('9C3W9QCJ+2VX', 51.3708675, -1.217765625)
|
70
|
+
assert_raise ArgumentError do
|
71
|
+
@olc.decode('sfdg')
|
72
|
+
end
|
73
|
+
assert_raise ArgumentError do
|
74
|
+
@olc.shorten('9C3W9Q+', 1, 2)
|
75
|
+
end
|
76
|
+
assert_raise ArgumentError do
|
77
|
+
@olc.shorten('9C3W9Q00+', 1, 2)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_valid_with_special_case
|
82
|
+
assert_false(@olc.valid?('3W00CJJJ+'))
|
83
|
+
end
|
84
|
+
|
85
|
+
def read_csv_lines(csv_file)
|
86
|
+
f = File.open(File.join(@test_data_folder_path, csv_file), 'r')
|
87
|
+
f.each_line.lazy.select { |line| line !~ /^\s*#/ }.map { |line| line.chop }
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: open-location-code
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Wei-Ming Wu
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: test-unit
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: simplecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: yard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Ruby implementation of Google Open Location Code(Plus+Codes)
|
84
|
+
email:
|
85
|
+
- wnameless@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- lib/plus_codes.rb
|
91
|
+
- lib/plus_codes/code_area.rb
|
92
|
+
- lib/plus_codes/open_location_code.rb
|
93
|
+
- test/plus_codes_test.rb
|
94
|
+
homepage: https://github.com/google/open-location-code
|
95
|
+
licenses:
|
96
|
+
- Apache License, Version 2.0
|
97
|
+
metadata: {}
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
requirements: []
|
113
|
+
rubyforge_project:
|
114
|
+
rubygems_version: 2.4.5
|
115
|
+
signing_key:
|
116
|
+
specification_version: 4
|
117
|
+
summary: Ruby implementation of Google Open Location Code(Plus+Codes)
|
118
|
+
test_files:
|
119
|
+
- test/plus_codes_test.rb
|