geomodel 0.0.1
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 +17 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +192 -0
- data/Rakefile +22 -0
- data/geomodel.gemspec +35 -0
- data/lib/geomodel.rb +158 -0
- data/lib/geomodel/geocell.rb +467 -0
- data/lib/geomodel/geomath.rb +28 -0
- data/lib/geomodel/geotypes.rb +109 -0
- data/lib/geomodel/util.rb +51 -0
- data/lib/geomodel/version.rb +3 -0
- data/spec/geocell_spec.rb +154 -0
- data/spec/geomath_spec.rb +55 -0
- data/spec/geomodel_spec.rb +89 -0
- data/spec/geotypes_spec.rb +65 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/util_spec.rb +19 -0
- metadata +229 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b8914e3d585af5281290b7a854cb1eec6d7a566c
|
4
|
+
data.tar.gz: 30b52b4cb9989bf752dbe9fb426f71607ef5858c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b170a8372a165c3bdad452588f7ffad1174996b448076f8a139bd0ee48bd83924ef1a4420734d9c98f8106239ce2fb601d0cd525350a4b692662f68847d61e80
|
7
|
+
data.tar.gz: 13298af29d84898c152475a74ffc11d917507224a5f2a36b5e2cd3bc0c6e4e0e18fbca99366c8c545771c0076e5daf02421d53d2200d92e2ec2d1a63e6cda41f
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
geomodel
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.0.0
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Integrallis Software & Brian Sam-Bodden
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
# Geomodel
|
2
|
+
|
3
|
+
[](http://travis-ci.org/integrallis/geomodel)
|
4
|
+
[](http://badge.fury.io/rb/geomodel)
|
5
|
+
[](https://gemnasium.com/integrallis/geomodel)
|
6
|
+
[](https://codeclimate.com/github/integrallis/geomodel)
|
7
|
+
[](https://coveralls.io/r/integrallis/geomodel?branch=master)
|
8
|
+
|
9
|
+
Geomodel aims to provide a generalized solution for performing basic indexing
|
10
|
+
and querying of geospatial data in non-relation environments. At the core, this
|
11
|
+
solution utilizes geohash-like objects called geocells.
|
12
|
+
|
13
|
+
A geocell is a hexadecimal string that defines a two dimensional rectangular
|
14
|
+
region inside the [-90,90] x [-180,180] latitude/longitude space. A geocell's
|
15
|
+
'resolution' is its length. For most practical purposes, at high resolutions,
|
16
|
+
geocells can be treated as single points.
|
17
|
+
|
18
|
+
Much like geohashes (see http://en.wikipedia.org/wiki/Geohash), geocells are
|
19
|
+
hierarchical, in that any prefix of a geocell is considered its ancestor, with
|
20
|
+
geocell[:-1] being geocell's immediate parent cell.
|
21
|
+
|
22
|
+
To calculate the rectangle of a given geocell string, first divide the
|
23
|
+
[-90,90] x [-180,180] latitude/longitude space evenly into a 4x4 grid like so:
|
24
|
+
|
25
|
+
<pre>
|
26
|
+
+---+---+---+---+ (90, 180)
|
27
|
+
| a | b | e | f |
|
28
|
+
+---+---+---+---+
|
29
|
+
| 8 | 9 | c | d |
|
30
|
+
+---+---+---+---+
|
31
|
+
| 2 | 3 | 6 | 7 |
|
32
|
+
+---+---+---+---+
|
33
|
+
| 0 | 1 | 4 | 5 |
|
34
|
+
(-90,-180) +---+---+---+---+
|
35
|
+
</pre>
|
36
|
+
|
37
|
+
NOTE: The point (0, 0) is at the intersection of grid cells 3, 6, 9 and c. And,
|
38
|
+
for example, cell 7 should be the sub-rectangle from (-45, 90) to (0, 180).
|
39
|
+
|
40
|
+
Calculate the sub-rectangle for the first character of the geocell string and
|
41
|
+
re-divide this sub-rectangle into another 4x4 grid. For example, if the geocell
|
42
|
+
string is '78a', we will re-divide the sub-rectangle like so:
|
43
|
+
|
44
|
+
<pre>
|
45
|
+
. .
|
46
|
+
. .
|
47
|
+
. . +----+----+----+----+ (0, 180)
|
48
|
+
| 7a | 7b | 7e | 7f |
|
49
|
+
+----+----+----+----+
|
50
|
+
| 78 | 79 | 7c | 7d |
|
51
|
+
+----+----+----+----+
|
52
|
+
| 72 | 73 | 76 | 77 |
|
53
|
+
+----+----+----+----+
|
54
|
+
| 70 | 71 | 74 | 75 |
|
55
|
+
. . (-45,90) +----+----+----+----+
|
56
|
+
. .
|
57
|
+
. .
|
58
|
+
</pre>
|
59
|
+
|
60
|
+
Continue to re-divide into sub-rectangles and 4x4 grids until the entire
|
61
|
+
geocell string has been exhausted. The final sub-rectangle is the rectangular
|
62
|
+
region for the geocell.
|
63
|
+
|
64
|
+
A geocell can be associated with a single geographic point and subsequently
|
65
|
+
indexed and filtered by either conformance to a bounding box or by proximity
|
66
|
+
(nearest-n) to a search center point.
|
67
|
+
|
68
|
+
# Approach
|
69
|
+
|
70
|
+
This Ruby implementation of GeoModel is based on the Python, Java and JavaScript implementations.
|
71
|
+
It's implemented as class level methods contained modules and a few datatype classes. So the 'model'
|
72
|
+
part isn't quite there and I don't really see a need for it. Since the library is meant to be use in
|
73
|
+
Non-Relational/Non-ORM environmets, binding the functions/methods to a model does not make much sense.
|
74
|
+
The model part was mostly implemented in the other libraries to bind directly to Google App Engine.
|
75
|
+
|
76
|
+
# References
|
77
|
+
|
78
|
+
- http://code.google.com/p/javageomodel/
|
79
|
+
- http://code.google.com/p/geomodel/
|
80
|
+
- https://github.com/danieldkim/geomodel
|
81
|
+
|
82
|
+
## Installation
|
83
|
+
|
84
|
+
Add this line to your application's Gemfile:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
gem 'geomodel'
|
88
|
+
```
|
89
|
+
|
90
|
+
And then execute:
|
91
|
+
|
92
|
+
$ bundle
|
93
|
+
|
94
|
+
Or install it yourself as:
|
95
|
+
|
96
|
+
$ gem install geomodel
|
97
|
+
|
98
|
+
## Usage
|
99
|
+
|
100
|
+
Currently, only single-point entities and two types of basic geospatial queries
|
101
|
+
on those entities are supported.
|
102
|
+
|
103
|
+
### Representing your locations
|
104
|
+
|
105
|
+
You'll need a class to hold a geolocation. It assumes that an "entity" has a unique
|
106
|
+
"id" (specific field can be configure), a latitude/longitude combination stored in
|
107
|
+
a "location" field (a Geomodel::Types::Point) and a collection of "geocells".
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
class Entity
|
111
|
+
attr_accessor :id, :location, :geocells
|
112
|
+
|
113
|
+
def to_s
|
114
|
+
self.id
|
115
|
+
end
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
An instance of one of these entities can be instantiated as shown next. Let's say we
|
120
|
+
wanted to create an entity for the Frank Lloyd Wright Iconic Desert Spire in Scottsdale, AZ
|
121
|
+
(http://livebetterinscottsdale.com/2012/02/things-to-see-in-scottsdale-az-the-frank-lloyd-wright-spire/):
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
flw_spire = Entity.new
|
125
|
+
flw_spire.id = 'Flatiron'
|
126
|
+
flw_spire.location = Geomodel::Types::Point.new(33.633406, -111.916803)
|
127
|
+
flw_spire.geocells = Geomodel::GeoCell.generate_geocells(flw_spire.location)
|
128
|
+
```
|
129
|
+
|
130
|
+
### Bounding Box Queries
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
# compute a geocell for the location using a resolution of 14
|
134
|
+
cell = Geomodel::GeoCell.compute(flw_spire.location, 14)
|
135
|
+
|
136
|
+
# create a bounding box for the cell
|
137
|
+
bounding_box = Geomodel::GeoCell.compute_box(cell)
|
138
|
+
|
139
|
+
# get a list of geocells for the given bounding box
|
140
|
+
geocells = Geomodel.geocells_for_bounding_box(bounding_box)
|
141
|
+
|
142
|
+
# use the bounding box geocells to do a key lookup in your database assuming that there is
|
143
|
+
# a location_geocell 'column' and you can do an IN query like:
|
144
|
+
result_set = my_db.query('SELECT * WHERE location_geocells IN (?)', query_geocells)
|
145
|
+
|
146
|
+
# the results then can be filtered by whether they fall inside the bounding box using:
|
147
|
+
matches = Geomodel.filter_result_set_by_bounding_box(bounding_box, result_set)
|
148
|
+
```
|
149
|
+
|
150
|
+
### proximity (nearest-n) queries
|
151
|
+
|
152
|
+
Find nearby locations given a location (lat & lon) and a radius in meters:
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
|
156
|
+
# a list of places (instance of Entity or object that responds to :id, :location, :geocells)
|
157
|
+
places = [place1, place2, place3, ...]
|
158
|
+
|
159
|
+
# a function that can query your database. It takes as a parameter an array of geocells (strings)
|
160
|
+
# that are used to filter the query (below is an in-memory implementation using the 'places' array
|
161
|
+
# as our datasource)
|
162
|
+
query_runner = lambda do |geocells|
|
163
|
+
result = places.reject do |o|
|
164
|
+
(o.geocells & geocells).length < 0
|
165
|
+
end
|
166
|
+
|
167
|
+
result
|
168
|
+
end
|
169
|
+
|
170
|
+
# query for a maximum of 20 results, 15 miles (~24140 meters) from the Frank Lloyd Wright Spire
|
171
|
+
# results are tuples (2 element arrays) with the matching entity and its distance from the location
|
172
|
+
results = Geomodel.proximity_fetch(flw_spire.location, query_runner, 20, 24140)
|
173
|
+
|
174
|
+
# extract the matching places
|
175
|
+
places = results.map(&:first)
|
176
|
+
|
177
|
+
# extract the distances
|
178
|
+
distances = results.map(&:last)
|
179
|
+
|
180
|
+
```
|
181
|
+
|
182
|
+
## Contributing
|
183
|
+
|
184
|
+
1. Fork it
|
185
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
186
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
187
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
188
|
+
5. Create new Pull Request
|
189
|
+
|
190
|
+
## License
|
191
|
+
|
192
|
+
MIT License
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#--
|
4
|
+
# Copyright &169;2001-2014 Integrallis Software, LLC.
|
5
|
+
# All Rights Reserved.
|
6
|
+
#
|
7
|
+
# Permission is granted for use, copying, modification, distribution,
|
8
|
+
# and distribution of modified versions of this work as long as the
|
9
|
+
# above copyright notice is included.
|
10
|
+
#++
|
11
|
+
|
12
|
+
# encoding: utf-8
|
13
|
+
|
14
|
+
# --------------------------------------------------------------------
|
15
|
+
require 'rubygems'
|
16
|
+
require 'bundler/setup'
|
17
|
+
require 'bundler/gem_tasks'
|
18
|
+
require 'rspec/core/rake_task'
|
19
|
+
|
20
|
+
RSpec::Core::RakeTask.new(:spec)
|
21
|
+
|
22
|
+
task default: :spec
|
data/geomodel.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'geomodel/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'geomodel'
|
8
|
+
spec.version = Geomodel::VERSION
|
9
|
+
spec.authors = ['Brian Sam-Bodden']
|
10
|
+
spec.email = ['bsbodden@integrallis.com']
|
11
|
+
spec.description = %q{A Ruby implementation of the Geomodel concept}
|
12
|
+
spec.summary = %q{Geomodel aims to provide a generalized solution for performing basic indexing
|
13
|
+
and querying of geospatial data in non-relation environments. At the core, this
|
14
|
+
solution utilizes geohash-like objects called geocells.}
|
15
|
+
spec.homepage = 'https://github.com/integrallis/geomodel'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
spec.files = `git ls-files`.split($/)
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.add_dependency 'geocoder', '~> 1.1.9'
|
24
|
+
spec.add_dependency 'hashie', '~> 2.0.5'
|
25
|
+
|
26
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
27
|
+
spec.add_development_dependency 'rake'
|
28
|
+
spec.add_development_dependency 'rspec'
|
29
|
+
spec.add_development_dependency 'simplecov'
|
30
|
+
spec.add_development_dependency 'hashie'
|
31
|
+
spec.add_development_dependency 'debugger'
|
32
|
+
spec.add_development_dependency 'launchy'
|
33
|
+
spec.add_development_dependency 'pry'
|
34
|
+
spec.add_development_dependency 'coveralls'
|
35
|
+
end
|
data/lib/geomodel.rb
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
require "geomodel/version"
|
2
|
+
require "geomodel/geomath"
|
3
|
+
require "geomodel/geotypes"
|
4
|
+
require "geomodel/geocell"
|
5
|
+
require "geomodel/util"
|
6
|
+
require 'set'
|
7
|
+
|
8
|
+
module Geomodel
|
9
|
+
|
10
|
+
# The default cost function, used if none is provided
|
11
|
+
DEFAULT_COST_FUNCTION = lambda do |num_cells, resolution|
|
12
|
+
num_cells > (Geomodel::GeoCell::GEOCELL_GRID_SIZE ** 2) ? 1e10000 : 0
|
13
|
+
end
|
14
|
+
|
15
|
+
# Retrieve the geocells to be used in a bounding box query
|
16
|
+
# Something like geocells IN (...)
|
17
|
+
#
|
18
|
+
# Args:
|
19
|
+
#
|
20
|
+
# bbox: A geotypes.Box indicating the bounding box to filter entities by.
|
21
|
+
# cost_function: An optional function that accepts two arguments:
|
22
|
+
# * num_cells: the number of cells to search
|
23
|
+
# * resolution: the resolution of each cell to search
|
24
|
+
# and returns the 'cost' of querying against this number of cells
|
25
|
+
# at the given resolution.
|
26
|
+
def self.geocells_for_bounding_box(bounding_box, cost_function = nil)
|
27
|
+
cost_function = DEFAULT_COST_FUNCTION if cost_function.nil?
|
28
|
+
Geomodel::GeoCell.best_bbox_search_cells(bounding_box, cost_function)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Given a result set from your datastore (a query you filtered with
|
32
|
+
# #geocells_for_bounding_box) it will filter the records that land outside
|
33
|
+
# of the given bounding box (generally you'll use the same bounding box used
|
34
|
+
# in #geocells_for_bounding_box)
|
35
|
+
def self.filter_result_set_by_bounding_box(bounding_box, result_set)
|
36
|
+
result_set.select do |row|
|
37
|
+
row.latitude >= bounding_box.south &&
|
38
|
+
row.latitude <= bounding_box.north &&
|
39
|
+
row.longitude >= bounding_box.west &&
|
40
|
+
row.longitude <= bounding_box.east
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# center: A geotypes.Point or db.GeoPt indicating the center point around
|
45
|
+
# which to search for matching entities.
|
46
|
+
# max_results: An int indicating the maximum number of desired results.
|
47
|
+
# The default is 10, and the larger this number, the longer the fetch
|
48
|
+
# will take.
|
49
|
+
# max_distance: An optional number indicating the maximum distance to
|
50
|
+
# search, in meters.
|
51
|
+
def self.proximity_fetch(center, query_runner, max_results = 10, max_distance = 0)
|
52
|
+
results = []
|
53
|
+
|
54
|
+
searched_cells = Set.new
|
55
|
+
|
56
|
+
# The current search geocell containing the lat,lon.
|
57
|
+
cur_containing_geocell = Geomodel::GeoCell.compute(center)
|
58
|
+
|
59
|
+
# The currently-being-searched geocells.
|
60
|
+
# NOTES:
|
61
|
+
# * Start with max possible.
|
62
|
+
# * Must always be of the same resolution.
|
63
|
+
# * Must always form a rectangular region.
|
64
|
+
# * One of these must be equal to the cur_containing_geocell.
|
65
|
+
cur_geocells = [cur_containing_geocell]
|
66
|
+
|
67
|
+
closest_possible_next_result_dist = 0
|
68
|
+
|
69
|
+
# Assumes both a and b are lists of (entity, dist) tuples, *sorted by dist*.
|
70
|
+
# NOTE: This is an in-place merge, and there are guaranteed
|
71
|
+
# no duplicates in the resulting list.
|
72
|
+
|
73
|
+
cmp_fn = lambda do |x, y|
|
74
|
+
(!x.empty? && !y.empty?) ? x[1] <=> y[1] : 0
|
75
|
+
end
|
76
|
+
|
77
|
+
dup_fn = lambda do |x|
|
78
|
+
(x.nil? || x.empty?) ? nil : x[0].id
|
79
|
+
end # assuming the the element responds to #id
|
80
|
+
|
81
|
+
sorted_edges = [[0,0]]
|
82
|
+
sorted_edge_distances = [0]
|
83
|
+
|
84
|
+
while !cur_geocells.empty?
|
85
|
+
closest_possible_next_result_dist = sorted_edge_distances[0]
|
86
|
+
|
87
|
+
next if max_distance and closest_possible_next_result_dist > max_distance
|
88
|
+
|
89
|
+
cur_geocells_unique = cur_geocells - searched_cells.to_a
|
90
|
+
|
91
|
+
# Run query on the next set of geocells.
|
92
|
+
cur_resolution = cur_geocells[0].size
|
93
|
+
|
94
|
+
# Update results and sort.
|
95
|
+
new_results = query_runner.call(cur_geocells_unique)
|
96
|
+
|
97
|
+
searched_cells.merge(cur_geocells)
|
98
|
+
|
99
|
+
# Begin storing distance from the search result entity to the
|
100
|
+
# search center along with the search result itself, in a tuple.
|
101
|
+
new_results = new_results.map { |entity| [entity, Geomodel::Math.distance(center, entity.location)] }
|
102
|
+
new_results.sort! { |x, y| (!x.empty? && !y.empty?) ? x[1] <=> y[1] : 0 }
|
103
|
+
new_results = new_results[0...max_results]
|
104
|
+
|
105
|
+
# Merge new_results into results or the other way around, depending on
|
106
|
+
# which is larger.
|
107
|
+
if results.size > new_results.size
|
108
|
+
Geomodel::Util.merge_in_place(results, [new_results], dup_fn, cmp_fn)
|
109
|
+
else
|
110
|
+
Geomodel::Util.merge_in_place(new_results, [results], dup_fn, cmp_fn)
|
111
|
+
results = new_results
|
112
|
+
end
|
113
|
+
|
114
|
+
results = results[0...max_results]
|
115
|
+
|
116
|
+
sorted_edges, sorted_edge_distances = Geomodel::Util.distance_sorted_edges(cur_geocells, center)
|
117
|
+
|
118
|
+
if results.empty? || cur_geocells.size == 4
|
119
|
+
# Either no results (in which case we optimize by not looking at adjacents, go straight to the parent)
|
120
|
+
# or we've searched 4 adjacent geocells, in which case we should now search the parents of those
|
121
|
+
# geocells.
|
122
|
+
cur_containing_geocell = cur_containing_geocell[0...-1]
|
123
|
+
cur_geocells = cur_geocells.map { |cell| cell[0...-1] }
|
124
|
+
break if !cur_geocells.empty? || !cur_geocells[0] # Done with search, we've searched everywhere.
|
125
|
+
elsif cur_geocells.size == 1
|
126
|
+
# Get adjacent in one direction.
|
127
|
+
# TODO(romannurik): Watch for +/- 90 degree latitude edge case geocells.
|
128
|
+
nearest_edge = sorted_edges[0]
|
129
|
+
cur_geocells << Geomodel::GeoCell.adjacent(cur_geocells[0], nearest_edge)
|
130
|
+
elsif cur_geocells.size == 2
|
131
|
+
# Get adjacents in perpendicular direction.
|
132
|
+
nearest_edge = Geomodel::Util.distance_sorted_edges([cur_containing_geocell], center)[0][0]
|
133
|
+
if nearest_edge[0] == 0
|
134
|
+
# Was vertical, perpendicular is horizontal.
|
135
|
+
perpendicular_nearest_edge = sorted_edges.keep_if { |x| x[0] != 0 }.first
|
136
|
+
else
|
137
|
+
# Was horizontal, perpendicular is vertical.
|
138
|
+
perpendicular_nearest_edge = sorted_edges.keep_if { |x| x[0] == 0 }.first
|
139
|
+
end
|
140
|
+
|
141
|
+
cur_geocells.concat(
|
142
|
+
cur_geocells.map { |cell| Geomodel::GeoCell.adjacent(cell, perpendicular_nearest_edge) }
|
143
|
+
)
|
144
|
+
end
|
145
|
+
|
146
|
+
# We don't have enough items yet, keep searching.
|
147
|
+
next if results.size < max_results
|
148
|
+
|
149
|
+
# If the currently max_results'th closest item is closer than any
|
150
|
+
# of the next test geocells, we're done searching.
|
151
|
+
current_farthest_returnable_result_dist = Geomodel::Math.distance(center, results[max_results - 1][0].location)
|
152
|
+
break if (closest_possible_next_result_dist >= current_farthest_returnable_result_dist)
|
153
|
+
end
|
154
|
+
|
155
|
+
results[0...max_results].keep_if { |result| max_distance == 0 || result.last < max_distance }
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|