spatial_features 1.3.10 → 1.4.0

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: 8a4f197e3ba07d1cc425b2b8b8367603c1434643
4
- data.tar.gz: 3b6ad7bbd92da3ba7d2b781305463807181a8eb5
3
+ metadata.gz: 4825ec1877a47df3ef4c5ccb0d83ee648e093322
4
+ data.tar.gz: a9aa87b0eed16d9e1a12a629ece02ec164365b2c
5
5
  SHA512:
6
- metadata.gz: d03677e9c02f0d812740eab6e283a83a8b64f83a39d179199149a375dc5c6f6920c1ce63754b75c1f1975fbfee592553946585994bc77cc480c62e9067cfad54
7
- data.tar.gz: 5eb03110f86a432f45dea48fad7fdb4fdfd155178fc0e0f255a666b291830f732ba6ba02b6c7296761de13e84140ff38a093d1cd0458a2a92b5b11727b0aeefa
6
+ metadata.gz: c5b3b9952c83e2010e6138be8784b87047c11a8694a7ecf4178617415d253bbf39978ebd985b079e9dacc3bacedcb312e70467314f31bd12af27f18606c9bf0a
7
+ data.tar.gz: 88244ebe9df2c9d123de36aac02f9fe23cbebe946918fea36ac74c51f90bb74b7f044511ff5034aefb29851b158aa6392ffb765b41536ca66c3d1807efe61fb3
@@ -15,70 +15,117 @@ module SpatialFeatures
15
15
  end
16
16
 
17
17
  module ClassMethods
