module Geomodel
Constants
- DEFAULT_COST_FUNCTION
The default cost function, used if none is provided
- VERSION
Public Class Methods
filter_result_set_by_bounding_box(bounding_box, result_set)
click to toggle source
Given a result set from your datastore (a query you filtered with geocells_for_bounding_box) it will filter the records that land outside of the given bounding box (generally you'll use the same bounding box used in geocells_for_bounding_box)
# File lib/geomodel.rb, line 35 def self.filter_result_set_by_bounding_box(bounding_box, result_set) result_set.select do |row| row.latitude >= bounding_box.south && row.latitude <= bounding_box.north && row.longitude >= bounding_box.west && row.longitude <= bounding_box.east end end
geocells_for_bounding_box(bounding_box, cost_function = nil)
click to toggle source
Retrieve the geocells to be used in a bounding box query Something like geocells IN (…)
Args:
bbox: A geotypes.Box indicating the bounding box to filter entities by. cost_function: An optional function that accepts two arguments: * num_cells: the number of cells to search * resolution: the resolution of each cell to search and returns the 'cost' of querying against this number of cells at the given resolution.
# File lib/geomodel.rb, line 26 def self.geocells_for_bounding_box(bounding_box, cost_function = nil) cost_function = DEFAULT_COST_FUNCTION if cost_function.nil? Geomodel::GeoCell.best_bbox_search_cells(bounding_box, cost_function) end
proximity_fetch(center, query_runner, max_results = 10, max_distance = 0)
click to toggle source
center: A geotypes.Point or db.GeoPt indicating the center point around
which to search for matching entities.
max_results: An int indicating the maximum number of desired results.
The default is 10, and the larger this number, the longer the fetch will take.
max_distance: An optional number indicating the maximum distance to
search, in meters.
# File lib/geomodel.rb, line 51 def self.proximity_fetch(center, query_runner, max_results = 10, max_distance = 0) results = [] searched_cells = Set.new # The current search geocell containing the lat,lon. cur_containing_geocell = Geomodel::GeoCell.compute(center) # The currently-being-searched geocells. # NOTES: # * Start with max possible. # * Must always be of the same resolution. # * Must always form a rectangular region. # * One of these must be equal to the cur_containing_geocell. cur_geocells = [cur_containing_geocell] closest_possible_next_result_dist = 0 # Assumes both a and b are lists of (entity, dist) tuples, *sorted by dist*. # NOTE: This is an in-place merge, and there are guaranteed # no duplicates in the resulting list. cmp_fn = lambda do |x, y| (!x.empty? && !y.empty?) ? x[1] <=> y[1] : 0 end dup_fn = lambda do |x| (x.nil? || x.empty?) ? nil : x[0].id end # assuming the the element responds to #id sorted_edges = [[0,0]] sorted_edge_distances = [0] while !cur_geocells.empty? closest_possible_next_result_dist = sorted_edge_distances[0] break if max_distance && closest_possible_next_result_dist > max_distance cur_geocells_unique = cur_geocells - searched_cells.to_a # Run query on the next set of geocells. cur_resolution = cur_geocells[0].size # Update results and sort. new_results = query_runner.call(cur_geocells_unique) searched_cells.merge(cur_geocells) # Begin storing distance from the search result entity to the # search center along with the search result itself, in a tuple. new_results = new_results.map { |entity| [entity, Geomodel::Math.distance(center, entity.location)] } new_results.sort! { |x, y| (!x.empty? && !y.empty?) ? x[1] <=> y[1] : 0 } new_results = new_results[0...max_results] # Merge new_results into results or the other way around, depending on # which is larger. if results.size > new_results.size Geomodel::Util.merge_in_place(results, [new_results], dup_fn, cmp_fn) else Geomodel::Util.merge_in_place(new_results, [results], dup_fn, cmp_fn) results = new_results end results = results[0...max_results] sorted_edges, sorted_edge_distances = Geomodel::Util.distance_sorted_edges(cur_geocells, center) if results.empty? || cur_geocells.size == 4 # Either no results (in which case we optimize by not looking at adjacents, go straight to the parent) # or we've searched 4 adjacent geocells, in which case we should now search the parents of those # geocells. cur_containing_geocell = cur_containing_geocell[0...-1] cur_geocells = cur_geocells.map { |cell| cell[0...-1] } break if cur_geocells.empty? || !cur_geocells[0] # Done with search, we've searched everywhere. elsif cur_geocells.size == 1 # Get adjacent in one direction. # TODO(romannurik): Watch for +/- 90 degree latitude edge case geocells. nearest_edge = sorted_edges[0] cur_geocells << Geomodel::GeoCell.adjacent(cur_geocells[0], nearest_edge) elsif cur_geocells.size == 2 # Get adjacents in perpendicular direction. nearest_edge = Geomodel::Util.distance_sorted_edges([cur_containing_geocell], center)[0][0] if nearest_edge[0] == 0 # Was vertical, perpendicular is horizontal. perpendicular_nearest_edge = sorted_edges.keep_if { |x| x[0] != 0 }.first else # Was horizontal, perpendicular is vertical. perpendicular_nearest_edge = sorted_edges.keep_if { |x| x[0] == 0 }.first end cur_geocells.concat( cur_geocells.map { |cell| Geomodel::GeoCell.adjacent(cell, perpendicular_nearest_edge) } ) end # We don't have enough items yet, keep searching. next if results.size < max_results # If the currently max_results'th closest item is closer than any # of the next test geocells, we're done searching. current_farthest_returnable_result_dist = Geomodel::Math.distance(center, results[max_results - 1][0].location) break if (closest_possible_next_result_dist >= current_farthest_returnable_result_dist) end results[0...max_results].keep_if { |result| max_distance == 0 || result.last < max_distance } end