geohash_int 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f28778ebf764e4d66db4d08634196017c0c3347f
4
+ data.tar.gz: 2bc80f85ccc6dcc219ef99cc54ba3bc4a55d5262
5
+ SHA512:
6
+ metadata.gz: 4695278863e53c7ee00b37e4cf3f6779b2fcd971c82eee630f79b319a5e12e18fee1dad57a5956fe1f0bc75f9d39478fbd831859f74632dbcb516999e66dd8dc
7
+ data.tar.gz: c90453a766b4b77a39a8db3d86ede1599289848dc5691b813c661b4d7fc443dd531e5cd7ec44b62d424975d774778bc011778c64f75edcca846319619b560c49
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
13
+
14
+ *.so
15
+ *.dylib
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.1
5
+ before_install: gem install bundler -v 1.15.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in geohash_int.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Ary Borenszweig
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,143 @@
1
+ # GeohashInt
2
+
3
+ Wraps [geohash-int](https://github.com/yinqiwen/geohash-int)
4
+ (A fast C99 geohash library which only provides int64 as hash result) for Ruby
5
+ using [FFI](https://github.com/ffi/ffi) (this means it is compatible with all
6
+ implementations of Ruby that support FFI, including MRI, JRuby, and Rubinius).
7
+
8
+ This can be used to build an efficient spatial data index, as explained
9
+ [here](https://github.com/yinqiwen/ardb/wiki/Spatial-Index).
10
+
11
+ ## Explanation
12
+
13
+ This library turns a coordinate (a latitude and a longitude) into a 64 bits
14
+ integer. To understand what this integer means we need to learn how the Geohash
15
+ algorithm works. Don't worry, it's super simple.
16
+
17
+ Suppose we want to encode the coordinate with latitude 42.6 and longitude -5.6.
18
+
19
+ Let's focus on the value 42.6. Latitudes fall in the range -90..90. We split that
20
+ range in half (-90...0, 0..90) and we check in which half the value falls. We'll
21
+ use 0 if it falls in the lower interval and 1 if it falls in the higher interval.
22
+ In this case it's 1. We then do the same but with this new interval (0..90): split
23
+ it (0...45, 45..90) and see in which half it falls. In this case it's 0.
24
+
25
+ If we stop here, we get the sequence "10".
26
+
27
+ We then do the same for the longitude, starting with the range -180..180.
28
+ If we repeat the process two times like before, we get the sequence "01".
29
+
30
+ We then **interleave** the longitude and latitude sequences and we get "0110".
31
+ Interpreting that as a 64 bits integer we get the value 6, and this is the encoded value.
32
+
33
+ To decode this value 6 we do the reverse process: de-interleave the sequence and
34
+ reconstruct the original numbers. But, in the process we'll lose precision:
35
+ by splitting the initial ranges -90..90 and -180..180 in halves, but only a couple
36
+ of times, we'll know that the original coordinate is anywhere in the range 0..45 for
37
+ latitude and -90..0 for longitude, with its center latitude 22.5 and longitude -45.0
38
+ as an estimation.
39
+
40
+ We can increase the number of steps we succesively split the original ranges to
41
+ increase the precision. For example, if we do it 10 times we get the sequence
42
+ "01101111111100000100", which is the number 458500. When decode it back, we get
43
+ (42.626953125, -5.44921875) as a result, which is closer to the original value.
44
+
45
+ The maximum number of steps we can do this for a 64 bits integer is 32,
46
+ because we use 32 bits for the latitude and 32 bits for the longitude.
47
+
48
+ ## Installation
49
+
50
+ Add this line to your application's Gemfile:
51
+
52
+ ```ruby
53
+ gem 'geohash_int'
54
+ ```
55
+
56
+ And then execute:
57
+
58
+ $ bundle
59
+
60
+ Or install it yourself as:
61
+
62
+ $ gem install geohash_int
63
+
64
+ ## Usage
65
+
66
+ To encode and decode values:
67
+
68
+ ```ruby
69
+ require "geohash_int"
70
+
71
+ latitude = 12.34
72
+ longitude = 56.78
73
+ steps = 10
74
+
75
+ value = GeohashInt.encode(latitude, longitude, steps)
76
+
77
+ value # => 825366
78
+
79
+ result = GeohashInt.decode(value, steps)
80
+
81
+ # Geohash is lossy
82
+ result.latitude # => 12.392578125
83
+ result.longitude # => 56.77734375
84
+
85
+ # Geohash actually encodes a value to a bounding box:
86
+ # the above latitude and longitude are just its center.
87
+ result.min_latitude # => 12.3046875
88
+ result.max_latitude # => 12.48046875
89
+ result.min_longitude # => 56.6015625
90
+ result.max_longitude # => 56.953125
91
+ ```
92
+
93
+ From an encoded value you get a neighbour or all neighbors:
94
+
95
+ ```ruby
96
+ require "geohash_int"
97
+
98
+ latitude = 12.34
99
+ longitude = 56.78
100
+ steps = 10
101
+
102
+ value = GeohashInt.encode(latitude, longitude, steps)
103
+
104
+ neighbor = GeohashInt.get_neighbor(value, GeohashInt::NORTH, steps)
105
+
106
+ neighbor # => 825367
107
+
108
+ neighbors = GeohashInt.get_neighbors(value, steps)
109
+
110
+ neighbors = # => #<struct GeohashInt::Neighbors
111
+ # north=825367, east=825372, west=825364, south=825363,
112
+ # south_west=825361, south_east=825369,
113
+ # north_west=825365, north_east=825373>
114
+ ```
115
+
116
+ ## Development
117
+
118
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
119
+
120
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
121
+
122
+ ## Contributing
123
+
124
+ Bug reports and pull requests are welcome on GitHub at https://github.com/citrusbyte/ruby-geohash_int.
125
+
126
+ ## License
127
+
128
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
129
+
130
+ ## About Citrusbyte
131
+
132
+ ![Citrusbyte](http://i.imgur.com/W6eISI3.png)
133
+
134
+ GeohashInt is lovingly maintained and funded by Citrusbyte.
135
+ Citrusbyte specializes in solving difficult computer science problems for startups and the enterprise.
136
+
137
+ At Citrusbyte we believe in and support open source software.
138
+ * Check out more of our open source software at Citrusbyte Labs.
139
+ * Learn more about [our work](https://citrusbyte.com/portfolio).
140
+ * [Hire us](https://citrusbyte.com/contact) to work on your project.
141
+ * [Want to join the team?](http://careers.citrusbyte.com)
142
+
143
+ *Citrusbyte and the Citrusbyte logo are trademarks or registered trademarks of Citrusbyte, LLC.*
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "geohash_int"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,24 @@
1
+ Copyright (c) 2013, yinqiwen
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+ * Redistributions of source code must retain the above copyright
7
+ notice, this list of conditions and the following disclaimer.
8
+ * Redistributions in binary form must reproduce the above copyright
9
+ notice, this list of conditions and the following disclaimer in the
10
+ documentation and/or other materials provided with the distribution.
11
+ * Neither the name of the Ardb nor the
12
+ names of its contributors may be used to endorse or promote products
13
+ derived from this software without specific prior written permission.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
19
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,33 @@
1
+ require "rbconfig"
2
+ require "ffi"
3
+
4
+ task :default => [:build, :clean]
5
+
6
+ def cmd(string)
7
+ fail "Command failed: #{string}" unless system(string)
8
+ end
9
+
10
+ task :build do
11
+ lib_name = "geohash.#{FFI::Platform::LIBSUFFIX}"
12
+
13
+ cc = ENV['CC'] || RbConfig::CONFIG['CC'] || 'cc'
14
+ cflags = "-fexceptions -O -fno-omit-frame-pointer -fno-strict-aliasing -Wall -Wextra"
15
+ ldflags = "-fexceptions"
16
+
17
+ if FFI::Platform.mac?
18
+ ldflags << " -bundle"
19
+ elsif FFI::Platform.name =~ /linux/
20
+ cflags << " -fPIC"
21
+ ldflags << " -shared -Wl,-soname,#{lib_name}"
22
+ else
23
+ cflags << " -fPIC"
24
+ ldflags << " -shared"
25
+ end
26
+
27
+ cmd "#{cc} #{cflags} -o geohash.o -c geohash.c"
28
+ cmd "#{cc} #{ldflags} -o #{lib_name} geohash.o"
29
+ end
30
+
31
+ task :clean do
32
+ cmd "rm geohash.o"
33
+ end
@@ -0,0 +1,427 @@
1
+ /*
2
+ *Copyright (c) 2013-2014, yinqiwen <yinqiwen@gmail.com>
3
+ *All rights reserved.
4
+ *
5
+ *Redistribution and use in source and binary forms, with or without
6
+ *modification, are permitted provided that the following conditions are met:
7
+ *
8
+ * * Redistributions of source code must retain the above copyright notice,
9
+ * this list of conditions and the following disclaimer.
10
+ * * Redistributions in binary form must reproduce the above copyright
11
+ * notice, this list of conditions and the following disclaimer in the
12
+ * documentation and/or other materials provided with the distribution.
13
+ * * Neither the name of Redis nor the names of its contributors may be used
14
+ * to endorse or promote products derived from this software without
15
+ * specific prior written permission.
16
+ *
17
+ *THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ *AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ *IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
+ *ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
21
+ *BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22
+ *CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23
+ *SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24
+ *INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
+ *CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26
+ *ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27
+ *THE POSSIBILITY OF SUCH DAMAGE.
28
+ */
29
+ #include <stddef.h>
30
+ #include <stdio.h>
31
+ #include <math.h>
32
+ #include "geohash.h"
33
+
34
+ /**
35
+ * Hashing works like this:
36
+ * Divide the world into 4 buckets. Label each one as such:
37
+ * -----------------
38
+ * | | |
39
+ * | | |
40
+ * | 0,1 | 1,1 |
41
+ * -----------------
42
+ * | | |
43
+ * | | |
44
+ * | 0,0 | 1,0 |
45
+ * -----------------
46
+ */
47
+
48
+ int geohash_encode(
49
+ GeoHashRange lat_range, GeoHashRange lon_range,
50
+ double latitude, double longitude, uint8_t step, GeoHashBits* hash)
51
+ {
52
+ if (NULL == hash || step > 32 || step == 0)
53
+ {
54
+ return -1;
55
+ }
56
+ hash->bits = 0;
57
+ hash->step = step;
58
+ uint8_t i = 0;
59
+ if (latitude < lat_range.min || latitude > lat_range.max
60
+ || longitude < lon_range.min || longitude > lon_range.max)
61
+ {
62
+ return -1;
63
+ }
64
+
65
+ for (; i < step; i++)
66
+ {
67
+ uint8_t lat_bit, lon_bit;
68
+ if (lat_range.max - latitude >= latitude - lat_range.min)
69
+ {
70
+ lat_bit = 0;
71
+ lat_range.max = (lat_range.max + lat_range.min) / 2;
72
+ }
73
+ else
74
+ {
75
+ lat_bit = 1;
76
+ lat_range.min = (lat_range.max + lat_range.min) / 2;
77
+ }
78
+ if (lon_range.max - longitude >= longitude - lon_range.min)
79
+ {
80
+ lon_bit = 0;
81
+ lon_range.max = (lon_range.max + lon_range.min) / 2;
82
+ }
83
+ else
84
+ {
85
+ lon_bit = 1;
86
+ lon_range.min = (lon_range.max + lon_range.min) / 2;
87
+ }
88
+ hash->bits <<= 1;
89
+ hash->bits += lon_bit;
90
+ hash->bits <<= 1;
91
+ hash->bits += lat_bit;
92
+ }
93
+ return 0;
94
+ }
95
+
96
+ static inline uint8_t get_bit(uint64_t bits, uint8_t pos)
97
+ {
98
+ return (bits >> pos) & 0x01;
99
+ }
100
+
101
+ int geohash_decode(
102
+ GeoHashRange lat_range, GeoHashRange lon_range, GeoHashBits hash, GeoHashArea* area)
103
+ {
104
+ if (NULL == area)
105
+ {
106
+ return -1;
107
+ }
108
+ area->hash = hash;
109
+ uint8_t i = 0;
110
+ area->latitude.min = lat_range.min;
111
+ area->latitude.max = lat_range.max;
112
+ area->longitude.min = lon_range.min;
113
+ area->longitude.max = lon_range.max;
114
+ for (; i < hash.step; i++)
115
+ {
116
+ uint8_t lat_bit, lon_bit;
117
+ lon_bit = get_bit(hash.bits, (hash.step - i) * 2 - 1);
118
+ lat_bit = get_bit(hash.bits, (hash.step - i) * 2 - 2);
119
+ if (lat_bit == 0)
120
+ {
121
+ area->latitude.max = (area->latitude.max + area->latitude.min) / 2;
122
+ }
123
+ else
124
+ {
125
+ area->latitude.min = (area->latitude.max + area->latitude.min) / 2;
126
+ }
127
+ if (lon_bit == 0)
128
+ {
129
+ area->longitude.max = (area->longitude.max + area->longitude.min) / 2;
130
+ }
131
+ else
132
+ {
133
+ area->longitude.min = (area->longitude.max + area->longitude.min) / 2;
134
+ }
135
+ }
136
+ return 0;
137
+ }
138
+
139
+ static inline uint64_t interleave64(uint32_t xlo, uint32_t ylo)
140
+ {
141
+ static const uint64_t B[] =
142
+ { 0x5555555555555555, 0x3333333333333333, 0x0F0F0F0F0F0F0F0F, 0x00FF00FF00FF00FF, 0x0000FFFF0000FFFF };
143
+ static const unsigned int S[] =
144
+ { 1, 2, 4, 8, 16 };
145
+
146
+ uint64_t x = xlo; // Interleave lower bits of x and y, so the bits of x
147
+ uint64_t y = ylo; // are in the even positions and bits from y in the odd; //https://graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN
148
+
149
+ // x and y must initially be less than 2**32.
150
+
151
+ x = (x | (x << S[4])) & B[4];
152
+ y = (y | (y << S[4])) & B[4];
153
+
154
+ x = (x | (x << S[3])) & B[3];
155
+ y = (y | (y << S[3])) & B[3];
156
+
157
+ x = (x | (x << S[2])) & B[2];
158
+ y = (y | (y << S[2])) & B[2];
159
+
160
+ x = (x | (x << S[1])) & B[1];
161
+ y = (y | (y << S[1])) & B[1];
162
+
163
+ x = (x | (x << S[0])) & B[0];
164
+ y = (y | (y << S[0])) & B[0];
165
+
166
+ return x | (y << 1);
167
+ }
168
+
169
+ static inline uint64_t deinterleave64(uint64_t interleaved)
170
+ {
171
+ static const uint64_t B[] =
172
+ { 0x5555555555555555, 0x3333333333333333, 0x0F0F0F0F0F0F0F0F, 0x00FF00FF00FF00FF, 0x0000FFFF0000FFFF,
173
+ 0x00000000FFFFFFFF };
174
+ static const unsigned int S[] =
175
+ { 0, 1, 2, 4, 8, 16 };
176
+
177
+ uint64_t x = interleaved; ///reverse the interleave process (http://stackoverflow.com/questions/4909263/how-to-efficiently-de-interleave-bits-inverse-morton)
178
+ uint64_t y = interleaved >> 1;
179
+
180
+ x = (x | (x >> S[0])) & B[0];
181
+ y = (y | (y >> S[0])) & B[0];
182
+
183
+ x = (x | (x >> S[1])) & B[1];
184
+ y = (y | (y >> S[1])) & B[1];
185
+
186
+ x = (x | (x >> S[2])) & B[2];
187
+ y = (y | (y >> S[2])) & B[2];
188
+
189
+ x = (x | (x >> S[3])) & B[3];
190
+ y = (y | (y >> S[3])) & B[3];
191
+
192
+ x = (x | (x >> S[4])) & B[4];
193
+ y = (y | (y >> S[4])) & B[4];
194
+
195
+ x = (x | (x >> S[5])) & B[5];
196
+ y = (y | (y >> S[5])) & B[5];
197
+
198
+ return x | (y << 32);
199
+ }
200
+
201
+ int geohash_fast_encode(
202
+ GeoHashRange lat_range, GeoHashRange lon_range, double latitude,
203
+ double longitude, uint8_t step, GeoHashBits* hash)
204
+ {
205
+ if (NULL == hash || step > 32 || step == 0)
206
+ {
207
+ return -1;
208
+ }
209
+ hash->bits = 0;
210
+ hash->step = step;
211
+ if (latitude < lat_range.min || latitude > lat_range.max
212
+ || longitude < lon_range.min || longitude > lon_range.max)
213
+ {
214
+ return -1;
215
+ }
216
+
217
+ // The algorithm computes the morton code for the geohash location within
218
+ // the range this can be done MUCH more efficiently using the following code
219
+
220
+ //compute the coordinate in the range 0-1
221
+ double lat_offset = (latitude - lat_range.min) / (lat_range.max - lat_range.min);
222
+ double lon_offset = (longitude - lon_range.min) / (lon_range.max - lon_range.min);
223
+
224
+ //convert it to fixed point based on the step size
225
+ lat_offset *= (1LL << step);
226
+ lon_offset *= (1LL << step);
227
+
228
+ uint32_t ilato = (uint32_t) lat_offset;
229
+ uint32_t ilono = (uint32_t) lon_offset;
230
+
231
+ //interleave the bits to create the morton code. No branching and no bounding
232
+ hash->bits = interleave64(ilato, ilono);
233
+ return 0;
234
+ }
235
+
236
+ int geohash_fast_decode(GeoHashRange lat_range, GeoHashRange lon_range, GeoHashBits hash, GeoHashArea* area)
237
+ {
238
+ if (NULL == area)
239
+ {
240
+ return -1;
241
+ }
242
+ area->hash = hash;
243
+ uint8_t step = hash.step;
244
+ uint64_t xyhilo = deinterleave64(hash.bits); //decode morton code
245
+
246
+ double lat_scale = lat_range.max - lat_range.min;
247
+ double lon_scale = lon_range.max - lon_range.min;
248
+
249
+ uint32_t ilato = xyhilo; //get back the original integer coordinates
250
+ uint32_t ilono = xyhilo >> 32;
251
+
252
+ //double lat_offset=ilato;
253
+ //double lon_offset=ilono;
254
+ //lat_offset /= (1<<step);
255
+ //lon_offset /= (1<<step);
256
+
257
+ //the ldexp call converts the integer to a double,then divides by 2**step to get the 0-1 coordinate, which is then multiplied times scale and added to the min to get the absolute coordinate
258
+ // area->latitude.min = lat_range.min + ldexp(ilato, -step) * lat_scale;
259
+ // area->latitude.max = lat_range.min + ldexp(ilato + 1, -step) * lat_scale;
260
+ // area->longitude.min = lon_range.min + ldexp(ilono, -step) * lon_scale;
261
+ // area->longitude.max = lon_range.min + ldexp(ilono + 1, -step) * lon_scale;
262
+
263
+ /*
264
+ * much faster than 'ldexp'
265
+ */
266
+ area->latitude.min = lat_range.min + (ilato * 1.0 / (1ull << step)) * lat_scale;
267
+ area->latitude.max = lat_range.min + ((ilato + 1) * 1.0 / (1ull << step)) * lat_scale;
268
+ area->longitude.min = lon_range.min + (ilono * 1.0 / (1ull << step)) * lon_scale;
269
+ area->longitude.max = lon_range.min + ((ilono + 1) * 1.0 / (1ull << step)) * lon_scale;
270
+
271
+ return 0;
272
+ }
273
+
274
+ static int geohash_move_x(GeoHashBits* hash, int8_t d)
275
+ {
276
+ if (d == 0)
277
+ return 0;
278
+ uint64_t x = hash->bits & 0xaaaaaaaaaaaaaaaaLL;
279
+ uint64_t y = hash->bits & 0x5555555555555555LL;
280
+
281
+ uint64_t zz = 0x5555555555555555LL >> (64 - hash->step * 2);
282
+ if (d > 0)
283
+ {
284
+ x = x + (zz + 1);
285
+ }
286
+ else
287
+ {
288
+ x = x | zz;
289
+ x = x - (zz + 1);
290
+ }
291
+ x &= (0xaaaaaaaaaaaaaaaaLL >> (64 - hash->step * 2));
292
+ hash->bits = (x | y);
293
+ return 0;
294
+ }
295
+
296
+ static int geohash_move_y(GeoHashBits* hash, int8_t d)
297
+ {
298
+ if (d == 0)
299
+ return 0;
300
+ uint64_t x = hash->bits & 0xaaaaaaaaaaaaaaaaLL;
301
+ uint64_t y = hash->bits & 0x5555555555555555LL;
302
+
303
+ uint64_t zz = 0xaaaaaaaaaaaaaaaaLL >> (64 - hash->step * 2);
304
+ if (d > 0)
305
+ {
306
+ y = y + (zz + 1);
307
+ }
308
+ else
309
+ {
310
+ y = y | zz;
311
+ y = y - (zz + 1);
312
+ }
313
+ y &= (0x5555555555555555LL >> (64 - hash->step * 2));
314
+ hash->bits = (x | y);
315
+ return 0;
316
+ }
317
+
318
+ int geohash_get_neighbors(GeoHashBits hash, GeoHashNeighbors* neighbors)
319
+ {
320
+ geohash_get_neighbor(hash, GEOHASH_NORTH, &neighbors->north);
321
+ geohash_get_neighbor(hash, GEOHASH_EAST, &neighbors->east);
322
+ geohash_get_neighbor(hash, GEOHASH_WEST, &neighbors->west);
323
+ geohash_get_neighbor(hash, GEOHASH_SOUTH, &neighbors->south);
324
+ geohash_get_neighbor(hash, GEOHASH_SOUTH_WEST, &neighbors->south_west);
325
+ geohash_get_neighbor(hash, GEOHASH_SOUTH_EAST, &neighbors->south_east);
326
+ geohash_get_neighbor(hash, GEOHASH_NORT_WEST, &neighbors->north_west);
327
+ geohash_get_neighbor(hash, GEOHASH_NORT_EAST, &neighbors->north_east);
328
+ return 0;
329
+ }
330
+
331
+ int geohash_get_neighbor(GeoHashBits hash, GeoDirection direction, GeoHashBits* neighbor)
332
+ {
333
+ if (NULL == neighbor)
334
+ {
335
+ return -1;
336
+ }
337
+ *neighbor = hash;
338
+ switch (direction)
339
+ {
340
+ case GEOHASH_NORTH:
341
+ {
342
+ geohash_move_x(neighbor, 0);
343
+ geohash_move_y(neighbor, 1);
344
+ break;
345
+ }
346
+ case GEOHASH_SOUTH:
347
+ {
348
+ geohash_move_x(neighbor, 0);
349
+ geohash_move_y(neighbor, -1);
350
+ break;
351
+ }
352
+ case GEOHASH_EAST:
353
+ {
354
+ geohash_move_x(neighbor, 1);
355
+ geohash_move_y(neighbor, 0);
356
+ break;
357
+ }
358
+ case GEOHASH_WEST:
359
+ {
360
+ geohash_move_x(neighbor, -1);
361
+ geohash_move_y(neighbor, 0);
362
+ break;
363
+ }
364
+ case GEOHASH_SOUTH_WEST:
365
+ {
366
+ geohash_move_x(neighbor, -1);
367
+ geohash_move_y(neighbor, -1);
368
+ break;
369
+ }
370
+ case GEOHASH_SOUTH_EAST:
371
+ {
372
+ geohash_move_x(neighbor, 1);
373
+ geohash_move_y(neighbor, -1);
374
+ break;
375
+ }
376
+ case GEOHASH_NORT_WEST:
377
+ {
378
+ geohash_move_x(neighbor, -1);
379
+ geohash_move_y(neighbor, 1);
380
+ break;
381
+ }
382
+ case GEOHASH_NORT_EAST:
383
+ {
384
+ geohash_move_x(neighbor, 1);
385
+ geohash_move_y(neighbor, 1);
386
+ break;
387
+ }
388
+ default:
389
+ {
390
+ return -1;
391
+ }
392
+ }
393
+ return 0;
394
+ }
395
+
396
+ GeoHashBits geohash_next_leftbottom(GeoHashBits bits)
397
+ {
398
+ GeoHashBits newbits = bits;
399
+ newbits.step++;
400
+ newbits.bits <<= 2;
401
+ return newbits;
402
+ }
403
+ GeoHashBits geohash_next_rightbottom(GeoHashBits bits)
404
+ {
405
+ GeoHashBits newbits = bits;
406
+ newbits.step++;
407
+ newbits.bits <<= 2;
408
+ newbits.bits += 2;
409
+ return newbits;
410
+ }
411
+ GeoHashBits geohash_next_lefttop(GeoHashBits bits)
412
+ {
413
+ GeoHashBits newbits = bits;
414
+ newbits.step++;
415
+ newbits.bits <<= 2;
416
+ newbits.bits += 1;
417
+ return newbits;
418
+ }
419
+
420
+ GeoHashBits geohash_next_righttop(GeoHashBits bits)
421
+ {
422
+ GeoHashBits newbits = bits;
423
+ newbits.step++;
424
+ newbits.bits <<= 2;
425
+ newbits.bits += 3;
426
+ return newbits;
427
+ }
@@ -0,0 +1,108 @@
1
+ /*
2
+ *Copyright (c) 2013-2014, yinqiwen <yinqiwen@gmail.com>
3
+ *All rights reserved.
4
+ *
5
+ *Redistribution and use in source and binary forms, with or without
6
+ *modification, are permitted provided that the following conditions are met:
7
+ *
8
+ * * Redistributions of source code must retain the above copyright notice,
9
+ * this list of conditions and the following disclaimer.
10
+ * * Redistributions in binary form must reproduce the above copyright
11
+ * notice, this list of conditions and the following disclaimer in the
12
+ * documentation and/or other materials provided with the distribution.
13
+ * * Neither the name of Redis nor the names of its contributors may be used
14
+ * to endorse or promote products derived from this software without
15
+ * specific prior written permission.
16
+ *
17
+ *THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ *AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ *IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
+ *ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
21
+ *BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22
+ *CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23
+ *SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24
+ *INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
+ *CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26
+ *ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27
+ *THE POSSIBILITY OF SUCH DAMAGE.
28
+ */
29
+
30
+ #ifndef GEOHASH_H_
31
+ #define GEOHASH_H_
32
+
33
+ #include <stdint.h>
34
+
35
+ #if defined(__cplusplus)
36
+ extern "C"
37
+ {
38
+ #endif
39
+
40
+ typedef enum
41
+ {
42
+ GEOHASH_NORTH = 0,
43
+ GEOHASH_EAST,
44
+ GEOHASH_WEST,
45
+ GEOHASH_SOUTH,
46
+ GEOHASH_SOUTH_WEST,
47
+ GEOHASH_SOUTH_EAST,
48
+ GEOHASH_NORT_WEST,
49
+ GEOHASH_NORT_EAST
50
+ } GeoDirection;
51
+
52
+ typedef struct
53
+ {
54
+ uint64_t bits;
55
+ uint8_t step;
56
+ } GeoHashBits;
57
+
58
+ typedef struct
59
+ {
60
+ double max;
61
+ double min;
62
+ } GeoHashRange;
63
+
64
+ typedef struct
65
+ {
66
+ GeoHashBits hash;
67
+ GeoHashRange latitude;
68
+ GeoHashRange longitude;
69
+ } GeoHashArea;
70
+
71
+ typedef struct
72
+ {
73
+ GeoHashBits north;
74
+ GeoHashBits east;
75
+ GeoHashBits west;
76
+ GeoHashBits south;
77
+ GeoHashBits north_east;
78
+ GeoHashBits south_east;
79
+ GeoHashBits north_west;
80
+ GeoHashBits south_west;
81
+ } GeoHashNeighbors;
82
+
83
+ /*
84
+ * 0:success
85
+ * -1:failed
86
+ */
87
+ int geohash_encode(GeoHashRange lat_range, GeoHashRange lon_range, double latitude, double longitude, uint8_t step, GeoHashBits* hash);
88
+ int geohash_decode(GeoHashRange lat_range, GeoHashRange lon_range, GeoHashBits hash, GeoHashArea* area);
89
+
90
+ /*
91
+ * Fast encode/decode version, more magic in implementation.
92
+ */
93
+ int geohash_fast_encode(GeoHashRange lat_range, GeoHashRange lon_range, double latitude, double longitude, uint8_t step, GeoHashBits* hash);
94
+ int geohash_fast_decode(GeoHashRange lat_range, GeoHashRange lon_range, GeoHashBits hash, GeoHashArea* area);
95
+
96
+ int geohash_get_neighbors(GeoHashBits hash, GeoHashNeighbors* neighbors);
97
+ int geohash_get_neighbor(GeoHashBits hash, GeoDirection direction, GeoHashBits* neighbor);
98
+
99
+ GeoHashBits geohash_next_leftbottom(GeoHashBits bits);
100
+ GeoHashBits geohash_next_rightbottom(GeoHashBits bits);
101
+ GeoHashBits geohash_next_lefttop(GeoHashBits bits);
102
+ GeoHashBits geohash_next_righttop(GeoHashBits bits);
103
+
104
+
105
+ #if defined(__cplusplus)
106
+ }
107
+ #endif
108
+ #endif /* GEOHASH_H_ */
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "geohash_int/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "geohash_int"
8
+ spec.version = GeohashInt::VERSION
9
+ spec.authors = ["Ary Borenszweig"]
10
+ spec.email = ["asterite@gmail.com"]
11
+
12
+ spec.summary = "Fast Geohash for Ruby that yields integers instead of strings"
13
+ spec.description = "Wraps geohash-int (https://github.com/yinqiwen/geohash-int, " \
14
+ "a fast C99 geohash library which only provides int64 as hash " \
15
+ "result) for Ruby using ffi"
16
+ spec.homepage = "https://github.com/citrusbyte/ruby-geohash_int"
17
+ spec.license = "MIT"
18
+
19
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
20
+ f.match(%r{^(test|spec|features)/})
21
+ end
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+ spec.extensions = ['ext/geohash_int/Rakefile']
26
+
27
+ spec.add_dependency "ffi", "~> 1.9.18"
28
+
29
+ spec.add_development_dependency "bundler", "~> 1.15"
30
+ spec.add_development_dependency "rake", "~> 10.0"
31
+ spec.add_development_dependency "rspec", "~> 3.0"
32
+ end
@@ -0,0 +1,146 @@
1
+ require "geohash_int/version"
2
+ require "geohash_int/ffi"
3
+
4
+ # The GeohashInt provides methods to encode and decode geographic
5
+ # coordinates using the geohash algorithm, but instead of returning
6
+ # a string, an integer (Fixnum) is returned.
7
+ #
8
+ # See also:
9
+ # - https://en.wikipedia.org/wiki/Geohash
10
+ # - https://github.com/yinqiwen/geohash-int
11
+ # - https://github.com/yinqiwen/ardb/wiki/Spatial-Index
12
+ module GeohashInt
13
+ module_function
14
+
15
+ LAT_RANGE = GeohashInt::FFI::Range.new.tap do |range|
16
+ range[:min] = -90.0
17
+ range[:max] = 90.0
18
+ end # :nodoc:
19
+
20
+ LNG_RANGE = GeohashInt::FFI::Range.new.tap do |range|
21
+ range[:min] = -180.0
22
+ range[:max] = 180.0
23
+ end # :nodoc:
24
+
25
+ NORTH = 0
26
+ EAST = 1
27
+ WEST = 2
28
+ SOUTH = 3
29
+ SOUTH_WEST = 4
30
+ SOUTH_EAST = 5
31
+ NORTH_WEST = 6
32
+ NORTH_EAST = 7
33
+
34
+ # The result of decoding an integer that represents an encoded coordinate.
35
+ #
36
+ # In Geohash, encoding a coordinate results in a value that, when decoded,
37
+ # returns a bounding box. The `latitude` and `longitude` values are taken
38
+ # as the middle of the bounding box.
39
+ class BoundingBox < Struct.new(
40
+ :latitude, :longitude,
41
+ :min_latitude, :max_latitude,
42
+ :min_longitude, :max_longitude,
43
+ ); end
44
+
45
+ # Neighbors of an encoded coordinate.
46
+ class Neighbors < Struct.new(
47
+ :north, :east, :west, :south,
48
+ :south_west, :south_east, :north_west, :north_east,
49
+ ); end
50
+
51
+ # Encodes a coordinate by splitting the world map the given number of steps (1..32).
52
+ #
53
+ # Returns the encoded value as an integer (Fixnum). To correctly decode this
54
+ # value back into a coordinate you must pass the same steps to #decode.
55
+ #
56
+ # To learn more about the *steps* argument, read this explanation in the Readme
57
+ # file: https://github.com/citrusbyte/ruby-geohash_int/blob/master/README.md#explanation
58
+ def encode(latitude, longitude, steps)
59
+ bits = GeohashInt::FFI::Bits.new
60
+
61
+ result = GeohashInt::FFI.geohash_fast_encode(LAT_RANGE, LNG_RANGE,
62
+ latitude, longitude,
63
+ steps, bits)
64
+ if result == 0
65
+ bits[:bits]
66
+ else
67
+ raise ArgumentError.new("Incorrect value for latitude (#{latitude}), " \
68
+ "longitude (#{longitude}) or steps (#{steps})")
69
+ end
70
+ end
71
+
72
+ # Decodes a previously encoded value. The given *steps* must be the same
73
+ # as the one given in #encode to generate *value*.
74
+ #
75
+ # Returns a BoundingBox where the original coordinate falls
76
+ # (the Geohash algorithm is lossy), where `latitude` and `longitude` of that
77
+ # bounding box are its center.
78
+ def decode(value, steps)
79
+ bits = new_bits(value, steps)
80
+ area = GeohashInt::FFI::Area.new
81
+
82
+ # This will always return 0 because the only condition for it
83
+ # returning something else is when area is NULL, but we are
84
+ # not passing NULL.
85
+ GeohashInt::FFI.geohash_fast_decode(LAT_RANGE, LNG_RANGE, bits, area)
86
+
87
+ lat_range = area[:latitude]
88
+ lng_range = area[:longitude]
89
+
90
+ lat = (lat_range[:max] + lat_range[:min]) / 2
91
+ lng = (lng_range[:max] + lng_range[:min]) / 2
92
+
93
+ BoundingBox.new(
94
+ lat, lng,
95
+ lat_range[:min], lat_range[:max],
96
+ lng_range[:min], lng_range[:max],
97
+ )
98
+ end
99
+
100
+ # Gets a neighbor of an encoded value.
101
+ #
102
+ # - *direction* must be one of this module's constants ( NORTH, EAST, etc. ).
103
+ # - *steps* must be the same as the one used in #encode to generate *value*.
104
+ def get_neighbor(value, direction, steps)
105
+ bits = new_bits(value, steps)
106
+ neighbor = GeohashInt::FFI::Bits.new
107
+
108
+ result = GeohashInt::FFI.geohash_get_neighbor(bits, direction, neighbor)
109
+ if result == 0
110
+ neighbor[:bits]
111
+ else
112
+ raise ArgumentError.new("Incorrect value for direction (#{direction})")
113
+ end
114
+ end
115
+
116
+ # Gets all neighbors of an encoded value.
117
+ #
118
+ # - *steps* must be the same as the one used in `encode` to generate *value*.
119
+ #
120
+ # Returns a Neighbors instance.
121
+ def get_neighbors(value, steps)
122
+ bits = new_bits(value, steps)
123
+ neighbors = GeohashInt::FFI::Neighbors.new
124
+
125
+ # This function never fails
126
+ GeohashInt::FFI.geohash_get_neighbors(bits, neighbors)
127
+
128
+ Neighbors.new(
129
+ neighbors[:north ][:bits],
130
+ neighbors[:east ][:bits],
131
+ neighbors[:west ][:bits],
132
+ neighbors[:south ][:bits],
133
+ neighbors[:south_west][:bits],
134
+ neighbors[:south_east][:bits],
135
+ neighbors[:north_west][:bits],
136
+ neighbors[:north_east][:bits],
137
+ )
138
+ end
139
+
140
+ private def new_bits(value, steps)
141
+ GeohashInt::FFI::Bits.new.tap do |bits|
142
+ bits[:bits] = value
143
+ bits[:step] = steps
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,42 @@
1
+ require "ffi"
2
+
3
+ module GeohashInt
4
+ # :stopdoc:
5
+ module FFI
6
+ extend ::FFI::Library
7
+ ffi_lib File.expand_path("../../../ext/geohash_int/geohash.#{::FFI::Platform::LIBSUFFIX}", __FILE__)
8
+
9
+ class Bits < ::FFI::Struct
10
+ layout :bits, :uint64,
11
+ :step, :uint8
12
+ end
13
+
14
+ class Range < ::FFI::Struct
15
+ layout :max, :double,
16
+ :min, :double
17
+ end
18
+
19
+ class Area < ::FFI::Struct
20
+ layout :hash, Bits,
21
+ :latitude, Range,
22
+ :longitude, Range
23
+ end
24
+
25
+ class Neighbors < ::FFI::Struct
26
+ layout :north, Bits,
27
+ :east, Bits,
28
+ :west, Bits,
29
+ :south, Bits,
30
+ :north_east, Bits,
31
+ :south_east, Bits,
32
+ :north_west, Bits,
33
+ :south_west, Bits
34
+ end
35
+
36
+ attach_function :geohash_fast_encode, [Range.val, Range.val, :double, :double, :uint8, Bits.ptr], :int
37
+ attach_function :geohash_fast_decode, [Range.val, Range.val, Bits.val, Area.ptr], :int
38
+
39
+ attach_function :geohash_get_neighbors, [Bits.val, Neighbors.ptr], :int
40
+ attach_function :geohash_get_neighbor, [Bits.val, :int, Bits.ptr], :int
41
+ end
42
+ end
@@ -0,0 +1,3 @@
1
+ module GeohashInt
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: geohash_int
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ary Borenszweig
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-08-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ffi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.9.18
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.9.18
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.15'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.15'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ description: Wraps geohash-int (https://github.com/yinqiwen/geohash-int, a fast C99
70
+ geohash library which only provides int64 as hash result) for Ruby using ffi
71
+ email:
72
+ - asterite@gmail.com
73
+ executables: []
74
+ extensions:
75
+ - ext/geohash_int/Rakefile
76
+ extra_rdoc_files: []
77
+ files:
78
+ - ".gitignore"
79
+ - ".rspec"
80
+ - ".travis.yml"
81
+ - Gemfile
82
+ - LICENSE.txt
83
+ - README.md
84
+ - Rakefile
85
+ - bin/console
86
+ - bin/setup
87
+ - ext/geohash_int/LICENSE
88
+ - ext/geohash_int/Rakefile
89
+ - ext/geohash_int/geohash.c
90
+ - ext/geohash_int/geohash.h
91
+ - geohash_int.gemspec
92
+ - lib/geohash_int.rb
93
+ - lib/geohash_int/ffi.rb
94
+ - lib/geohash_int/version.rb
95
+ homepage: https://github.com/citrusbyte/ruby-geohash_int
96
+ licenses:
97
+ - MIT
98
+ metadata: {}
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubyforge_project:
115
+ rubygems_version: 2.6.11
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: Fast Geohash for Ruby that yields integers instead of strings
119
+ test_files: []