spatial_features 2.2.4 → 2.3.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/app/models/spatial_cache.rb +5 -0
- data/app/models/spatial_proximity.rb +7 -0
- data/lib/spatial_features/caching.rb +3 -6
- data/lib/spatial_features/controller_helpers/spatial_extensions.rb +2 -2
- data/lib/spatial_features/has_spatial_features/feature_import.rb +46 -14
- data/lib/spatial_features/has_spatial_features/queued_spatial_processing.rb +43 -0
- data/lib/spatial_features/has_spatial_features.rb +10 -39
- data/lib/spatial_features/utils.rb +32 -0
- data/lib/spatial_features/version.rb +1 -1
- data/lib/spatial_features.rb +3 -3
- metadata +4 -4
- data/lib/spatial_features/has_spatial_features/delayed_feature_import.rb +0 -37
- data/lib/spatial_features/workers/update_features_job.rb +0 -51
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7c370633815f2fd2637a8d5f8c47903b13ec623e
|
|
4
|
+
data.tar.gz: 182d9a77aca580e40aa999a2784e4bb7b02515de
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c38c86a9daeb07fd1b0eb308e02941d29d67a14e8e62cd7cbc4833887f5a5f5fc6557923f60eec19566a84d9c0a9b7d38f014693d8e4a458c62dfd2765efbe01
|
|
7
|
+
data.tar.gz: 14ff222d08e0114323b97501855feed7790187cd2990a06466a631e54212e138d33927769942f7e657f35bf58048a11596e4b57d02feadbe9afe3db740c1ee13
|
data/app/models/spatial_cache.rb
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
class SpatialCache < ActiveRecord::Base
|
|
2
2
|
belongs_to :spatial_model, :polymorphic => true, :inverse_of => :spatial_cache
|
|
3
3
|
|
|
4
|
+
def self.between(spatial_model, klass)
|
|
5
|
+
where(SpatialFeatures::Utils.polymorphic_condition(spatial_model, 'spatial_model'))
|
|
6
|
+
.where(SpatialFeatures::Utils.polymorphic_condition(klass, 'intersection_model'))
|
|
7
|
+
end
|
|
8
|
+
|
|
4
9
|
def stale?
|
|
5
10
|
spatial_model.has_spatial_features_hash? && self.features_hash != spatial_model.features_hash
|
|
6
11
|
end
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
class SpatialProximity < ActiveRecord::Base
|
|
2
2
|
belongs_to :model_a, :polymorphic => true
|
|
3
3
|
belongs_to :model_b, :polymorphic => true
|
|
4
|
+
|
|
5
|
+
def self.between(scope_1, scope_2)
|
|
6
|
+
where <<-SQL.squish
|
|
7
|
+
(#{SpatialFeatures::Utils.polymorphic_condition(scope_1, 'model_a')} AND #{SpatialFeatures::Utils.polymorphic_condition(scope_2, 'model_b')}) OR
|
|
8
|
+
(#{SpatialFeatures::Utils.polymorphic_condition(scope_2, 'model_a')} AND #{SpatialFeatures::Utils.polymorphic_condition(scope_1, 'model_b')})
|
|
9
|
+
SQL
|
|
10
|
+
end
|
|
4
11
|
end
|
|
@@ -53,17 +53,14 @@ module SpatialFeatures
|
|
|
53
53
|
SpatialCache.delete_all
|
|
54
54
|
SpatialProximity.delete_all
|
|
55
55
|
else
|
|
56
|
-
SpatialCache.
|
|
57
|
-
|
|
58
|
-
SpatialProximity.where(:model_a_type => klass, :model_b_type => clazz).delete_all
|
|
59
|
-
SpatialProximity.where(:model_a_type => clazz, :model_b_type => klass).delete_all
|
|
56
|
+
SpatialCache.between(klass, clazz).delete_all
|
|
57
|
+
SpatialProximity.between(klass, clazz).delete_all
|
|
60
58
|
end
|
|
61
59
|
end
|
|
62
60
|
|
|
63
61
|
def self.clear_record_cache(record, klass)
|
|
64
62
|
record.spatial_cache.where(:intersection_model_type => klass).delete_all
|
|
65
|
-
SpatialProximity.
|
|
66
|
-
SpatialProximity.where(:model_b_type => record.class, :model_b_id => record.id, :model_a_type => klass).delete_all
|
|
63
|
+
SpatialProximity.between(record, klass).delete_all
|
|
67
64
|
end
|
|
68
65
|
|
|
69
66
|
def self.create_spatial_proximities(record, klass)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
module SpatialExtensions
|
|
2
2
|
private
|
|
3
3
|
|
|
4
|
-
def abstract_refresh_geometry_action(models
|
|
4
|
+
def abstract_refresh_geometry_action(models)
|
|
5
5
|
Array.wrap(models).each do |model|
|
|
6
6
|
model.failed_feature_update_jobs.destroy_all
|
|
7
|
-
model.
|
|
7
|
+
model.delay_update_features!
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
redirect_to :back
|
|
@@ -3,14 +3,23 @@ require 'digest/md5'
|
|
|
3
3
|
module SpatialFeatures
|
|
4
4
|
module FeatureImport
|
|
5
5
|
extend ActiveSupport::Concern
|
|
6
|
+
include QueuedSpatialProcessing
|
|
6
7
|
|
|
7
8
|
included do
|
|
8
9
|
extend ActiveModel::Callbacks
|
|
9
10
|
define_model_callbacks :update_features
|
|
10
|
-
spatial_features_options.reverse_merge!(:import => {})
|
|
11
|
+
spatial_features_options.reverse_merge!(:import => {}, spatial_cache: [])
|
|
11
12
|
end
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
module ClassMethods
|
|
15
|
+
def update_features!(skip_invalid: false)
|
|
16
|
+
find_each do |record|
|
|
17
|
+
record.update_features!(skip_invalid: skip_invalid)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def update_features!(skip_invalid: false, **options)
|
|
14
23
|
options = options.reverse_merge(spatial_features_options)
|
|
15
24
|
|
|
16
25
|
ActiveRecord::Base.transaction do
|
|
@@ -21,13 +30,40 @@ module SpatialFeatures
|
|
|
21
30
|
|
|
22
31
|
run_callbacks :update_features do
|
|
23
32
|
import_features(imports, skip_invalid)
|
|
24
|
-
|
|
33
|
+
update_features_cache_key(cache_key)
|
|
34
|
+
update_features_area
|
|
35
|
+
|
|
36
|
+
if options[:spatial_cache].present? && options[:queue_spatial_cache]
|
|
37
|
+
queue_update_spatial_cache(options.slice(:spatial_cache))
|
|
38
|
+
else
|
|
39
|
+
update_spatial_cache(options.slice(:spatial_cache))
|
|
40
|
+
end
|
|
25
41
|
end
|
|
26
42
|
|
|
27
43
|
return true
|
|
28
44
|
end
|
|
29
45
|
end
|
|
30
46
|
|
|
47
|
+
def update_features_cache_key(cache_key)
|
|
48
|
+
return unless has_spatial_features_hash?
|
|
49
|
+
self.features_hash = cache_key
|
|
50
|
+
update_column(:features_hash, features_hash) unless new_record?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def update_features_area
|
|
54
|
+
return unless has_spatial_features_hash?
|
|
55
|
+
self.features_area = features.area(:cache => false)
|
|
56
|
+
update_column :features_area, features_area unless new_record?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def update_spatial_cache(options = {})
|
|
60
|
+
options = options.reverse_merge(spatial_features_options)
|
|
61
|
+
|
|
62
|
+
Array.wrap(options[:spatial_cache]).select(&:present?).each do |klass|
|
|
63
|
+
SpatialFeatures.cache_record_proximity(self, klass.to_s.constantize)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
31
67
|
private
|
|
32
68
|
|
|
33
69
|
def spatial_feature_imports(import_options, make_valid)
|
|
@@ -48,12 +84,14 @@ module SpatialFeatures
|
|
|
48
84
|
feature.save
|
|
49
85
|
end
|
|
50
86
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
end
|
|
87
|
+
errors = imports.flat_map(&:errors)
|
|
88
|
+
invalid.each do |feature|
|
|
89
|
+
errors << "Feature #{feature.name}: #{feature.errors.full_messages.to_sentence}"
|
|
90
|
+
end
|
|
56
91
|
|
|
92
|
+
if skip_invalid && errors.present?
|
|
93
|
+
Rails.logger.warn "Error updating #{self.class} #{self.id}. #{errors.to_sentence}"
|
|
94
|
+
elsif errors.present?
|
|
57
95
|
raise ImportError, "Error updating #{self.class} #{self.id}. #{errors.to_sentence}"
|
|
58
96
|
end
|
|
59
97
|
|
|
@@ -63,12 +101,6 @@ module SpatialFeatures
|
|
|
63
101
|
def features_cache_key_matches?(cache_key)
|
|
64
102
|
has_spatial_features_hash? && cache_key == features_hash
|
|
65
103
|
end
|
|
66
|
-
|
|
67
|
-
def set_features_cache_key(cache_key)
|
|
68
|
-
return unless has_spatial_features_hash?
|
|
69
|
-
self.features_hash = cache_key
|
|
70
|
-
update_column(:features_hash, cache_key) unless new_record?
|
|
71
|
-
end
|
|
72
104
|
end
|
|
73
105
|
|
|
74
106
|
class ImportError < StandardError; end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module SpatialFeatures
|
|
2
|
+
module QueuedSpatialProcessing
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
def queue_update_spatial_cache(*args)
|
|
6
|
+
queue_spatial_task('update_spatial_cache', *args)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def delay_update_features!(*args)
|
|
10
|
+
queue_spatial_task('update_features!', *args)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def updating_features?
|
|
14
|
+
running_feature_update_jobs.exists?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def feature_update_error
|
|
18
|
+
(failed_feature_update_jobs.first.try(:last_error) || '').split("\n").first
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def running_feature_update_jobs
|
|
22
|
+
spatial_processing_jobs('update_features!').where(failed_at: nil)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def failed_feature_update_jobs
|
|
26
|
+
spatial_processing_jobs('update_features!').where.not(failed_at: nil)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def spatial_processing_jobs(suffix = nil)
|
|
30
|
+
Delayed::Job.where('queue LIKE ?', "#{spatial_processing_queue_name}#{suffix}%")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def queue_spatial_task(method_name, *args)
|
|
36
|
+
delay(:queue => spatial_processing_queue_name + method_name).send(method_name, *args)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def spatial_processing_queue_name
|
|
40
|
+
"#{self.class}/#{self.id}/"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -7,7 +7,7 @@ module SpatialFeatures
|
|
|
7
7
|
|
|
8
8
|
extend ClassMethods
|
|
9
9
|
include InstanceMethods
|
|
10
|
-
include
|
|
10
|
+
include FeatureImport
|
|
11
11
|
|
|
12
12
|
has_many :features, lambda { extending FeaturesAssociationExtensions }, :as => :spatial_model, :dependent => :delete_all
|
|
13
13
|
|
|
@@ -22,8 +22,6 @@ module SpatialFeatures
|
|
|
22
22
|
has_many :model_a_spatial_proximities, :as => :model_a, :class_name => 'SpatialProximity', :dependent => :delete_all
|
|
23
23
|
has_many :model_b_spatial_proximities, :as => :model_b, :class_name => 'SpatialProximity', :dependent => :delete_all
|
|
24
24
|
|
|
25
|
-
after_save :update_features_area, :if => :features_hash_changed? if has_features_area? && has_spatial_features_hash?
|
|
26
|
-
|
|
27
25
|
delegate :has_spatial_features_hash?, :has_features_area?, :to => self
|
|
28
26
|
end
|
|
29
27
|
|
|
@@ -100,7 +98,7 @@ module SpatialFeatures
|
|
|
100
98
|
options = options.reverse_merge(:columns => "#{table_name}.*")
|
|
101
99
|
|
|
102
100
|
# Don't use the cache if it doesn't exist
|
|
103
|
-
return all.extending(UncachedRelation) unless other.spatial_cache_for?(
|
|
101
|
+
return all.extending(UncachedRelation) unless other.spatial_cache_for?(Utils.class_of(self), buffer_in_meters)
|
|
104
102
|
|
|
105
103
|
scope = cached_spatial_join(other)
|
|
106
104
|
scope = scope.select(options[:columns])
|
|
@@ -111,14 +109,14 @@ module SpatialFeatures
|
|
|
111
109
|
end
|
|
112
110
|
|
|
113
111
|
def cached_spatial_join(other)
|
|
114
|
-
other_class =
|
|
112
|
+
other_class = Utils.class_of(other)
|
|
115
113
|
|
|
116
114
|
raise "Cannot use cached spatial join for the same class" if self == other_class
|
|
117
115
|
|
|
118
116
|
other_column = other_class.name < self.name ? :model_a : :model_b
|
|
119
117
|
self_column = other_column == :model_a ? :model_b : :model_a
|
|
120
118
|
|
|
121
|
-
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 (#{
|
|
119
|
+
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 (#{Utils.id_sql(other)})")
|
|
122
120
|
end
|
|
123
121
|
|
|
124
122
|
def uncached_within_buffer_scope(other, buffer_in_meters, options)
|
|
@@ -153,30 +151,10 @@ module SpatialFeatures
|
|
|
153
151
|
|
|
154
152
|
def features_scope(other)
|
|
155
153
|
scope = Feature
|
|
156
|
-
scope = scope.where(:spatial_model_type =>
|
|
157
|
-
scope = scope.where(:spatial_model_id => other) unless
|
|
154
|
+
scope = scope.where(:spatial_model_type => Utils.class_of(other))
|
|
155
|
+
scope = scope.where(:spatial_model_id => other) unless Utils.class_of(other) == other
|
|
158
156
|
return scope
|
|
159
157
|
end
|
|
160
|
-
|
|
161
|
-
# Returns the class for the given, class, scope, or record
|
|
162
|
-
def class_for(other)
|
|
163
|
-
case other
|
|
164
|
-
when ActiveRecord::Base
|
|
165
|
-
other.class
|
|
166
|
-
when ActiveRecord::Relation
|
|
167
|
-
other.klass
|
|
168
|
-
else
|
|
169
|
-
other
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def ids_sql_for(other)
|
|
174
|
-
if other.is_a?(ActiveRecord::Base)
|
|
175
|
-
other.id || '0'
|
|
176
|
-
else
|
|
177
|
-
other.unscope(:select).select(:id).to_sql
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
158
|
end
|
|
181
159
|
|
|
182
160
|
module InstanceMethods
|
|
@@ -221,11 +199,12 @@ module SpatialFeatures
|
|
|
221
199
|
|
|
222
200
|
def total_intersection_area_in_square_meters(other)
|
|
223
201
|
other = other.intersecting(self) unless other.is_a?(ActiveRecord::Base)
|
|
202
|
+
return features.area if spatial_cache_for?(other, 0) && SpatialProximity.between(self, other).where('intersection_area_in_square_meters >= ?', features.area).exists?
|
|
224
203
|
return features.total_intersection_area_in_square_meters(other.features)
|
|
225
204
|
end
|
|
226
205
|
|
|
227
|
-
def spatial_cache_for?(
|
|
228
|
-
if cache =
|
|
206
|
+
def spatial_cache_for?(other, buffer_in_meters)
|
|
207
|
+
if cache = spatial_cache.between(self, SpatialFeatures::Utils.class_of(other)).first
|
|
229
208
|
return cache.intersection_cache_distance.nil? if buffer_in_meters.nil? # cache must be total if no buffer_in_meters
|
|
230
209
|
return false if cache.stale? # cache must be for current features
|
|
231
210
|
return true if cache.intersection_cache_distance.nil? # always good if cache is total
|
|
@@ -235,14 +214,6 @@ module SpatialFeatures
|
|
|
235
214
|
return false
|
|
236
215
|
end
|
|
237
216
|
end
|
|
238
|
-
|
|
239
|
-
def spatial_cache_for(klass)
|
|
240
|
-
spatial_cache.where(:intersection_model_type => klass).first
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
def update_features_area
|
|
244
|
-
update_column :features_area, features.area(:cache => false)
|
|
245
|
-
end
|
|
246
217
|
end
|
|
247
218
|
|
|
248
219
|
module FeaturesAssociationExtensions
|
|
@@ -250,7 +221,7 @@ module SpatialFeatures
|
|
|
250
221
|
if options[:cache] == false || !proxy_association.owner.class.has_features_area?
|
|
251
222
|
area_in_square_meters
|
|
252
223
|
else
|
|
253
|
-
proxy_association.owner.features_area.to_f
|
|
224
|
+
(proxy_association.owner.features_area || area_in_square_meters).to_f
|
|
254
225
|
end
|
|
255
226
|
end
|
|
256
227
|
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module SpatialFeatures
|
|
2
|
+
module Utils
|
|
3
|
+
extend self
|
|
4
|
+
|
|
5
|
+
def polymorphic_condition(scope, column_name)
|
|
6
|
+
sql = "#{column_name}_type = ?"
|
|
7
|
+
sql << " AND #{column_name}_id IN (#{id_sql(scope)})" unless scope.is_a?(Class)
|
|
8
|
+
|
|
9
|
+
return class_of(scope).send :sanitize_sql, [sql, class_of(scope)]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Returns the class for the given, class, scope, or record
|
|
13
|
+
def class_of(object)
|
|
14
|
+
case object
|
|
15
|
+
when ActiveRecord::Base
|
|
16
|
+
object.class
|
|
17
|
+
when ActiveRecord::Relation
|
|
18
|
+
object.klass
|
|
19
|
+
else
|
|
20
|
+
object
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def id_sql(object)
|
|
25
|
+
if object.is_a?(ActiveRecord::Base)
|
|
26
|
+
object.id || '0'
|
|
27
|
+
else
|
|
28
|
+
object.unscope(:select).select(:id).to_sql
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/spatial_features.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# Gems
|
|
2
|
+
require 'delayed_job_active_record'
|
|
2
3
|
require 'rgeo/shapefile'
|
|
3
4
|
require 'nokogiri'
|
|
4
5
|
require 'zip'
|
|
@@ -12,10 +13,11 @@ require 'spatial_features/venn_polygons'
|
|
|
12
13
|
require 'spatial_features/controller_helpers/spatial_extensions'
|
|
13
14
|
require 'spatial_features/download'
|
|
14
15
|
require 'spatial_features/unzip'
|
|
16
|
+
require 'spatial_features/utils'
|
|
15
17
|
|
|
16
18
|
require 'spatial_features/has_spatial_features'
|
|
19
|
+
require 'spatial_features/has_spatial_features/queued_spatial_processing'
|
|
17
20
|
require 'spatial_features/has_spatial_features/feature_import'
|
|
18
|
-
require 'spatial_features/has_spatial_features/delayed_feature_import'
|
|
19
21
|
|
|
20
22
|
require 'spatial_features/has_fusion_table_features'
|
|
21
23
|
require 'spatial_features/has_fusion_table_features/api'
|
|
@@ -30,8 +32,6 @@ require 'spatial_features/importers/kml_file_arcgis'
|
|
|
30
32
|
require 'spatial_features/importers/geomark'
|
|
31
33
|
require 'spatial_features/importers/shapefile'
|
|
32
34
|
|
|
33
|
-
require 'spatial_features/workers/update_features_job'
|
|
34
|
-
|
|
35
35
|
require 'spatial_features/engine'
|
|
36
36
|
|
|
37
37
|
module SpatialFeatures
|
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: 2.
|
|
4
|
+
version: 2.3.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: 2016-12-
|
|
12
|
+
date: 2016-12-13 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: rails
|
|
@@ -161,8 +161,8 @@ files:
|
|
|
161
161
|
- lib/spatial_features/has_fusion_table_features/configuration.rb
|
|
162
162
|
- lib/spatial_features/has_fusion_table_features/service.rb
|
|
163
163
|
- lib/spatial_features/has_spatial_features.rb
|
|
164
|
-
- lib/spatial_features/has_spatial_features/delayed_feature_import.rb
|
|
165
164
|
- lib/spatial_features/has_spatial_features/feature_import.rb
|
|
165
|
+
- lib/spatial_features/has_spatial_features/queued_spatial_processing.rb
|
|
166
166
|
- lib/spatial_features/importers/base.rb
|
|
167
167
|
- lib/spatial_features/importers/file.rb
|
|
168
168
|
- lib/spatial_features/importers/geomark.rb
|
|
@@ -171,9 +171,9 @@ files:
|
|
|
171
171
|
- lib/spatial_features/importers/kml_file_arcgis.rb
|
|
172
172
|
- lib/spatial_features/importers/shapefile.rb
|
|
173
173
|
- lib/spatial_features/unzip.rb
|
|
174
|
+
- lib/spatial_features/utils.rb
|
|
174
175
|
- lib/spatial_features/venn_polygons.rb
|
|
175
176
|
- lib/spatial_features/version.rb
|
|
176
|
-
- lib/spatial_features/workers/update_features_job.rb
|
|
177
177
|
- lib/tasks/spatial_features_tasks.rake
|
|
178
178
|
homepage: https://github.com/culturecode/spatial_features
|
|
179
179
|
licenses:
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
module SpatialFeatures
|
|
2
|
-
module DelayedFeatureImport
|
|
3
|
-
extend ActiveSupport::Concern
|
|
4
|
-
include FeatureImport
|
|
5
|
-
|
|
6
|
-
def queue_feature_update!(options = {})
|
|
7
|
-
job = UpdateFeaturesJob.new(options.merge :spatial_model_type => self.class, :spatial_model_id => self.id)
|
|
8
|
-
Delayed::Job.enqueue(job, :queue => delayed_jobs_queue_name)
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def updating_features?
|
|
12
|
-
running_feature_update_jobs.exists?
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def feature_update_error
|
|
16
|
-
(failed_feature_update_jobs.first.try(:last_error) || '').split("\n").first
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def running_feature_update_jobs
|
|
20
|
-
feature_update_jobs.where(failed_at: nil)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def failed_feature_update_jobs
|
|
24
|
-
feature_update_jobs.where.not(failed_at: nil)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def feature_update_jobs
|
|
28
|
-
Delayed::Job.where(queue: delayed_jobs_queue_name)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
private
|
|
32
|
-
|
|
33
|
-
def delayed_jobs_queue_name
|
|
34
|
-
"#{self.class}/#{self.id}/update_features"
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
class UpdateFeaturesJob < Struct.new(:options)
|
|
2
|
-
def perform
|
|
3
|
-
model = options[:spatial_model_type].find(options[:spatial_model_id])
|
|
4
|
-
|
|
5
|
-
if model.update_features!
|
|
6
|
-
Array(options[:cache_classes]).each {|klass| SpatialFeatures.cache_record_proximity(model, klass) }
|
|
7
|
-
after_feature_update(model)
|
|
8
|
-
end
|
|
9
|
-
rescue => e
|
|
10
|
-
raise "Can't refresh geometry: #{normalize_message(e.message)}"
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
private
|
|
14
|
-
|
|
15
|
-
def after_feature_update(model)
|
|
16
|
-
# stub to be overridden
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
NUMBER_REGEX = /-?\d+\.\d+/
|
|
20
|
-
|
|
21
|
-
def normalize_message(message)
|
|
22
|
-
normalized_messages = []
|
|
23
|
-
|
|
24
|
-
if message =~ /invalid KML representation/
|
|
25
|
-
normalized_messages += invalid_kml_reason(message).presence || ["KML importer received invalid geometry."]
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
if message =~ /Self-intersection/
|
|
29
|
-
normalized_messages += message.scan(/\[(#{NUMBER_REGEX}) (#{NUMBER_REGEX})\]/).collect do |lng, lat|
|
|
30
|
-
"Self-intersection at #{lng},#{lat}"
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
if normalized_messages.many?
|
|
35
|
-
return '<ul><li>' + normalized_messages.join('</li><li>') + '</li></ul>'
|
|
36
|
-
elsif normalized_messages.present?
|
|
37
|
-
normalized_messages.first
|
|
38
|
-
else
|
|
39
|
-
return message
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
COORDINATE_REGEX = /<LinearRing><coordinates>\s*((?:#{NUMBER_REGEX},#{NUMBER_REGEX},#{NUMBER_REGEX}\s*)+)<\/coordinates><\/LinearRing>/
|
|
44
|
-
def invalid_kml_reason(message)
|
|
45
|
-
message.scan(COORDINATE_REGEX).collect do |match|
|
|
46
|
-
coords = match[0].remove(/,0\.0+/).split(/\s+/).chunk {|c| c }.map(&:first)
|
|
47
|
-
|
|
48
|
-
"Sliver polygon detected at #{coords.first}" if coords.length < 4
|
|
49
|
-
end.compact
|
|
50
|
-
end
|
|
51
|
-
end
|