Skip to content

Commit

Permalink
Fixing GeographicArea.are_contained_in & .is_contained_by /with specs.
Browse files Browse the repository at this point in the history
  • Loading branch information
TuckerJD committed Feb 12, 2015
1 parent 250ab6a commit dffb604
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 118 deletions.
4 changes: 2 additions & 2 deletions app/models/collecting_event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -264,11 +264,11 @@ def name_hash(types)
# use geographic_area only if there are no GIs or EGIs
unless self.geographic_area.nil?
# we need to use the geographic_area directly
gi_list << GeographicItem.is_contained_in('any', self.geographic_area.geographic_items)
gi_list << GeographicItem.are_contained_in('any', self.geographic_area.geographic_items)
end
else
# gather all the GIs which contain this GI or EGI
gi_list << GeographicItem.is_contained_in('any', self.geographic_items.to_a + self.error_geographic_items.to_a)
gi_list << GeographicItem.are_contained_in('any', self.geographic_items.to_a + self.error_geographic_items.to_a)
end

# there are a few ways we can end up with no GIs
Expand Down
105 changes: 59 additions & 46 deletions app/models/geographic_area.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ def self.ancestors_and_descendants_of(geographic_area)
where(['ga.name = ?', names[1]])
}

# @param [Array] of names of self and parents
# @return [Scope]
# TODO: Test, or extend a general method
# Matches GeographicAreas that match name, parent name, parent.parent name.
# Call via find_by_self_and_parents(%w{Champaign Illinois United\ States}).
Expand All @@ -122,12 +124,12 @@ def self.ancestors_and_descendants_of(geographic_area)
where(['gb.name = ?', names[2]])
}

# @param array [Array] of strings of names for areas
# @return [Scope] of GeographicAreas which match name and parent.name
# Route out to a scope given the length of the
# search array. Could be abstracted to
# build nesting on the fly if we actually
# needed more than three nesting
# @param array [Array] of strings of names for areas
# @return [Scope] of GeographicAreas which match name and parent.name
def self.find_by_self_and_parents(array)
if array.length == 1
where(name: array.first)
Expand All @@ -148,7 +150,7 @@ def self.countries
# @param params [Hash] of parameters for this search
# @return [Scope] of items found
def self.find_for_autocomplete(params)
term = params[:term]
term = params[:term]
terms = term.split
limit = 100
case term.length
Expand All @@ -162,64 +164,69 @@ def self.find_for_autocomplete(params)
self.where(name: term) + self.where(search_term).includes(:parent, :geographic_area_type).order('length(name)', :name).limit(limit)
end

# @param geographic_item [GeographicItem]
# @return [Scope] of geographic_items
def self.find_others_contained_by(geographic_item)
pieces = GeographicItem.is_contained_by('any_poly', geographic_item.geo_object)
# @param [GeographicArea]
# @return [Scope] of geographic_areas
def self.is_contained_by(geographic_area)
pieces = nil
if geographic_area.geographic_items.any?
pieces = GeographicItem.is_contained_by('any_poly', geographic_area.geo_object)
others = []
pieces.each { |other|
others.push(other.geographic_areas.to_a)
}
pieces = GeographicArea.where('id in (?)', others.flatten.map(&:id).uniq)
end
pieces

others = []
pieces.each { |o|
others.push(o.collecting_events_through_georeferences.to_a)
others.push(o.collecting_events_through_georeference_error_geographic_item.to_a)
}
pieces = CollectingEvent.where('id in (?)', others.flatten.map(&:id).uniq)

pieces.excluding(geographic_area)

end

# @param geographic_item [GeographicItem]
# @return [Scope] of geographic_items
def self.find_others_contained_in(geographic_area)
pieces = GeographicItem.is_contained_in('any', geographic_area.geo_object)
# @param [GeographicArea]
# @return [Scope] of geographic_areas
def self.are_contained_in(geographic_area)
pieces = nil
if geographic_area.geographic_items.any?
pieces = GeographicItem.are_contained_in('any_poly', geographic_area.geo_object)
others = []
pieces.each { |other|
others.push(other.geographic_areas.to_a)
}
pieces = GeographicArea.where('id in (?)', others.flatten.map(&:id).uniq)
end
pieces

