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.
- 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
|
+

|
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: []
|