spatial_features 1.3.10 → 1.4.0
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 +4 -4
- data/lib/spatial_features/has_spatial_features.rb +170 -136
- data/lib/spatial_features/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4825ec1877a47df3ef4c5ccb0d83ee648e093322
|
4
|
+
data.tar.gz: a9aa87b0eed16d9e1a12a629ece02ec164365b2c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
max_id, count =
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
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
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
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
|
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.
|
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-
|
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.
|
141
|
+
rubygems_version: 2.4.6
|
142
142
|
signing_key:
|
143
143
|
specification_version: 4
|
144
144
|
summary: Adds spatial methods to a model.
|