spatial_features 1.5.6 → 1.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b1b2fd845f974c81c193ab65128205450944af69
4
- data.tar.gz: 4b31e9277f2c4d97662faaa707b9a40e54f6198c
3
+ metadata.gz: 6cbe6788f92697f84521cb44e3fd3189b7b4d85d
4
+ data.tar.gz: b4e5fe7c097c9f78cb600d886b24be42c87903cc
5
5
  SHA512:
6
- metadata.gz: 944dc0f7f777bac917f94d9fdf5e31d98fe484d3fef6194bd5eb7363b17cd0e65a3434ec0c762c51a93286bb0ca5d786fe538c8160d61adb8aed38e60f119044
7
- data.tar.gz: 4a685e2b5d74ae77842dff7f3b8eedd21529a8e71ae72f43a249e187518fd88653a014cd4fc7c1141e8de0d2618710d38a0b577545180c88cee3df0736b54700
6
+ metadata.gz: 3f19855ec3be02877f845513e146d170f8ba5eaf659b7cfb21f700529b041647589abe8fc012375ebb76e1594928ec4b6502d3c372e22ecf05f8e89e845837d4
7
+ data.tar.gz: d04249b6d2971b45fd966a66ed30f5ffcf6f06154c5ec60d2a4eda1dfabdee6197070ab4ba77f5701d21658b67cfd57a4e902d9151c2543f3f6ab00a379f428a
@@ -9,6 +9,8 @@ module SpatialFeatures
9
9
  scope :with_features, lambda { joins(:features).uniq }
10
10
  scope :without_features, lambda { joins("LEFT OUTER JOIN features ON features.spatial_model_type = '#{name}' AND features.spatial_model_id = #{table_name}.id").where("features.id IS NULL") }
11
11
 
12
+ scope :with_stale_spatial_cache, lambda { joins(:spatial_cache).where("#{table_name}.features_hash != spatial_caches.features_hash").uniq } if has_spatial_features_hash?
13
+
12
14
  has_many :spatial_cache, :as => :spatial_model, :dependent => :delete_all
13
15
  has_many :model_a_spatial_proximities, :as => :model_a, :class_name => 'SpatialProximity', :dependent => :delete_all
14
16
  has_many :model_b_spatial_proximities, :as => :model_b, :class_name => 'SpatialProximity', :dependent => :delete_all
@@ -38,26 +40,15 @@ module SpatialFeatures
38
40
  # Cache only works on single records, not scopes.
39
41
  # This is because the cached intersection_area doesn't account for overlaps between the features in the scope.
40
42
  if options[:cache] != false && other.is_a?(ActiveRecord::Base)
41
- # Don't use the cache if it doesn't exist
42
- return all.extending(UncachedRelation) unless other.spatial_cache_for?(class_for(self), buffer_in_meters)
43
-
44
- scope = cached_spatial_join(other).select("#{table_name}.*")
45
- scope = scope.where("spatial_proximities.distance_in_meters <= ?", buffer_in_meters) if buffer_in_meters
46
- scope = scope.select("spatial_proximities.distance_in_meters") if options[:distance]
47
- scope = scope.select("spatial_proximities.intersection_area_in_square_meters") if options[:intersection_area]
48
- else # NON-CACHED
49
- scope = joins_features_for(other).select("#{table_name}.*").group("#{table_name}.#{primary_key}")
50
- scope = scope.where('ST_DWithin(features_for.geom, features_for_other.geom, ?)', buffer_in_meters) if buffer_in_meters
51
- scope = scope.select("MIN(ST_Distance(features_for.geom, features_for_other.geom)) AS distance_in_meters") if options[:distance]
52
- scope = scope.select("ST_Area(ST_Intersection(ST_UNION(features_for.geom), ST_UNION(features_for_other.geom))) AS intersection_area_in_square_meters") if options[:intersection_area]
43
+ cached_within_buffer_scope(other, buffer_in_meters, options)
44
+ else
45
+ uncached_within_buffer_scope(other, buffer_in_meters, options)
53
46
  end
54
-
55
- return scope
56
47
  end
57
48
 
58
49
  def covering(other)
59
50
  scope = joins_features_for(other).select("#{table_name}.*").group("#{table_name}.#{primary_key}")
60
- scope = scope.where('ST_Covers(features_for.geom, features_for_other.geom)')
51
+ scope = scope.where('ST_Covers(features.geom, features_for_other.geom)')
61
52
 
62
53
  return scope
63
54
  end
@@ -85,16 +76,13 @@ module SpatialFeatures
85
76
  # Returns a scope that includes the features for this record as the table_alias and the features for other as #{table_alias}_other
86
77
  # Can be used to perform spatial calculations on the relationship between the two sets of features
87
78
  def joins_features_for(other, table_alias = 'features_for')
