Module: SpatialFeatures
- Defined in:
- lib/spatial_features.rb,
lib/spatial_features/engine.rb,
lib/spatial_features/caching.rb,
lib/spatial_features/version.rb,
lib/spatial_features/venn_polygons.rb,
lib/spatial_features/has_spatial_features.rb
Defined Under Namespace
Modules: ActMethod, ClassMethods, InstanceMethods, UncachedRelation Classes: Engine
Constant Summary collapse
- VERSION =
"1.3.4"
Class Method Summary collapse
-
.cache_proximity(klass, clazz) ⇒ Object
Create or update the spatial cache of a spatial class in relation to another NOTE: Arguments are order independent, so their names do not reflect the _a _b naming scheme used in other cache methods.
-
.cache_record_proximity(record, klass) ⇒ Object
Create or update the spatial cache of a single record in relation to another spatial class.
-
.clear_cache(klass = nil, clazz = nil) ⇒ Object
Delete all cache entries relating klass to clazz.
- .clear_record_cache(record, klass) ⇒ Object
- .create_spatial_cache(model, klass) ⇒ Object
- .create_spatial_proximities(record, klass) ⇒ Object
-
.venn_polygons(*scopes) ⇒ Object
Splits overlapping features into separate polygons at their areas of overlap, and returns an array of objects with kml for the overlapping area and a list of the record ids whose kml overlapped within that area.
Class Method Details
.cache_proximity(klass, clazz) ⇒ Object
Create or update the spatial cache of a spatial class in relation to another NOTE: Arguments are order independent, so their names do not reflect the _a _b naming scheme used in other cache methods
8 9 10 11 12 13 14 15 16 17 18 19 |
# File 'lib/spatial_features/caching.rb', line 8 def self.cache_proximity(klass, clazz) clear_cache(klass, clazz) klass.find_each do |record| create_spatial_proximities(record, clazz) create_spatial_cache(record, clazz) end clazz.find_each do |record| create_spatial_cache(record, klass) end end |
.cache_record_proximity(record, klass) ⇒ Object
Create or update the spatial cache of a single record in relation to another spatial class
22 23 24 25 26 |
# File 'lib/spatial_features/caching.rb', line 22 def self.cache_record_proximity(record, klass) clear_record_cache(record, klass) create_spatial_proximities(record, klass) create_spatial_cache(record, klass) end |
.clear_cache(klass = nil, clazz = nil) ⇒ Object
Delete all cache entries relating klass to clazz
29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/spatial_features/caching.rb', line 29 def self.clear_cache(klass = nil, clazz = nil) if klass.blank? && clazz.blank? SpatialCache.delete_all SpatialProximity.delete_all else SpatialCache.where(:spatial_model_type => klass, :intersection_model_type => clazz).delete_all SpatialCache.where(:spatial_model_type => clazz, :intersection_model_type => klass).delete_all SpatialProximity.where(:model_a_type => klass, :model_b_type => clazz).delete_all SpatialProximity.where(:model_a_type => clazz, :model_b_type => klass).delete_all end end |
.clear_record_cache(record, klass) ⇒ Object
41 42 43 44 45 |
# File 'lib/spatial_features/caching.rb', line 41 def self.clear_record_cache(record, klass) record.spatial_cache.where(:intersection_model_type => klass.name).delete_all SpatialProximity.where(:model_a_type => record.class.name, :model_a_id => record.id, :model_b_type => klass.name).delete_all SpatialProximity.where(:model_b_type => record.class.name, :model_b_id => record.id, :model_a_type => klass.name).delete_all end |
.create_spatial_cache(model, klass) ⇒ Object
60 61 62 63 64 65 |
# File 'lib/spatial_features/caching.rb', line 60 def self.create_spatial_cache(model, klass) SpatialCache.create!( :spatial_model => model, :intersection_model_type => klass.name, :cache_distance => default_cache_buffer_in_meters) end |
.create_spatial_proximities(record, klass) ⇒ Object
47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/spatial_features/caching.rb', line 47 def self.create_spatial_proximities(record, klass) record_is_a = record.class.name < klass.name scope = klass.within_buffer(record, default_cache_buffer_in_meters, :intersection_area => true, :distance => true, :cache => false) scope.find_each do |klass_record| SpatialProximity.create!( :model_a => record_is_a ? record : klass_record, :model_b => record_is_a ? klass_record : record, :distance_in_meters => klass_record.distance_in_meters, :intersection_area_in_square_meters => klass_record.intersection_area_in_square_meters) end end |
.venn_polygons(*scopes) ⇒ Object
Splits overlapping features into separate polygons at their areas of overlap, and returns an array of objects with kml for the overlapping area and a list of the record ids whose kml overlapped within that area
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/spatial_features/venn_polygons.rb', line 4 def self.venn_polygons(*scopes) = scopes. scope = scopes.collect do |scope| scope.klass.from(scope, scope.klass.table_name).joins(:features).where('features.feature_type = ?', 'polygon').select("features.geom AS the_geom").to_sql end.join(' UNION ') sql = " SELECT scope.id, scope.type, ST_AsKML(venn_polygons.geom) AS kml FROM ST_Dump(( SELECT ST_Polygonize(the_geom) AS the_geom FROM ( SELECT ST_Union(the_geom) AS the_geom FROM ( -- Handle Multigeometry SELECT ST_ExteriorRing((ST_DumpRings(the_geom)).geom) AS the_geom FROM (#{scope}) AS scope ) AS exterior_lines ) AS noded_lines WHERE NOT ST_IsEmpty(the_geom) -- Ignore empty geometry from ST_Union if there are no polygons because polygonize will explode )) AS venn_polygons " # If we have a target model, throw away all venn_polygons not bounded by the target if [:target] sql << "INNER JOIN features ON features.spatial_model_type = '#{[:target].class}' AND features.spatial_model_id = #{[:target].id} AND ST_Intersects(features.geom, venn_polygons.geom) " end # Join with the original polygons so we can determine which original polygons each venn polygon came from scope = scopes.collect do |scope| scope.klass.from(scope, scope.klass.table_name).joins(:features).where('features.feature_type = ?', 'polygon').select("#{scope.klass.table_name}.id, features.spatial_model_type AS type, features.geom").to_sql end.join(' UNION ') sql << "INNER JOIN (#{scope}) AS scope ON ST_Covers(scope.geom, ST_PointOnSurface(venn_polygons.geom)) -- Shrink the venn polygons so they don't share edges with the original polygons which could cause varying results due to tiny inaccuracy" # Eager load the records for each venn polygon eager_load_hash = Hash.new {|hash, key| hash[key] = []} polygons = ActiveRecord::Base.connection.select_all(sql) polygons.group_by{|row| row['type']}.each do |record_type, rows| rows.each do |row| eager_load_hash[record_type] << row['id'] end end eager_load_hash.each do |record_type, ids| eager_load_hash[record_type] = record_type.constantize.find(ids) end # Instantiate objects to hold the kml and records for each venn polygon polygons.group_by{|row| row['kml']}.collect do |kml, rows| # Uniq on row id in case a single record had self intersecting multi geometry, which would cause it to appear duplicated on a single venn polygon records = rows.uniq{|row| row.values_at('id', 'type') }.collect{|row| eager_load_hash.fetch(row['type']).detect{|record| record.id == row['id'].to_i } } OpenStruct.new(:kml => kml, :records => records) end end |