others = []
pieces.each { |o|
others.push(o.collecting_events_through_georeferences.to_a)
others.push(o.collecting_events_through_georeference_error_geographic_item.to_a)
}
pieces = CollectingEvent.where('id in (?)', others.flatten.map(&:id).uniq)

pieces.excluding(geographic_area)

end

# @param latitude [Double] Decimal degrees
# @param longitude [Double] Decimal degrees
# @return [Scope] of all area which contain the point specified
def self.find_by_lat_long(latitude = 0.0, longitude = 0.0)
point = "POINT(#{longitude} #{latitude})"
where_clause = "ST_Contains(polygon::geometry, GeomFromEWKT('srid=4326;#{point}')) OR ST_Contains(multi_polygon::geometry, GeomFromEWKT('srid=4326;#{point}'))"
point = "POINT(#{longitude} #{latitude})"
where_clause = "ST_Contains(polygon::geometry, GeomFromEWKT('srid=4326;#{point}'))" +
" OR ST_Contains(multi_polygon::geometry, GeomFromEWKT('srid=4326;#{point}'))"
retval = GeographicArea.joins(:geographic_items).where(where_clause)
retval
end

# @return [Scope]
def children_at_level1
GeographicArea.descendants_of(self).where('level1_id IS NOT NULL AND level2_id IS NULL')
end

# @return [Scope]
def children_at_level2
GeographicArea.descendants_of(self).where('level2_id IS NOT NULL')
end

# @param [String] of geographic_area_type
# @return [Scope] descendants of this instance which have specific types, such as counties of a state
def descendants_of_geographic_area_type(geographic_area_type)
GeographicArea.descendants_of(self).includes([:geographic_area_type]).where(geographic_area_types: {name: geographic_area_type})
GeographicArea.descendants_of(self).includes([:geographic_area_type]).
where(geographic_area_types: {name: geographic_area_type})
end

# @param [Array]
# @return [Scope] descendants of this instance which have specific types, such as cities and counties of a province
def descendants_of_geographic_area_types(geographic_area_types)
GeographicArea.descendants_of(self).includes([:geographic_area_type]).where(geographic_area_types: {name: geographic_area_types})
GeographicArea.descendants_of(self).includes([:geographic_area_type]).
where(geographic_area_types: {name: geographic_area_types})
end

# @return [Hash] keys point to each of the four level components of the ID. Matches values in original data.
Expand All @@ -231,12 +238,13 @@ def tdwg_ids
}
end

# @return [String, nil] of 1, 2, 3, 4 iff is TDWG data source
def tdwg_level
return nil if !self.data_origin =~ /TDWG/
self.data_origin[-1]
end

# @return [GeographicItem, nil]
# @return [GeographicItem, nil]
# a "preferred" geographic item for this geogrpahic area, where preference
# is based on an ordering of source gazeteers, the order being
# 1) Natural Earth Countries
Expand All @@ -256,23 +264,26 @@ def default_geographic_item
retval
end

def default_geo_json
default_geographic_item.to_geo_json2
end
# # @return [GeoJSON] of the default GeographicItem
# def default_geo_json
# default_geographic_item.to_geo_json2
# end

# @return [RGeo object] of the default GeographicItem
def geo_object
default_geographic_item
end

