module SpatialFeatures::ClassMethods
Public Instance Methods
acts_like_spatial_features?()
click to toggle source
# File lib/spatial_features/has_spatial_features.rb, line 35 def acts_like_spatial_features? true end
aggregate_features()
click to toggle source
# File lib/spatial_features/has_spatial_features.rb, line 80 def aggregate_features type = base_class.to_s # Rails stores polymorphic foreign keys as the base class if all == unscoped AggregateFeature.where(:spatial_model_type => type) else AggregateFeature.where(:spatial_model_type => type, :spatial_model_id => all.unscope(:select)) end end
area_in_square_meters()
click to toggle source
# File lib/spatial_features/has_spatial_features.rb, line 99 def area_in_square_meters features.area end
features()
click to toggle source
# File lib/spatial_features/has_spatial_features.rb, line 71 def features type = base_class.to_s # Rails stores polymorphic foreign keys as the base class if all == unscoped Feature.where(:spatial_model_type => type) else Feature.where(:spatial_model_type => type, :spatial_model_id => all.unscope(:select)) end end
features_cache_key()
click to toggle source
# File lib/spatial_features/has_spatial_features.rb, line 39 def features_cache_key "#{name}/#{aggregate_features.cache_key}" end
has_features_area?()
click to toggle source
Returns true if the model stores a cache of the features area
# File lib/spatial_features/has_spatial_features.rb, line 95 def has_features_area? owner_class_has_loaded_column?('features_area') end
has_spatial_features_hash?()
click to toggle source
Returns true if the model stores a hash of the features so we don't need to process the features if they haven't changed
# File lib/spatial_features/has_spatial_features.rb, line 90 def has_spatial_features_hash? owner_class_has_loaded_column?('features_hash') end
intersecting(other, options = {})
click to toggle source
# File lib/spatial_features/has_spatial_features.rb, line 43 def intersecting(other, options = {}) within_buffer(other, 0, options) end
lines()
click to toggle source
# File lib/spatial_features/has_spatial_features.rb, line 63 def lines features.lines end
points()
click to toggle source
# File lib/spatial_features/has_spatial_features.rb, line 67 def points features.points end
polygons()
click to toggle source
# File lib/spatial_features/has_spatial_features.rb, line 59 def polygons features.polygons end
within_buffer(other, buffer_in_meters = 0, options = {})
click to toggle source
# File lib/spatial_features/has_spatial_features.rb, line 47 def within_buffer(other, buffer_in_meters = 0, options = {}) return none if other.is_a?(ActiveRecord::Base) && other.new_record? # Cache only works on single records, not scopes. # This is because the cached intersection_area doesn't account for overlaps between the features in the scope. if options[:cache] != false && other.is_a?(ActiveRecord::Base) cached_within_buffer_scope(other, buffer_in_meters, options) else uncached_within_buffer_scope(other, buffer_in_meters, options) end end
Private Instance Methods
cached_spatial_join(other)
click to toggle source
# File lib/spatial_features/has_spatial_features.rb, line 121 def cached_spatial_join(other) other_class = Utils.base_class_of(other) self_class = Utils.base_class_of(self) joins <<~SQL INNER JOIN spatial_proximities ON (spatial_proximities.model_a_type = '#{self_class}' AND spatial_proximities.model_a_id = #{table_name}.id AND spatial_proximities.model_b_type = '#{other_class}' AND spatial_proximities.model_b_id IN (#{Utils.id_sql(other)})) OR (spatial_proximities.model_b_type = '#{self_class}' AND spatial_proximities.model_b_id = #{table_name}.id AND spatial_proximities.model_a_type = '#{other_class}' AND spatial_proximities.model_a_id IN (#{Utils.id_sql(other)})) SQL end
cached_within_buffer_scope(other, buffer_in_meters, options)
click to toggle source
# File lib/spatial_features/has_spatial_features.rb, line 105 def cached_within_buffer_scope(other, buffer_in_meters, options) options = options.reverse_merge(:columns => "#{table_name}.*") # Don't use the cache if it doesn't exist unless other.class.unscoped { other.spatial_cache_for?(Utils.class_of(self), buffer_in_meters) } # Unscope so if we're checking for same class intersections the scope doesn't affect this lookup return none.extending(UncachedResult) end scope = cached_spatial_join(other) scope = scope.select(options[:columns]) scope = scope.where("spatial_proximities.distance_in_meters <= ?", buffer_in_meters) if buffer_in_meters scope = scope.select("spatial_proximities.distance_in_meters") if options[:distance] scope = scope.select("spatial_proximities.intersection_area_in_square_meters") if options[:intersection_area] return scope end
features_scope(other)
click to toggle source
# File lib/spatial_features/has_spatial_features.rb, line 157 def features_scope(other) scope = AggregateFeature scope = scope.where(:spatial_model_type => Utils.base_class_of(other).to_s) scope = scope.where(:spatial_model_id => other) unless Utils.class_of(other) == other return scope end
owner_class_has_loaded_column?(column_name)
click to toggle source
# File lib/spatial_features/has_spatial_features.rb, line 164 def owner_class_has_loaded_column?(column_name) return false unless connected? return false unless table_exists? column_names.include? column_name end
spatial_join(other, buffer = 0, table_alias = 'features', other_alias = 'other_features', geom = 'geom_lowres')
click to toggle source
Returns a scope that includes the features for this record as the table_alias and the features for other as other_alias Performs a spatial intersection between the two sets of features, within the buffer distance given in meters
# File lib/spatial_features/has_spatial_features.rb, line 146 def spatial_join(other, buffer = 0, table_alias = 'features', other_alias = 'other_features', geom = 'geom_lowres') scope = features_scope(self).select("#{geom} AS geom").select(:spatial_model_id) other_scope = features_scope(other).select("ST_Union(#{geom}) AS geom") return joins(%Q(INNER JOIN (#{scope.to_sql}) AS #{table_alias} ON #{table_alias}.spatial_model_id = #{table_name}.id)) .joins(%Q(INNER JOIN (#{other_scope.to_sql}) AS #{other_alias} ON NOT ST_IsEmpty(#{table_alias}.geom) -- Can't ST_DWithin empty geometry AND NOT ST_IsEmpty(#{other_alias}.geom) -- Can't ST_DWithin empty geometry AND ST_DWithin(#{table_alias}.geom, #{other_alias}.geom, #{buffer}))) end
uncached_within_buffer_scope(other, buffer_in_meters, options)
click to toggle source
# File lib/spatial_features/has_spatial_features.rb, line 132 def uncached_within_buffer_scope(other, buffer_in_meters, options) options = options.reverse_merge(:columns => "#{table_name}.*") scope = spatial_join(other, buffer_in_meters) scope = scope.select(options[:columns]) scope = scope.select("ST_Distance(features.geom, other_features.geom) AS distance_in_meters") if options[:distance] scope = scope.select("ST_Area(ST_Intersection(ST_CollectionExtract(features.geom, 3), ST_CollectionExtract(other_features.geom, 3))) AS intersection_area_in_square_meters") if options[:intersection_area] # Use ST_CollectionExtract to avoid a segfault we've been seeing when intersecting certain geometry return scope end