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

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)
  options = scopes.extract_options!
  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 options[:target]
    sql <<
      "INNER JOIN features
        ON features.spatial_model_type = '#{options[:target].class}' AND features.spatial_model_id = #{options[: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