spatial_features 3.8.2 → 3.10.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/abstract_feature.rb +13 -13
- data/app/models/aggregate_feature.rb +2 -2
- data/app/models/feature.rb +6 -6
- data/app/models/spatial_cache.rb +2 -2
- data/app/models/spatial_proximity.rb +2 -2
- data/lib/spatial_features/caching.rb +3 -3
- data/lib/spatial_features/controller_helpers/spatial_extensions.rb +6 -6
- data/lib/spatial_features/download.rb +1 -1
- data/lib/spatial_features/has_spatial_features/feature_import.rb +3 -3
- data/lib/spatial_features/has_spatial_features/queued_spatial_processing.rb +4 -4
- data/lib/spatial_features/has_spatial_features.rb +16 -16
- data/lib/spatial_features/importers/esri_geo_json.rb +0 -1
- data/lib/spatial_features/importers/geo_json.rb +4 -4
- data/lib/spatial_features/importers/kml.rb +2 -2
- data/lib/spatial_features/importers/kml_file_arcgis.rb +0 -1
- data/lib/spatial_features/importers/shapefile.rb +3 -3
- data/lib/spatial_features/unzip.rb +8 -2
- data/lib/spatial_features/venn_polygons.rb +3 -1
- data/lib/spatial_features/version.rb +1 -1
- metadata +24 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a5ada735515d4ec029917c5d00994254804e9b270c65093f32672d851fb1a452
|
|
4
|
+
data.tar.gz: 1828f79799ab6e91bfcd0503aba6091b9e0120d33aa28a05158f729dc1738e0f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ff7491b8acd5875ad23909d2ce988ea793d00707be4ece7d9cb7b700c4a1e064db949778ccc029f948f3ea389d91b7e8c54dceac55fb3206bd928bd97319e760
|
|
7
|
+
data.tar.gz: f1256bec3bb256e2789e3e7e0462f8f710f23fc1a493814b8a9126c3cd5495680394f588ca5121908875847d5ae3748a10a0e65d5b0f84d7ee65a5304a238c4b
|
|
@@ -7,7 +7,7 @@ class AbstractFeature < ActiveRecord::Base
|
|
|
7
7
|
class_attribute :lowres_simplification
|
|
8
8
|
self.lowres_simplification = 2 # Threshold in meters
|
|
9
9
|
|
|
10
|
-
belongs_to :spatial_model, :
|
|
10
|
+
belongs_to :spatial_model, polymorphic: :true, autosave: false
|
|
11
11
|
|
|
12
12
|
attr_writer :make_valid
|
|
13
13
|
|
|
@@ -16,7 +16,7 @@ class AbstractFeature < ActiveRecord::Base
|
|
|
16
16
|
validates_presence_of :geog
|
|
17
17
|
validate :validate_geometry, if: :will_save_change_to_geog?
|
|
18
18
|
before_save :sanitize, if: :will_save_change_to_geog?
|
|
19
|
-
after_save :cache_derivatives, :
|
|
19
|
+
after_save :cache_derivatives, if: [:automatically_cache_derivatives?, :saved_change_to_geog?]
|
|
20
20
|
|
|
21
21
|
def self.cache_key
|
|
22
22
|
collection_cache_key
|
|
@@ -41,15 +41,15 @@ class AbstractFeature < ActiveRecord::Base
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def self.polygons
|
|
44
|
-
where(:
|
|
44
|
+
where(feature_type: 'polygon')
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def self.lines
|
|
48
|
-
where(:
|
|
48
|
+
where(feature_type: 'line')
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def self.points
|
|
52
|
-
where(:
|
|
52
|
+
where(feature_type: 'point')
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def self.within_distance_of_point(lat, lng, distance_in_meters, geom = 'geom_lowres')
|
|
@@ -59,7 +59,7 @@ class AbstractFeature < ActiveRecord::Base
|
|
|
59
59
|
else "ST_Transform(ST_SetSRID(ST_Point(:lng, :lat), 4326), #{detect_srid(geom)})"
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
binds = { :
|
|
62
|
+
binds = { lng: lng.to_d, lat: lat.to_d }
|
|
63
63
|
|
|
64
64
|
within_distance_of_sql(point_sql, distance_in_meters, geom, **binds)
|
|
65
65
|
end
|
|
@@ -71,7 +71,7 @@ class AbstractFeature < ActiveRecord::Base
|
|
|
71
71
|
else "ST_Transform(ST_SetSRID(:points::geometry, 4326), #{detect_srid(geom)})"
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
binds = { :
|
|
74
|
+
binds = { points: "LINESTRING(#{points.map {|coords| coords.join(' ') }.join(', ')})" }
|
|
75
75
|
|
|
76
76
|
within_distance_of_sql(point_sql, distance_in_meters, geom, **binds)
|
|
77
77
|
end
|
|
@@ -83,14 +83,14 @@ class AbstractFeature < ActiveRecord::Base
|
|
|
83
83
|
else "ST_Transform(ST_Polygon(:points::geometry, 4326), #{detect_srid(geom)})"
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
-
binds = { :
|
|
86
|
+
binds = { points: "LINESTRING(#{points.map {|coords| coords.join(' ') }.join(', ')})" }
|
|
87
87
|
|
|
88
88
|
within_distance_of_sql(point_sql, distance_in_meters, geom, **binds)
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def self.within_distance_of_sql(geometry_sql, distance_in_meters, features_column = 'geom_lowres', **binds)
|
|
92
92
|
if distance_in_meters.to_f > 0
|
|
93
|
-
where("ST_DWithin(features.#{features_column}, #{geometry_sql}, :distance)", **binds, :
|
|
93
|
+
where("ST_DWithin(features.#{features_column}, #{geometry_sql}, :distance)", **binds, distance: distance_in_meters)
|
|
94
94
|
else
|
|
95
95
|
where("ST_Intersects(features.#{features_column}, #{geometry_sql})", **binds)
|
|
96
96
|
end
|
|
@@ -128,7 +128,7 @@ class AbstractFeature < ActiveRecord::Base
|
|
|
128
128
|
end
|
|
129
129
|
|
|
130
130
|
def envelope(buffer_in_meters = 0)
|
|
131
|
-
envelope_json = JSON.parse(self.class.select("ST_AsGeoJSON(ST_Envelope(ST_Buffer(features.geog, #{buffer_in_meters})::geometry)) AS result").where(:
|
|
131
|
+
envelope_json = JSON.parse(self.class.select("ST_AsGeoJSON(ST_Envelope(ST_Buffer(features.geog, #{buffer_in_meters})::geometry)) AS result").where(id: id).first.result)
|
|
132
132
|
envelope_json = envelope_json["coordinates"].first
|
|
133
133
|
|
|
134
134
|
raise "Can't calculate envelope for Feature #{self.id}" if envelope_json.blank?
|
|
@@ -257,12 +257,12 @@ class AbstractFeature < ActiveRecord::Base
|
|
|
257
257
|
end
|
|
258
258
|
|
|
259
259
|
def cache_derivatives(*args)
|
|
260
|
-
self.class.default_scoped.where(:
|
|
260
|
+
self.class.default_scoped.where(id: self.id).cache_derivatives(*args)
|
|
261
261
|
end
|
|
262
262
|
|
|
263
263
|
def kml(options = {})
|
|
264
264
|
column = options[:lowres] ? 'geom_lowres' : 'geog'
|
|
265
|
-
return SpatialFeatures::Utils.select_db_value(self.class.where(:
|
|
265
|
+
return SpatialFeatures::Utils.select_db_value(self.class.where(id: id).select("ST_AsKML(#{column}, 6)"))
|
|
266
266
|
end
|
|
267
267
|
|
|
268
268
|
def geojson(*args)
|
|
@@ -290,7 +290,7 @@ class AbstractFeature < ActiveRecord::Base
|
|
|
290
290
|
end
|
|
291
291
|
|
|
292
292
|
def self.join_other_features(other)
|
|
293
|
-
joins('INNER JOIN features AS other_features ON true').where(:
|
|
293
|
+
joins('INNER JOIN features AS other_features ON true').where(other_features: {id: other})
|
|
294
294
|
end
|
|
295
295
|
|
|
296
296
|
def validate_geometry
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
require_dependency SpatialFeatures::Engine.root.join('app/models/abstract_feature')
|
|
2
2
|
|
|
3
3
|
class AggregateFeature < AbstractFeature
|
|
4
|
-
has_many :features, lambda { |aggregate| where(:
|
|
4
|
+
has_many :features, lambda { |aggregate| where(spatial_model_type: aggregate.spatial_model_type) }, foreign_key: :spatial_model_id, primary_key: :spatial_model_id
|
|
5
5
|
|
|
6
6
|
# Aggregate the features for the spatial model into a single feature
|
|
7
|
-
before_validation :set_geog, :
|
|
7
|
+
before_validation :set_geog, on: :create, unless: :geog?
|
|
8
8
|
|
|
9
9
|
private
|
|
10
10
|
|
data/app/models/feature.rb
CHANGED
|
@@ -7,9 +7,9 @@ class Feature < AbstractFeature
|
|
|
7
7
|
class_attribute :lowres_precision
|
|
8
8
|
self.lowres_precision = 5
|
|
9
9
|
|
|
10
|
-
has_one :aggregate_feature, lambda { |feature| where(:
|
|
10
|
+
has_one :aggregate_feature, lambda { |feature| where(spatial_model_type: feature.spatial_model_type) }, foreign_key: :spatial_model_id, primary_key: :spatial_model_id
|
|
11
11
|
|
|
12
|
-
scope :source_identifier, lambda {|source_identifier| where(:
|
|
12
|
+
scope :source_identifier, lambda {|source_identifier| where(source_identifier: source_identifier) if source_identifier.present? }
|
|
13
13
|
|
|
14
14
|
before_save :truncate_name
|
|
15
15
|
|
|
@@ -21,7 +21,7 @@ class Feature < AbstractFeature
|
|
|
21
21
|
start_at = Feature.maximum(:id).to_i + 1
|
|
22
22
|
output = without_aggregate_refresh(&block)
|
|
23
23
|
|
|
24
|
-
where(:
|
|
24
|
+
where(id: start_at..Float::INFINITY).refresh_aggregates
|
|
25
25
|
|
|
26
26
|
return output
|
|
27
27
|
end
|
|
@@ -36,13 +36,13 @@ class Feature < AbstractFeature
|
|
|
36
36
|
|
|
37
37
|
def self.refresh_aggregates
|
|
38
38
|
# Find one feature from each spatial model and trigger the aggregate feature refresh
|
|
39
|
-
ids = where.not(:
|
|
40
|
-
.where.not(:
|
|
39
|
+
ids = where.not(spatial_model_type: nil)
|
|
40
|
+
.where.not(spatial_model_id: nil)
|
|
41
41
|
.group('spatial_model_type, spatial_model_id')
|
|
42
42
|
.pluck('MAX(id)')
|
|
43
43
|
|
|
44
44
|
# Unscope so that newly built AggregateFeatures get their type column set correctly
|
|
45
|
-
AbstractFeature.unscoped { where(:
|
|
45
|
+
AbstractFeature.unscoped { where(id: ids).find_each(&:refresh_aggregate) }
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def refresh_aggregate
|
data/app/models/spatial_cache.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
class SpatialCache < ActiveRecord::Base
|
|
2
|
-
belongs_to :spatial_model, :
|
|
2
|
+
belongs_to :spatial_model, polymorphic: true, inverse_of: :spatial_caches
|
|
3
3
|
|
|
4
4
|
def self.between(spatial_model, klass)
|
|
5
5
|
where(SpatialFeatures::Utils.polymorphic_condition(spatial_model, 'spatial_model'))
|
|
6
|
-
.where(:
|
|
6
|
+
.where(intersection_model_type: SpatialFeatures::Utils.class_name_with_ancestors(klass))
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def stale?
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
class SpatialProximity < ActiveRecord::Base
|
|
2
|
-
belongs_to :model_a, :
|
|
3
|
-
belongs_to :model_b, :
|
|
2
|
+
belongs_to :model_a, polymorphic: true
|
|
3
|
+
belongs_to :model_b, polymorphic: true
|
|
4
4
|
|
|
5
5
|
def self.between(scope1, scope2)
|
|
6
6
|
where condition_sql(scope1, scope2, <<~SQL.squish)
|
|
@@ -69,7 +69,7 @@ module SpatialFeatures
|
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
def self.clear_record_cache(record, klass)
|
|
72
|
-
record.spatial_caches.where(:
|
|
72
|
+
record.spatial_caches.where(intersection_model_type: SpatialFeatures::Utils.class_name_with_ancestors(klass)).delete_all
|
|
73
73
|
SpatialProximity.between(record, klass).delete_all
|
|
74
74
|
end
|
|
75
75
|
|
|
@@ -77,8 +77,8 @@ module SpatialFeatures
|
|
|
77
77
|
klass = klass.to_s.constantize
|
|
78
78
|
klass_record = klass.new
|
|
79
79
|
|
|
80
|
-
scope = klass.within_buffer(record, default_cache_buffer_in_meters, :
|
|
81
|
-
scope = scope.where.not(:
|
|
80
|
+
scope = klass.within_buffer(record, default_cache_buffer_in_meters, columns: :id, intersection_area: true, distance: true, cache: false)
|
|
81
|
+
scope = scope.where.not(id: record.id) if klass.table_name == record.class.table_name # Don't calculate self proximity
|
|
82
82
|
results = klass.connection.select_rows(scope.to_sql)
|
|
83
83
|
|
|
84
84
|
results.each do |id, distance, area|
|
|
@@ -15,21 +15,21 @@ module SpatialExtensions
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def abstract_proximity_action(scope, target, distance, &block)
|
|
18
|
-
@nearby_records = scope_for_search(scope).within_buffer(target, distance, :
|
|
18
|
+
@nearby_records = scope_for_search(scope).within_buffer(target, distance, distance: true, intersection_area: true).order('distance_in_meters ASC, intersection_area_in_square_meters DESC, id ASC')
|
|
19
19
|
@target = target
|
|
20
20
|
|
|
21
21
|
if block_given?
|
|
22
22
|
block.call(@nearby_records)
|
|
23
23
|
else
|
|
24
24
|
respond_to do |format|
|
|
25
|
-
format.html { render :
|
|
26
|
-
format.kml { render :
|
|
25
|
+
format.html { render template: 'shared/spatial/feature_proximity', layout: false }
|
|
26
|
+
format.kml { render template: 'shared/spatial/feature_proximity' }
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def abstract_venn_polygons_action(scope, target, &block)
|
|
32
|
-
@venn_polygons = SpatialFeatures.venn_polygons(scope_for_search(scope).intersecting(target), target.class.where(:
|
|
32
|
+
@venn_polygons = SpatialFeatures.venn_polygons(scope_for_search(scope).intersecting(target), target.class.where(id: target), target: target)
|
|
33
33
|
@klass = klass_for_search(scope)
|
|
34
34
|
@target = target
|
|
35
35
|
|
|
@@ -37,7 +37,7 @@ module SpatialExtensions
|
|
|
37
37
|
block.call(@venn_polygons)
|
|
38
38
|
else
|
|
39
39
|
respond_to do |format|
|
|
40
|
-
format.kml { render :
|
|
40
|
+
format.kml { render template: 'shared/spatial/feature_venn_polygons' }
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
end
|
|
@@ -50,7 +50,7 @@ module SpatialExtensions
|
|
|
50
50
|
if params.key?(:ids)
|
|
51
51
|
ids = params[:ids]
|
|
52
52
|
ids = ids.split(/\D/) if ids.is_a?(String)
|
|
53
|
-
scope.where(:
|
|
53
|
+
scope.where(id: ids)
|
|
54
54
|
else
|
|
55
55
|
scope
|
|
56
56
|
end
|
|
@@ -9,7 +9,7 @@ module SpatialFeatures
|
|
|
9
9
|
included do
|
|
10
10
|
extend ActiveModel::Callbacks
|
|
11
11
|
define_model_callbacks :update_features
|
|
12
|
-
spatial_features_options.reverse_merge!(:
|
|
12
|
+
spatial_features_options.reverse_merge!(import: {}, spatial_cache: [], image_handlers: [])
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
module ClassMethods
|
|
@@ -73,7 +73,7 @@ module SpatialFeatures
|
|
|
73
73
|
|
|
74
74
|
def update_features_area
|
|
75
75
|
return unless has_attribute?(:features_area)
|
|
76
|
-
self.features_area = features.area(:
|
|
76
|
+
self.features_area = features.area(cache: false)
|
|
77
77
|
update_column :features_area, features_area unless new_record?
|
|
78
78
|
end
|
|
79
79
|
|
|
@@ -101,7 +101,7 @@ module SpatialFeatures
|
|
|
101
101
|
Array.wrap(send(data_method)).flat_map do |data|
|
|
102
102
|
next unless data.present?
|
|
103
103
|
|
|
104
|
-
spatial_importer_from_name(importer_name).create_all(data, **options, :
|
|
104
|
+
spatial_importer_from_name(importer_name).create_all(data, **options, make_valid: make_valid, tmpdir: tmpdir)
|
|
105
105
|
end
|
|
106
106
|
end.compact
|
|
107
107
|
end
|
|
@@ -90,7 +90,7 @@ module SpatialFeatures
|
|
|
90
90
|
|
|
91
91
|
def queue_spatial_task(method_name, *args, priority: 1, **kwargs)
|
|
92
92
|
# NOTE: We pass kwargs as an arg because Delayed::Job does not support separation of positional and keyword arguments in Ruby 3.0. Instead we perform manual extraction in `perform`.
|
|
93
|
-
Delayed::Job.enqueue SpatialProcessingJob.new(self, method_name, *args, kwargs), :
|
|
93
|
+
Delayed::Job.enqueue SpatialProcessingJob.new(self, method_name, *args, kwargs), queue: spatial_processing_queue_name + method_name, priority:
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
def spatial_processing_queue_name
|
|
@@ -111,7 +111,7 @@ module SpatialFeatures
|
|
|
111
111
|
end
|
|
112
112
|
|
|
113
113
|
def before(job)
|
|
114
|
-
ids = running_jobs.where.not(:
|
|
114
|
+
ids = running_jobs.where.not(id: job.id).pluck(:id)
|
|
115
115
|
raise "Already processing delayed jobs in this spatial queue: Delayed::Job #{ids.to_sentence}." if ids.present?
|
|
116
116
|
end
|
|
117
117
|
|
|
@@ -141,8 +141,8 @@ module SpatialFeatures
|
|
|
141
141
|
|
|
142
142
|
def running_jobs
|
|
143
143
|
@record.spatial_processing_jobs
|
|
144
|
-
.where(:
|
|
145
|
-
.where(:
|
|
144
|
+
.where(locked_at: Delayed::Worker.max_run_time.ago..Time.current)
|
|
145
|
+
.where(failed_at: nil)
|
|
146
146
|
end
|
|
147
147
|
end
|
|
148
148
|
end
|
|
@@ -4,29 +4,29 @@ module SpatialFeatures
|
|
|
4
4
|
def has_spatial_features(options = {})
|
|
5
5
|
unless acts_like?(:spatial_features)
|
|
6
6
|
class_attribute :spatial_features_options
|
|
7
|
-
self.spatial_features_options = {:
|
|
7
|
+
self.spatial_features_options = {make_valid: true}
|
|
8
8
|
|
|
9
9
|
extend ClassMethods
|
|
10
10
|
include InstanceMethods
|
|
11
11
|
include FeatureImport
|
|
12
12
|
|
|
13
|
-
has_many :features, lambda { extending FeaturesAssociationExtensions }, :
|
|
14
|
-
has_one :aggregate_feature, lambda { extending FeaturesAssociationExtensions }, :
|
|
13
|
+
has_many :features, lambda { extending FeaturesAssociationExtensions }, as: :spatial_model, dependent: :delete_all
|
|
14
|
+
has_one :aggregate_feature, lambda { extending FeaturesAssociationExtensions }, as: :spatial_model, dependent: :delete
|
|
15
15
|
|
|
16
16
|
scope :with_features, lambda { joins(:features).distinct }
|
|
17
17
|
scope :without_features, lambda { joins("LEFT OUTER JOIN features ON features.spatial_model_type = '#{Utils.base_class(name)}' AND features.spatial_model_id = #{table_name}.id").where("features.id IS NULL") }
|
|
18
18
|
scope :include_bounds, lambda { SQLHelpers.append_select(joins(:aggregate_feature), :north, :east, :south, :west) }
|
|
19
19
|
scope :include_area, lambda { SQLHelpers.append_select(joins(:aggregate_feature), :area) }
|
|
20
20
|
|
|
21
|
-
scope :with_spatial_cache, lambda {|klass| joins(:spatial_caches).where(:
|
|
21
|
+
scope :with_spatial_cache, lambda {|klass| joins(:spatial_caches).where(spatial_caches: { intersection_model_type: Utils.class_name_with_ancestors(klass) }).distinct }
|
|
22
22
|
scope :without_spatial_cache, lambda {|klass| joins("LEFT OUTER JOIN #{SpatialCache.table_name} ON #{SpatialCache.table_name}.spatial_model_id = #{table_name}.id AND #{SpatialCache.table_name}.spatial_model_type = '#{Utils.base_class(name)}' and intersection_model_type IN ('#{Utils.class_name_with_ancestors(klass).join("','") }')").where("#{SpatialCache.table_name}.spatial_model_id IS NULL") }
|
|
23
23
|
scope :with_stale_spatial_cache, lambda { has_spatial_features_hash? ? joins(:spatial_caches).where("#{table_name}.features_hash != spatial_caches.features_hash").distinct : none }
|
|
24
24
|
|
|
25
|
-
has_many :spatial_caches, :
|
|
26
|
-
has_many :model_a_spatial_proximities, :
|
|
27
|
-
has_many :model_b_spatial_proximities, :
|
|
25
|
+
has_many :spatial_caches, as: :spatial_model, dependent: :delete_all, class_name: 'SpatialCache'
|
|
26
|
+
has_many :model_a_spatial_proximities, as: :model_a, class_name: 'SpatialProximity', dependent: :delete_all
|
|
27
|
+
has_many :model_b_spatial_proximities, as: :model_b, class_name: 'SpatialProximity', dependent: :delete_all
|
|
28
28
|
|
|
29
|
-
delegate :has_spatial_features_hash?, :has_features_area?, :
|
|
29
|
+
delegate :has_spatial_features_hash?, :has_features_area?, to: self
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
self.spatial_features_options = self.spatial_features_options.deep_merge(options)
|
|
@@ -87,18 +87,18 @@ module SpatialFeatures
|
|
|
87
87
|
def features
|
|
88
88
|
type = base_class.to_s # Rails stores polymorphic foreign keys as the base class
|
|
89
89
|
if all == unscoped
|
|
90
|
-
Feature.where(:
|
|
90
|
+
Feature.where(spatial_model_type: type)
|
|
91
91
|
else
|
|
92
|
-
Feature.where(:
|
|
92
|
+
Feature.where(spatial_model_type: type, spatial_model_id: all.unscope(:select))
|
|
93
93
|
end
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
def aggregate_features
|
|
97
97
|
type = base_class.to_s # Rails stores polymorphic foreign keys as the base class
|
|
98
98
|
if all == unscoped
|
|
99
|
-
AggregateFeature.where(:
|
|
99
|
+
AggregateFeature.where(spatial_model_type: type)
|
|
100
100
|
else
|
|
101
|
-
AggregateFeature.where(:
|
|
101
|
+
AggregateFeature.where(spatial_model_type: type, spatial_model_id: all.unscope(:select))
|
|
102
102
|
end
|
|
103
103
|
end
|
|
104
104
|
|
|
@@ -119,7 +119,7 @@ module SpatialFeatures
|
|
|
119
119
|
private
|
|
120
120
|
|
|
121
121
|
def cached_within_buffer_scope(other, buffer_in_meters, options)
|
|
122
|
-
options = options.reverse_merge(:
|
|
122
|
+
options = options.reverse_merge(columns: "#{table_name}.*")
|
|
123
123
|
scope = cached_spatial_join(other)
|
|
124
124
|
scope = scope.select(options[:columns])
|
|
125
125
|
scope = scope.where("spatial_proximities.distance_in_meters <= ?", buffer_in_meters) if buffer_in_meters
|
|
@@ -142,7 +142,7 @@ module SpatialFeatures
|
|
|
142
142
|
end
|
|
143
143
|
|
|
144
144
|
def uncached_within_buffer_scope(other, buffer_in_meters, options)
|
|
145
|
-
options = options.reverse_merge(:
|
|
145
|
+
options = options.reverse_merge(columns: "#{table_name}.*")
|
|
146
146
|
|
|
147
147
|
scope = spatial_join(other, buffer_in_meters)
|
|
148
148
|
scope = scope.select(options[:columns])
|
|
@@ -168,8 +168,8 @@ module SpatialFeatures
|
|
|
168
168
|
|
|
169
169
|
def features_scope(other)
|
|
170
170
|
scope = AggregateFeature
|
|
171
|
-
scope = scope.where(:
|
|
172
|
-
scope = scope.where(:
|
|
171
|
+
scope = scope.where(spatial_model_type: Utils.base_class_of(other).to_s)
|
|
172
|
+
scope = scope.where(spatial_model_id: other) unless Utils.class_of(other) == other
|
|
173
173
|
return scope
|
|
174
174
|
end
|
|
175
175
|
|
|
@@ -17,10 +17,10 @@ module SpatialFeatures
|
|
|
17
17
|
metadata = record['properties'] || {}
|
|
18
18
|
name = metadata.delete('name')
|
|
19
19
|
yield OpenStruct.new(
|
|
20
|
-
:
|
|
21
|
-
:
|
|
22
|
-
:
|
|
23
|
-
:
|
|
20
|
+
feature_type: record['geometry']['type'],
|
|
21
|
+
geog: SpatialFeatures::Utils.geom_from_json(record['geometry']),
|
|
22
|
+
name: name,
|
|
23
|
+
metadata: metadata
|
|
24
24
|
)
|
|
25
25
|
end
|
|
26
26
|
end
|
|
@@ -36,7 +36,7 @@ module SpatialFeatures
|
|
|
36
36
|
|
|
37
37
|
importable_image_paths = images_from_metadata(metadata)
|
|
38
38
|
|
|
39
|
-
yield OpenStruct.new(:
|
|
39
|
+
yield OpenStruct.new(geog: geog, name: name, metadata: metadata, importable_image_paths: importable_image_paths)
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
42
|
end
|
|
@@ -92,7 +92,7 @@ module SpatialFeatures
|
|
|
92
92
|
metadata = {}
|
|
93
93
|
metadata.merge! extract_table(placemark)
|
|
94
94
|
metadata.merge! extract_extended_data(placemark)
|
|
95
|
-
metadata.merge! :
|
|
95
|
+
metadata.merge! description: placemark.css('description').text if metadata.empty?
|
|
96
96
|
metadata.delete_if {|key, value| value.blank? }
|
|
97
97
|
|
|
98
98
|
return metadata
|
|
@@ -30,7 +30,7 @@ module SpatialFeatures
|
|
|
30
30
|
def each_record
|
|
31
31
|
open_shapefile(archive) do |records, proj4|
|
|
32
32
|
records.each do |record|
|
|
33
|
-
yield OpenStruct.new
|
|
33
|
+
yield OpenStruct.new(**data_from_record(record, proj4)) if record.geometry.present?
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
rescue Errno::ENOENT => e
|
|
@@ -45,7 +45,7 @@ module SpatialFeatures
|
|
|
45
45
|
def data_from_record(record, proj4 = nil)
|
|
46
46
|
geometry = record.geometry
|
|
47
47
|
wkt = geometry.as_text
|
|
48
|
-
data = { :
|
|
48
|
+
data = { metadata: record.attributes }
|
|
49
49
|
|
|
50
50
|
if proj4 == PROJ4_4326
|
|
51
51
|
data[:geog] = wkt
|
|
@@ -66,7 +66,7 @@ module SpatialFeatures
|
|
|
66
66
|
validate_shapefile!(file.path)
|
|
67
67
|
proj4 = proj4_projection(file.path)
|
|
68
68
|
|
|
69
|
-
RGeo::Shapefile::Reader.open(file.path, :
|
|
69
|
+
RGeo::Shapefile::Reader.open(file.path, allow_unsafe: true) do |records| # Fall back to unprojected geometry if projection fails
|
|
70
70
|
block.call records, proj4
|
|
71
71
|
end
|
|
72
72
|
ensure
|
|
@@ -21,11 +21,17 @@ module SpatialFeatures
|
|
|
21
21
|
[].tap do |paths|
|
|
22
22
|
entries(file_path).each do |entry|
|
|
23
23
|
next if entry.name =~ IGNORED_ENTRY_PATHS
|
|
24
|
+
|
|
24
25
|
output_filename = entry.name
|
|
25
26
|
output_filename = output_filename.downcase if downcase
|
|
27
|
+
|
|
26
28
|
path = "#{tmpdir}/#{output_filename}"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
directory = File.dirname(path)
|
|
30
|
+
basename = File.basename(path)
|
|
31
|
+
|
|
32
|
+
FileUtils.mkdir_p(directory)
|
|
33
|
+
entry.extract(basename, destination_directory: directory)
|
|
34
|
+
|
|
29
35
|
paths << path
|
|
30
36
|
end
|
|
31
37
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'ostruct'
|
|
2
|
+
|
|
1
3
|
module SpatialFeatures
|
|
2
4
|
# Splits overlapping features into separate polygons at their areas of overlap, and returns an array of objects
|
|
3
5
|
# with kml for the overlapping area and a list of the record ids whose kml overlapped within that area
|
|
@@ -57,7 +59,7 @@ module SpatialFeatures
|
|
|
57
59
|
polygons.group_by{|row| row['kml']}.collect do |kml, rows|
|
|
58
60
|
# Uniq on row id in case a single record had self intersecting multi geometry, which would cause it to appear duplicated on a single venn polygon
|
|
59
61
|
records = rows.uniq {|row| row.values_at('id', 'type') }.collect{|row| eager_load_hash.fetch(row['type']).detect{|record| record.id == row['id'].to_i } }
|
|
60
|
-
OpenStruct.new(:
|
|
62
|
+
OpenStruct.new(kml: kml, records: records)
|
|
61
63
|
end
|
|
62
64
|
end
|
|
63
65
|
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: 3.
|
|
4
|
+
version: 3.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ryan Wallace
|
|
@@ -19,7 +19,7 @@ dependencies:
|
|
|
19
19
|
version: '6'
|
|
20
20
|
- - "<"
|
|
21
21
|
- !ruby/object:Gem::Version
|
|
22
|
-
version: '
|
|
22
|
+
version: '9'
|
|
23
23
|
type: :runtime
|
|
24
24
|
prerelease: false
|
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -29,7 +29,7 @@ dependencies:
|
|
|
29
29
|
version: '6'
|
|
30
30
|
- - "<"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '
|
|
32
|
+
version: '9'
|
|
33
33
|
- !ruby/object:Gem::Dependency
|
|
34
34
|
name: delayed_job_active_record
|
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -62,32 +62,46 @@ dependencies:
|
|
|
62
62
|
name: rgeo-geojson
|
|
63
63
|
requirement: !ruby/object:Gem::Requirement
|
|
64
64
|
requirements:
|
|
65
|
-
- - "
|
|
65
|
+
- - ">="
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: 2.1.1
|
|
68
68
|
type: :runtime
|
|
69
69
|
prerelease: false
|
|
70
70
|
version_requirements: !ruby/object:Gem::Requirement
|
|
71
71
|
requirements:
|
|
72
|
-
- - "
|
|
72
|
+
- - ">="
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
74
|
version: 2.1.1
|
|
75
75
|
- !ruby/object:Gem::Dependency
|
|
76
76
|
name: rubyzip
|
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '3.0'
|
|
82
|
+
type: :runtime
|
|
83
|
+
prerelease: false
|
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '3.0'
|
|
89
|
+
- !ruby/object:Gem::Dependency
|
|
90
|
+
name: nokogiri
|
|
77
91
|
requirement: !ruby/object:Gem::Requirement
|
|
78
92
|
requirements:
|
|
79
93
|
- - ">="
|
|
80
94
|
- !ruby/object:Gem::Version
|
|
81
|
-
version:
|
|
95
|
+
version: '0'
|
|
82
96
|
type: :runtime
|
|
83
97
|
prerelease: false
|
|
84
98
|
version_requirements: !ruby/object:Gem::Requirement
|
|
85
99
|
requirements:
|
|
86
100
|
- - ">="
|
|
87
101
|
- !ruby/object:Gem::Version
|
|
88
|
-
version:
|
|
102
|
+
version: '0'
|
|
89
103
|
- !ruby/object:Gem::Dependency
|
|
90
|
-
name:
|
|
104
|
+
name: ostruct
|
|
91
105
|
requirement: !ruby/object:Gem::Requirement
|
|
92
106
|
requirements:
|
|
93
107
|
- - ">="
|
|
@@ -109,7 +123,7 @@ dependencies:
|
|
|
109
123
|
version: '7'
|
|
110
124
|
- - "<"
|
|
111
125
|
- !ruby/object:Gem::Version
|
|
112
|
-
version: '
|
|
126
|
+
version: '9'
|
|
113
127
|
type: :development
|
|
114
128
|
prerelease: false
|
|
115
129
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -119,7 +133,7 @@ dependencies:
|
|
|
119
133
|
version: '7'
|
|
120
134
|
- - "<"
|
|
121
135
|
- !ruby/object:Gem::Version
|
|
122
|
-
version: '
|
|
136
|
+
version: '9'
|
|
123
137
|
- !ruby/object:Gem::Dependency
|
|
124
138
|
name: pg
|
|
125
139
|
requirement: !ruby/object:Gem::Requirement
|