18
- # Add methods to generate cache keys for a record or all records of this class
19
- # NOTE: features are never updated, only deleted and created, therefore we can
20
- # tell if they have changed by finding the maximum id and count instead of needing timestamps
21
- def features_cache_key
22
- max_id, count = Feature.where(:spatial_model_type => self).pluck("MAX(id), COUNT(*)").first
23
- "#{name}/#{max_id}-#{count}"
24
- end
25
-
26
- def intersecting(other, options = {})
27
- within_buffer(other, 0, options)
28
- end
29
-
30
- def within_buffer(other, buffer_in_meters = 0, options = {})
31
- raise "Can't intersect with #{other} because it does not implement has_features" unless other.has_spatial_features?
32
-
33
- if options[:cache] != false # CACHED
34
- return all.extending(UncachedRelation) unless other.spatial_cache_for?(self, buffer_in_meters) # Don't use the cache if it doesn't exist
35
-
36
- scope = cached_spatial_join(other)
37
- .select("#{table_name}.*, spatial_proximities.distance_in_meters, spatial_proximities.intersection_area_in_square_meters")
38
-
39
- scope = scope.where("spatial_proximities.distance_in_meters <= ?", buffer_in_meters) if buffer_in_meters
40
- else # NON-CACHED
41
- scope = joins_features_for(other)
42
- .select("#{table_name}.*")
43
- .group("#{table_name}.#{primary_key}")
44
-
45
- scope = scope.where('ST_DWithin(features_for.geom, features_for_other.geom, ?)', buffer_in_meters) if buffer_in_meters
46
- scope = scope.select("MIN(ST_Distance(features_for.geom, features_for_other.geom)) AS distance_in_meters") if options[:distance]
47
- scope = scope.select("SUM(ST_Area(ST_Intersection(features_for.geom, features_for_other.geom))) AS intersection_area_in_square_meters") if options[:intersection_area]
48
- end
49
-
50
- return scope
51
- end
52
-
53
- def polygons
54
- Feature.polygons.where(:spatial_model_type => self.class)
55
- end
56
-
57
- def lines
58
- Feature.lines.where(:spatial_model_type => self.class)
59
- end
60
-
61
- def points
62
- Feature.points.where(:spatial_model_type => self.class)
63
- end
64
-
65
- def cached_spatial_join(other)
66
- raise "Cannot use cached spatial join for the same class" if other.class.name == self.name
67
-
68
- other_column = other.class.name < self.name ? :model_a : :model_b
69
- self_column = other_column == :model_a ? :model_b : :model_a
70
-
71
- joins("INNER JOIN spatial_proximities ON spatial_proximities.#{self_column}_type = '#{self}' AND spatial_proximities.#{self_column}_id = #{table_name}.id AND spatial_proximities.#{other_column}_type = '#{other.class}' AND spatial_proximities.#{other_column}_id = '#{other.id}'")
72
- end
73
-
74
- def joins_features_for(other, table_alias = 'features_for')
75
- joins_features(table_alias)
76
- .joins(%Q(INNER JOIN features "#{table_alias}_other" ON "#{table_alias}_other".spatial_model_type = '#{other.class.name}' AND "#{table_alias}_other".spatial_model_id = #{other.id}))
77
- end
78
-
79
- def joins_features(table_alias = 'features_for')
80
- joins(%Q(INNER JOIN features "#{table_alias}" ON "#{table_alias}".spatial_model_type = '#{name}' AND "#{table_alias}".spatial_model_id = #{table_name}.id))
81
- end
18
+ # Add methods to generate cache keys for a record or all records of this class
19
+ # NOTE: features are never updated, only deleted and created, therefore we can
20
+ # tell if they have changed by finding the maximum id and count instead of needing timestamps
21
+ def features_cache_key
22
+ max_id, count = features.pluck("MAX(id), COUNT(*)").first
23
+ "#{name}/#{max_id}-#{count}"
24
+ end
25
+
26
+ def intersecting(other, options = {})
27
+ within_buffer(other, 0, options)
28
+ end
29
+
30
+ def within_buffer(other, buffer_in_meters = 0, options = {})
31
+ if options[:cache] != false # CACHED
32
+ return all.extending(UncachedRelation) unless class_for(other).spatial_cache_for?(self, buffer_in_meters) # Don't use the cache if it doesn't exist
33
+
34
+ scope = cached_spatial_join(other)
35
+ .select("#{table_name}.*, spatial_proximities.distance_in_meters, spatial_proximities.intersection_area_in_square_meters")
36
+ .group("#{table_name}.#{primary_key}")
37
+
38
+ scope = scope.where("spatial_proximities.distance_in_meters <= ?", buffer_in_meters) if buffer_in_meters
39
+ else # NON-CACHED
40
+ scope = joins_features_for(other)
41
+ .select("#{table_name}.*")
42
+ .group("#{table_name}.#{primary_key}")
43
+
44
+ scope = scope.where('ST_DWithin(features_for.geom, features_for_other.geom, ?)', buffer_in_meters) if buffer_in_meters
45
+ scope = scope.select("MIN(ST_Distance(features_for.geom, features_for_other.geom)) AS distance_in_meters") if options[:distance]
46
+ scope = scope.select("SUM(ST_Area(ST_Intersection(features_for.geom, features_for_other.geom))) AS intersection_area_in_square_meters") if options[:intersection_area]
47
+ end
48
+
49
+ return scope
50
+ end
51
+
52
+ def covering(other)
53
+ scope = joins_features_for(other).select("#{table_name}.*").group("#{table_name}.#{primary_key}")
54
+ scope = scope.where('ST_Covers(features_for.geom, features_for_other.geom)')
55
+
56
+ return scope
57
+ end
58
+
59
+ def polygons
60
+ features.polygons
61
+ end
62
+
63
+ def lines
64
+ features.lines
65
+ end
66
+
67
+ def points
68
+ features.points
69
+ end
70
+
71
+ def spatial_cache_for?(other, buffer_in_meters)
72
+ if cache = spatial_cache_for(other)
73
+ return cache.intersection_cache_distance.nil? if buffer_in_meters.nil? # cache must be total if no buffer_in_meters
74
+ return false if cache.stale? # cache must be for current features
75
+ return true if cache.intersection_cache_distance.nil? # always good if cache is total
76
+
77
+ return buffer_in_meters <= cache.intersection_cache_distance
78
+ else
79
+ return false
80
+ end
81
+ end
82
+
83
+
84
+ def features
85
+ Feature.where(:spatial_model_type => self, :spatial_model_id => all)
86
+ end
87
+
88
+ def joins_features_for(other, table_alias = 'features_for')
89
+ joins_features(table_alias)
90
+ .joins(%Q(INNER JOIN features "#{table_alias}_other" ON "#{table_alias}_other".spatial_model_type = '#{class_for(other)}' AND "#{table_alias}_other".spatial_model_id IN (#{ids_sql_for(other)})))
91
+ end
92
+
93
+ def joins_features(table_alias = 'features_for')
94
+ joins(%Q(INNER JOIN features "#{table_alias}" ON "#{table_alias}".spatial_model_type = '#{name}' AND "#{table_alias}".spatial_model_id = #{table_name}.id))
95
+ end
96
+
97
+ private
98
+
99
+ def cached_spatial_join(other)
100
+ other_class = class_for(other)
101
+
102
+ raise "Cannot use cached spatial join for the same class" if self == other_class
103
+
104
+ other_column = other_class.name < self.name ? :model_a : :model_b
105
+ self_column = other_column == :model_a ? :model_b : :model_a
106
+
107
+ joins("INNER JOIN spatial_proximities ON spatial_proximities.#{self_column}_type = '#{self}' AND spatial_proximities.#{self_column}_id = #{table_name}.id AND spatial_proximities.#{other_column}_type = '#{other_class}' AND spatial_proximities.#{other_column}_id IN (#{ids_sql_for(other)})")
108
+ end
109
+
110
+ def spatial_cache_for(other)
111
+ SpatialCache.where(:spatial_model_type => self, :intersection_model_type => class_for(other)).first
112
+ end
113
+
114
+ # Returns the class for the given, class, scope, or record
115
+ def class_for(other)
116
+ case other
117
+ when ActiveRecord::Base
118
+ other.class
119
+ when ActiveRecord::Relation
120
+ other.klass
121
+ else
122
+ other
123
+ end
124
+ end
125
+
126
+ def ids_sql_for(other)
127
+ other.is_a?(ActiveRecord::Base) ? other.id : other.unscope(:select).select(:id).to_sql
128
+ end
82
129
  end
83
130
 
84
131
  module InstanceMethods
@@ -88,78 +135,65 @@ module SpatialFeatures
88
135
 
89
136
  def features_cache_key
90
137
  max_id, count = features.pluck("MAX(id), COUNT(*)").first
91
- "#{self.class.name}/#{self.id}-#{max_id}-#{count}"
92
- end
93
-
94
- def polygons?
95
- !features.polygons.empty?
96
- end
97
-
98
- def lines?
99
- !features.lines.empty?
100
- end
101
-
102
- def points?
103
- !features.points.empty?
104
- end
105
-
106
- def features?
107
- features.present?
108
- end
109
-
110
- # 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
111
- def has_spatial_features_hash?
112
- respond_to?(:features_hash)
113
- end
114
-
115
- def intersects?(other)
116
- self.class.intersecting(other).exists?(self)
117
- end
118
-
119
- def total_intersection_area_in_square_meters(klass, options = {})
120
- self.class
121
- .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))
122
- .joins(%Q(INNER JOIN features "features_for" ON "features_for".spatial_model_type = '#{self.class}' AND "features_for".spatial_model_id = #{self.class.table_name}.id))
123
- .joins(%Q(INNER JOIN features "features_for_other" ON "features_for_other".spatial_model_type = '#{klass}'))
124
- .where(:id => self.id)
125
- .where('ST_DWithin(features_for.geog_lowres, features_for_other.geog_lowres, 0)')
126
- .group("#{self.class.table_name}.id")
127
- .first
128
- .try(:intersection_area_in_square_meters) || 0
129
- end
130
-
131
- def total_intersection_area_in_hectares(klass)
132
- Formatters::HECTARES.call(total_intersection_area_in_square_meters(klass))
133
- end
134
-
135
- def total_intersection_area_percentage(klass)
136
- return 0.0 unless features_area_in_square_meters > 0
137
-
138
- ((total_intersection_area_in_square_meters(klass) / features_area_in_square_meters) * 100).round(1)
139
- end
140
-
141
- def features_area_in_square_meters
142
- @features_area_in_square_meters ||= features.sum('features.area')
143
- end
144
-
145
- def features_area_in_hectares
146
- Formatters::HECTARES.call(features_area_in_square_meters)
147
- end
148
-
149
- def spatial_cache_for(klass)
150
- spatial_cache.where(:intersection_model_type => klass).first
151
- end
152
-
153
- def spatial_cache_for?(klass, buffer_in_meters)
154
- if cache = spatial_cache_for(klass)
155
- return cache.intersection_cache_distance.nil? if buffer_in_meters.nil? # cache must be total if no buffer_in_meters
156
- return false if cache.stale? # cache must be for current features
157
- return true if cache.intersection_cache_distance.nil? # always good if cache is total
138
+ "#{self.class.name}/#{self.id}-#{max_id}-#{count}"
139
+ end
140
+
141
+ def polygons?
142
+ !features.polygons.empty?
143
+ end
144
+
145
+ def lines?
146
+ !features.lines.empty?
147
+ end
148
+
149
+ def points?
150
+ !features.points.empty?
151
+ end
152
+
153
+ def features?
154
+ features.present?
155
+ end
156
+
157
+ # 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
158
+ def has_spatial_features_hash?
159
+ respond_to?(:features_hash)
160
+ end
161
+
162
+ def covers?(other)
163
+ self.class.covering(other).exists?(self)
164
+ end
165
+
166
+ def intersects?(other)
167
+ self.class.intersecting(other).exists?(self)
168
+ end
169
+
170
+ def total_intersection_area_in_square_meters(klass, options = {})
171
+ self.class
172
+ .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))
173
+ .joins_features_for(klass)
174
+ .where(:id => self.id)
175
+ .where('ST_DWithin(features_for.geog_lowres, features_for_other.geog_lowres, 0)')
176
+ .group("#{self.class.table_name}.id")
177
+ .first
178
+ .try(:intersection_area_in_square_meters) || 0
179
+ end
158
180
 
159
- return buffer_in_meters <= cache.intersection_cache_distance
160
- else
161
- return false
162
- end
163
- end
181
+ def total_intersection_area_in_hectares(klass)
182
+ Formatters::HECTARES.call(total_intersection_area_in_square_meters(klass))
183
+ end
184
+
185
+ def total_intersection_area_percentage(klass)
186
+ return 0.0 unless features_area_in_square_meters > 0
187
+
188
+ ((total_intersection_area_in_square_meters(klass) / features_area_in_square_meters) * 100).round(1)
189
+ end
190
+
191
+ def features_area_in_square_meters
192
+ @features_area_in_square_meters ||= features.sum('features.area')
193
+ end
194
+
195
+ def features_area_in_hectares
196
+ Formatters::HECTARES.call(features_area_in_square_meters)
197
+ end
164
198
  end
165
199
  end
@@ -1,3 +1,3 @@
1
1
  module SpatialFeatures
2
- VERSION = "1.3.10"
2
+ VERSION = "1.4.0"
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.3.10
4
+ version: 1.4.0
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: 2015-07-29 00:00:00.000000000 Z
12
+ date: 2015-12-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -138,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
138
138
  version: '0'
139
139
  requirements: []
140
140
  rubyforge_project:
141
- rubygems_version: 2.4.7
141
+ rubygems_version: 2.4.6
142
142
  signing_key:
143
143
  specification_version: 4
144
144
  summary: Adds spatial methods to a model.