# @return [Hash] of the pieces of a GeoJSON 'Feature'
def to_geo_json_feature
# object = self.geographic_items.order(:id).first
#type = object.geo_type
#if type == :geometry_collection
# geometry = RGeo::GeoJSON.encode(object)
#else
# geo_id = self.geographic_items.order(:id).pluck(:id).first
# geometry = JSON.parse(GeographicItem.connection.select_all("select ST_AsGeoJSON(multi_polygon::geometry) geo_json from geographic_items where id=#{geo_id};")[0]['geo_json'])
# end
#if type == :geometry_collection
# geometry = RGeo::GeoJSON.encode(object)
#else
# geo_id = self.geographic_items.order(:id).pluck(:id).first
# geometry = JSON.parse(GeographicItem.connection.select_all("select ST_AsGeoJSON(multi_polygon::geometry) geo_json from geographic_items where id=#{geo_id};")[0]['geo_json'])
# end
retval = {
'type' => 'Feature',
'geometry' => self.geographic_items.order(:id).first.to_geo_json,
Expand All @@ -284,9 +295,9 @@ def to_geo_json_feature
retval
end


# Find a centroid by scaling this object tree up to the first antecedent which provides a geographic_item, and
# provide a point on which to focus the map. Return 'nil' if there are no GIs in the chain.
# @return [GeographicItem] a point
def geographic_area_map_focus
item = nil
if geographic_items.count == 0
Expand All @@ -300,6 +311,7 @@ def geographic_area_map_focus
item
end

# @return [Hash] of the attributes of this instance, as far as possible
def geolocate_ui_params_hash
# parameters = {county: level2.name, state: level1.name, country: level0.name}
parameters = {}
Expand All @@ -317,6 +329,7 @@ def geolocate_ui_params_hash
end

# "http://www.museum.tulane.edu/geolocate/web/webgeoreflight.aspx?country=United States of America&state=Illinois&locality=Champaign&points=40.091622|-88.241179|Champaign|low|7000&georef=run|false|false|true|true|false|false|false|0&gc=Tester"
# @return [String]
def geolocate_ui_params_string
if @geolocate_hash.nil?
geolocate_ui_params_hash
Expand Down
6 changes: 3 additions & 3 deletions app/models/geographic_item.rb
Original file line number Diff line number Diff line change
Expand Up @@ -307,14 +307,14 @@ def self.disjoint_from(column_name, *geographic_items)
# If this scope is given an Array of GeographicItems as a second parameter,
# it will return the 'or' of each of the objects against the table.
# SELECT COUNT(*) FROM "geographic_items" WHERE (ST_Contains(polygon::geometry, GeomFromEWKT('srid=4326;POINT (0.0 0.0 0.0)')) or ST_Contains(polygon::geometry, GeomFromEWKT('srid=4326;POINT (-9.8 5.0 0.0)')))
def self.is_contained_in(column_name, *geographic_items)
def self.are_contained_in(column_name, *geographic_items)
column_name.downcase!
case column_name
when 'any'
part = []
DATA_TYPES.each { |column|
unless column == :geometry_collection
part.push(GeographicItem.is_contained_in("#{column}", geographic_items).to_a)
part.push(GeographicItem.are_contained_in("#{column}", geographic_items).to_a)
end
}
# todo: change 'id in (?)' to some other sql construct
Expand All @@ -323,7 +323,7 @@ def self.is_contained_in(column_name, *geographic_items)
part = []
DATA_TYPES.each { |column|
if column.to_s.index(column_name.gsub('any_', ''))
part.push(GeographicItem.is_contained_in("#{column}", geographic_items).to_a)
part.push(GeographicItem.are_contained_in("#{column}", geographic_items).to_a)
end
}
# todo: change 'id in (?)' to some other sql construct
Expand Down
18 changes: 9 additions & 9 deletions spec/models/collecting_event_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@
specify 'geolocate_ui_params_hash from lat/long' do
# @ce_m1.georeference was built from verbatim data; no locality
expect(@ce_m1.geolocate_ui_params_hash).to eq({:country => 'Big Boxia',
:state => 'T',
:state => 'QT',
:county => 'M1',
:locality => 'Lesser Boxia Lake',
:Latitude => 27.5,
Expand Down Expand Up @@ -333,7 +333,7 @@

specify 'geolocate_ui_params_string from lat/long' do
#pending 'creation of a method for geolocate_ui_params_string'
expect(@ce_m1.geolocate_ui_params_string).to eq('http://www.museum.tulane.edu/geolocate/web/webgeoreflight.aspx?country=Big Boxia&state=T&county=M1&locality=Lesser Boxia Lake&points=27.5|33.5|Lesser Boxia Lake|0|3&georef=run|false|false|true|true|false|false|false|0&gc=Tester')
expect(@ce_m1.geolocate_ui_params_string).to eq('http://www.museum.tulane.edu/geolocate/web/webgeoreflight.aspx?country=Big Boxia&state=QT&county=M1&locality=Lesser Boxia Lake&points=27.5|33.5|Lesser Boxia Lake|0|3&georef=run|false|false|true|true|false|false|false|0&gc=Tester')
end
end
end
Expand Down Expand Up @@ -493,29 +493,29 @@
# @ce_o3 has no georeference, so the only way to 'O3' is through geographic_area
expect(@ce_o3.states_hash).to include({'O3' => [@area_o3]}, {'SO3' => [@area_so3]})
# @ce_p2 has no georeference, so the only way to 'U' is through geographic_area
expect(@ce_p2.states_hash).to include({'U' => [@area_u]}, {'East Boxia' => [@area_east_boxia_3]})
expect(@ce_p2.states_hash).to include({'QU' => [@area_u]}, {'East Boxia' => [@area_east_boxia_3]})
end
specify 'derived from georeference -> geographic_areas chain' do
# @ce_n4 has no geographic_area, so the only way to 'N4' is through georeference
expect(@ce_n4.states_hash).to include({'N4' => [@area_n4]}, {'RN4' => [@area_rn4]})
# @ce_n4 has no geographic_area, so the only way to 'T' is through georeference
list = @ce_n2.states_hash
expect(list.keys).to include('T')
expect(list['T']).to include(@area_t_1, @area_t_2)
expect(list.keys).to include('QT')
expect(list['QT']).to include(@area_t_1, @area_t_2)
end
end

context 'when more than one possible name is present' do
specify 'derived from geographic_area_chain' do
# 'T' is a state in 'Q'
list = @ce_m1.states_hash
expect(list.keys).to include('T')
expect(list['T']).to include(@area_t_1, @area_t_2)
expect(list.keys).to include('QT')
expect(list['QT']).to include(@area_t_1, @area_t_2)
end
specify 'derived from georeference -> geographic_areas chain' do
# @ce_p1 has both geographic_area and georeference; georeference has priority
list = @ce_p1.states_hash
expect(list.keys).to include('U', 'East Boxia')
expect(list.keys).to include('QU', 'East Boxia')
end
end
end
Expand Down Expand Up @@ -543,7 +543,7 @@
end
specify 'it should return the #states_hash.key that has the most #countries_hash.values if more than one present' do
# @ce_n2 leads back to three GAs; 'Q', 'Big Boxia', and 'Old Boxia'
expect(@ce_n2.state_name).to eq('T')
expect(@ce_n2.state_name).to eq('QT')
end
specify 'it should return the first #states_hash.key when an equal number of .values is present' do
# @ce_n3 is state names 'N3', and leads back to two GAs; 'R', and 'Old Boxia'
Expand Down
37 changes: 31 additions & 6 deletions spec/models/geographic_area_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -367,14 +367,39 @@
ActiveRecord::Base.connection.reset_pk_sequence!('projects')
end

specify('find_others_contained_in')do
pending
expect(GeographicArea.find_others_contained_in(@area_land_mass)).to include(@item_n3)
specify('is_contained_by') do
expect(GeographicArea.is_contained_by(@area_old_boxia).map(&:name)).to include('Old Boxia',
'West Boxia',
'QTM1',
'QTM2',
'QTN1',
'QTN2',
'M1',
'QT',
'West Boxia',
'R',
'N1',
'M2',
'RM3',
'RM4',
'RN3',
'RN4',
'M3',
'N3',
'M4',
'N4',
'N2')
end

specify('find_others_contained_by') do
pending
expect(GeographicArea.find_others_contained_by(@area_old_boxia)).to include(@area_r)
specify('are_contained_in') do
# pending
expect(GeographicArea.are_contained_in(@area_p2).map(&:name)).to include('East Boxia',
'Big Boxia',
'Q',
'QUP2',
'QU',
'P2',
'Great Northern Land Mass')
end

specify('find_by_lat_long') do
Expand Down

0 comments on commit dffb604

Please sign in to comment.