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:
|
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.
|