spatial_features 1.5.6 → 1.5.9

Sign up to get free protection for your applications and to get access to all the features.
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.