88
- joins_features(table_alias)
89
- .joins_features("#{table_alias}_other", class_for(other), spatial_model_id = ids_sql_for(other))
79
+ joins(:features).joins(%Q(INNER JOIN (#{other_features_union(other).to_sql}) AS "#{table_alias}_other" ON true))
90
80
  end
91
81
 
92
- # Returns a scope that includes the features for this record as the table_alias
93
- # Default arguments can be overridden to include features for a different set of records
94
- def joins_features(table_alias = 'features_for', spatial_model_type = name, spatial_model_id = "#{table_name}.id")
95
- joins %Q(INNER JOIN features "#{table_alias}"
96
- ON "#{table_alias}".spatial_model_type = '#{spatial_model_type}'
97
- AND "#{table_alias}".spatial_model_id IN (#{spatial_model_id}))
82
+ def other_features_union(other)
83
+ scope = Feature.select('ST_Union(geom) AS geom').where(:spatial_model_type => class_for(other))
84
+ scope = scope.where(:spatial_model_id => other) unless class_for(other) == other
85
+ return scope
98
86
  end
99
87
 
100
88
  # 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
@@ -107,8 +95,40 @@ module SpatialFeatures
107
95
  column_names.include? 'features_area'
108
96
  end
109
97
 
98
+ def area_in_square_meters
99
+ features.area_in_square_meters
100
+ end
101
+
110
102
  private
111
103
 
104
+ def cached_within_buffer_scope(other, buffer_in_meters, options)
105
+ # Don't use the cache if it doesn't exist
106
+ return all.extending(UncachedRelation) unless other.spatial_cache_for?(class_for(self), buffer_in_meters)
107
+
108
+ scope = cached_spatial_join(other).select("#{table_name}.*")
109
+ scope = scope.where("spatial_proximities.distance_in_meters <= ?", buffer_in_meters) if buffer_in_meters
110
+ scope = scope.select("spatial_proximities.distance_in_meters") if options[:distance]
111
+ scope = scope.select("spatial_proximities.intersection_area_in_square_meters") if options[:intersection_area]
112
+ return scope
113
+ end
114
+
115
+ def uncached_within_buffer_scope(other, buffer_in_meters, options)
116
+ scope = joins_features_for(other).select("#{table_name}.*")
117
+ scope = scope.where('ST_Intersects(features.geom, features_for_other.geom)') if buffer_in_meters == 0 # Optimize the 0 buffer case, ST_DWithin was slower in testing
118
+ scope = scope.where('ST_DWithin(features.geom, features_for_other.geom, ?)', buffer_in_meters) if buffer_in_meters.to_f > 0
119
+
120
+ # Ensure records with multiple features don't appear multiple times
121
+ if options[:distance] || options[:intersection_area]
122
+ scope = scope.group("#{table_name}.#{primary_key}") # Aggregate functions require grouping
123
+ else
124
+ scope = scope.distinct
125
+ end
126
+
127
+ scope = scope.select("MIN(ST_Distance(features.geom, features_for_other.geom)) AS distance_in_meters") if options[:distance]
128
+ scope = scope.select("ST_Area(ST_Intersection(ST_UNION(features.geom), ST_UNION(features_for_other.geom))) AS intersection_area_in_square_meters") if options[:intersection_area]
129
+ return scope
130
+ end
131
+
112
132
  def cached_spatial_join(other)
113
133
  other_class = class_for(other)
114
134
 
@@ -185,15 +205,8 @@ module SpatialFeatures
185
205
  @features_area_in_square_meters ||= features.area
186
206
  end
187
207
 
188
- def total_intersection_area_in_square_meters(klass, options = {})
189
- self.class
190
- .select(%Q(ST_Area(ST_Intersection(ST_Union(features_for.geog_lowres::geometry), ST_Union(features_for_other.geog_lowres::geometry))::geography) AS intersection_area_in_square_meters))
191
- .joins_features_for(klass)
192
- .where(:id => self.id)
193
- .where('ST_DWithin(features_for.geog_lowres, features_for_other.geog_lowres, 0)')
194
- .group("#{self.class.table_name}.id")
195
- .first
196
- .try(:intersection_area_in_square_meters) || 0
208
+ def total_intersection_area_in_square_meters(other)
209
+ features.total_intersection_area_in_square_meters(other.features)
197
210
  end
198
211
 
199
212
  def spatial_cache_for?(klass, buffer_in_meters)
@@ -27,6 +27,19 @@ class Feature < ActiveRecord::Base
27
27
  where(:feature_type => 'point')
28
28
  end
29
29
 
30
+ def self.area_in_square_meters
31
+ current_scope = all
32
+ unscoped { connection.select_value(select('ST_Area(ST_Union(geom))').from(current_scope, :features)).to_f }
33
+ end
34
+
35
+ def self.total_intersection_area_in_square_meters(other)
36
+ scope = join_other_features(other)
37
+ .where('ST_Intersects(features.geog_lowres, other_features.geog_lowres)')
38
+ .select('ST_Area(ST_Intersection(ST_Union(features.geog_lowres::geometry), ST_Union(other_features.geog_lowres::geometry))::geography) AS intersection_area_in_square_meters')
39
+
40
+ connection.select_value(scope).to_f
41
+ end
42
+
30
43
  def self.invalid
31
44
  select('features.*, ST_IsValidReason(geog::geometry) AS invalid_geometry_message').where.not('ST_IsValid(geog::geometry)')
32
45
  end
@@ -66,6 +79,10 @@ class Feature < ActiveRecord::Base
66
79
 
67
80
  private
68
81
 
82
+ def self.join_other_features(other)
83
+ joins('INNER JOIN features AS other_features ON true').where(:other_features => {:id => other})
84
+ end
85
+
69
86
  def geometry_is_valid
70
87
  if geog?
71
88
  instance = self.class.unscoped.invalid.from("(SELECT '#{sanitize_input_for_sql(self.geog)}'::geometry AS geog) #{self.class.table_name}").to_a.first
@@ -1,3 +1,3 @@
1
1
  module SpatialFeatures
2
- VERSION = "1.5.6"
2
+ VERSION = "1.5.9"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spatial_features
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.6
4
+ version: 1.5.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Wallace
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-02-26 00:00:00.000000000 Z
12
+ date: 2016-03-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -117,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
117
117
  version: '0'
118
118
  requirements: []
119
119
  rubyforge_project:
120
- rubygems_version: 2.4.5.1
120
+ rubygems_version: 2.4.6
121
121
  signing_key:
122
122
  specification_version: 4
123
123
  summary: Adds spatial methods to a model.