geohash_int 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +143 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/ext/geohash_int/LICENSE +24 -0
- data/ext/geohash_int/Rakefile +33 -0
- data/ext/geohash_int/geohash.c +427 -0
- data/ext/geohash_int/geohash.h +108 -0
- data/geohash_int.gemspec +32 -0
- data/lib/geohash_int.rb +146 -0
- data/lib/geohash_int/ffi.rb +42 -0
- data/lib/geohash_int/version.rb +3 -0
- metadata +119 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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.*
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -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__)
|
data/bin/setup
ADDED
@@ -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_ */
|
data/geohash_int.gemspec
ADDED
@@ -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
|
data/lib/geohash_int.rb
ADDED
@@ -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
|
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: []
|