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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6cbe6788f92697f84521cb44e3fd3189b7b4d85d
|
4
|
+
data.tar.gz: b4e5fe7c097c9f78cb600d886b24be42c87903cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
42
|
-
|
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(
|
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
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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(
|
189
|
-
|
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
|
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.
|
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-
|
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.
|
120
|
+
rubygems_version: 2.4.6
|
121
121
|
signing_key:
|
122
122
|
specification_version: 4
|
123
123
|
summary: Adds spatial methods to a model.
|