geohash_int 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []