Module: SpatialFeatures
- Defined in:
- lib/spatial_features/has_spatial_features.rb,
lib/spatial_features/unzip.rb,
lib/spatial_features/utils.rb,
lib/spatial_features/engine.rb,
lib/spatial_features/caching.rb,
lib/spatial_features/version.rb,
lib/spatial_features/download.rb,
lib/spatial_features/validation.rb,
lib/spatial_features/importers/kml.rb,
lib/spatial_features/venn_polygons.rb,
lib/spatial_features/importers/base.rb,
lib/spatial_features/importers/file.rb,
lib/spatial_features/uncached_result.rb,
lib/spatial_features/importers/geomark.rb,
lib/spatial_features/importers/geo_json.rb,
lib/spatial_features/importers/kml_file.rb,
lib/spatial_features/importers/shapefile.rb,
lib/spatial_features/importers/esri_geo_json.rb,
lib/spatial_features/importers/kml_file_arcgis.rb,
lib/spatial_features/has_spatial_features/feature_import.rb,
lib/spatial_features/has_spatial_features/queued_spatial_processing.rb
Overview
TODO: Test the ‘::features` on a subclass to ensure we scope correctly
Defined Under Namespace
Modules: ActMethod, ClassMethods, Download, FeatureImport, FeaturesAssociationExtensions, Importers, InstanceMethods, QueuedSpatialProcessing, SQLHelpers, UncachedResult, Unzip, Utils, Validation Classes: EmptyImportError, Engine, ImportEncodingError, ImportError
Constant Summary collapse
- VERSION =
"3.8.0"
- ENCODING_ERROR =
/invalid byte sequence/i.freeze
Class Method Summary collapse
-
.cache_proximity(*klasses) ⇒ 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.
-
.class_combinations(klasses) ⇒ Object
Returns a list of class pairs with each combination e.g.
-
.class_permutations(klasses) ⇒ Object
Returns a list of class pairs with each permutation e.g.
-
.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
- .update_proximity(*klasses) ⇒ Object
- .update_spatial_cache(scope) ⇒ 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(*klasses) ⇒ 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
28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/spatial_features/caching.rb', line 28 def self.cache_proximity(*klasses) class_combinations(klasses).each do |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 end |
.cache_record_proximity(record, klass) ⇒ Object
Create or update the spatial cache of a single record in relation to another spatial class
54 55 56 57 58 |
# File 'lib/spatial_features/caching.rb', line 54 def self.cache_record_proximity(record, klass) clear_record_cache(record, klass) create_spatial_proximities(record, klass) create_spatial_cache(record, klass) end |
.class_combinations(klasses) ⇒ Object
Returns a list of class pairs with each combination e.g. [a,b], [a,c] [b,c] and also [a,a], [b,b], [c,c]
44 45 46 |
# File 'lib/spatial_features/caching.rb', line 44 def self.class_combinations(klasses) klasses.zip(klasses) + klasses.combination(2).to_a end |
.class_permutations(klasses) ⇒ Object
Returns a list of class pairs with each permutation e.g. [a,b], [b,a] and also [a,a], [b,b]
49 50 51 |
# File 'lib/spatial_features/caching.rb', line 49 def self.class_permutations(klasses) klasses.zip(klasses) + klasses.permutation(2).to_a end |
.clear_cache(klass = nil, clazz = nil) ⇒ Object
Delete all cache entries relating klass to clazz
61 62 63 64 65 66 67 68 69 |
# File 'lib/spatial_features/caching.rb', line 61 def self.clear_cache(klass = nil, clazz = nil) if klass.blank? && clazz.blank? SpatialCache.delete_all SpatialProximity.delete_all else SpatialCache.between(klass, clazz).delete_all SpatialProximity.between(klass, clazz).delete_all end end |
.clear_record_cache(record, klass) ⇒ Object
71 72 73 74 |
# File 'lib/spatial_features/caching.rb', line 71 def self.clear_record_cache(record, klass) record.spatial_caches.where(:intersection_model_type => SpatialFeatures::Utils.class_name_with_ancestors(klass)).delete_all SpatialProximity.between(record, klass).delete_all end |
.create_spatial_cache(model, klass) ⇒ Object
100 101 102 103 104 105 106 107 |
# File 'lib/spatial_features/caching.rb', line 100 def self.create_spatial_cache(model, klass) SpatialCache.create! do |cache| cache.spatial_model = model cache.intersection_model_type = klass cache.intersection_cache_distance = default_cache_buffer_in_meters cache.features_hash = model.features_hash if model.has_spatial_features_hash? end end |
.create_spatial_proximities(record, klass) ⇒ Object
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/spatial_features/caching.rb', line 76 def self.create_spatial_proximities(record, klass) klass = klass.to_s.constantize klass_record = klass.new scope = klass.within_buffer(record, default_cache_buffer_in_meters, :columns => :id, :intersection_area => true, :distance => true, :cache => false) scope = scope.where.not(:id => record.id) if klass.table_name == record.class.table_name # Don't calculate self proximity results = klass.connection.select_rows(scope.to_sql) results.each do |id, distance, area| klass_record.id = id SpatialProximity.create! do |proximity| # Always make the spatial model earliest type and id be model a so we can optimize queries data = [[Utils.base_class(record).to_s, record.id], [Utils.base_class(klass_record).to_s, klass_record.id]] data.sort! # Set id and type instead of model to avoid autosaving the klass_record proximity.model_a_type, proximity.model_a_id = data.first proximity.model_b_type, proximity.model_b_id = data.second proximity.distance_in_meters = distance proximity.intersection_area_in_square_meters = area end end end |
.update_proximity(*klasses) ⇒ Object
5 6 7 8 9 10 11 12 13 14 15 |
# File 'lib/spatial_features/caching.rb', line 5 def self.update_proximity(*klasses) class_permutations(klasses).each do |klass, clazz| klass.without_spatial_cache(clazz).find_each do |record| cache_record_proximity(record, clazz) end end klasses.each do |klass| update_spatial_cache(klass) end end |
.update_spatial_cache(scope) ⇒ Object
17 18 19 20 21 22 23 |
# File 'lib/spatial_features/caching.rb', line 17 def self.update_spatial_cache(scope) scope.with_stale_spatial_cache.includes(:spatial_caches).find_each do |record| record.spatial_caches.each do |spatial_cache| cache_record_proximity(record, spatial_cache.intersection_model_type) if spatial_cache.stale? end 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 62 |
# File 'lib/spatial_features/venn_polygons.rb', line 4 def self.venn_polygons(*scopes) = scopes. scope = scopes.collect do |scope| scope.joins(:features).where('features.feature_type = ?', 'polygon').except(:select).select("features.geom AS the_geom").to_sql end.reject(&:blank?).join(' UNION ') # NullRelation.to_sql returns empty string, so reject it 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 = '#{Utils.base_class([: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.joins(:features).where('features.feature_type = ?', 'polygon').except(:select).select("#{scope.klass.table_name}.id, features.spatial_model_type AS type, features.geom").to_sql end.reject(&:blank?).join(' UNION ') # NullRelation.to_sql returns empty string, so reject it 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 |