spatial_features 3.4.2 → 3.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +17 -0
- data/app/models/abstract_feature.rb +8 -20
- data/app/models/feature.rb +1 -1
- data/lib/spatial_features/controller_helpers/spatial_extensions.rb +2 -2
- data/lib/spatial_features/has_spatial_features/feature_import.rb +5 -1
- data/lib/spatial_features/has_spatial_features/queued_spatial_processing.rb +54 -16
- data/lib/spatial_features/has_spatial_features.rb +1 -1
- data/lib/spatial_features/importers/base.rb +1 -0
- data/lib/spatial_features/importers/kml.rb +1 -1
- data/lib/spatial_features/importers/shapefile.rb +54 -26
- data/lib/spatial_features/version.rb +1 -1
- metadata +28 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 58ed4738a69dce88104f9ad09ad32dc9f6ebe8819e6bd4bd87c30ea7bde58c70
|
4
|
+
data.tar.gz: b70f376893db22e801ce2b1535c26390f02a754a6884938713ad292cac7432da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9e0aa77934a5e00d891bb22adc72e3779267193035deaad0e236ced895c0565c06a6d4b81218e2d2708a493f9913fefdec10cac6e64b40d105cb0e0518f57ec4
|
7
|
+
data.tar.gz: 9456cac927abb58f4710cbbc6366e5f906c93460e68013be9bd48cef90340d2ccf3d43d29e8ac8dff1752fc1c3950030aa494fbaffb3ca91e8ffc3ee179d8876
|
data/README.md
CHANGED
@@ -200,6 +200,23 @@ add_index :features, :source_identifier
|
|
200
200
|
MyModel.update_features!(:force => true) # Force an `update_features!` will populate the source_identifier column.
|
201
201
|
```
|
202
202
|
|
203
|
+
## Upgrading From 3.4 to 3.5
|
204
|
+
The gem now relies on virtual columns to set a number of derived column values.
|
205
|
+
```ruby
|
206
|
+
change_table :features do |t|
|
207
|
+
t.remove :tilegeom, :feature_type, :centroid, :area, :north, :east, :south, :west
|
208
|
+
|
209
|
+
t.virtual :tilegeom, :type => 'geometry(Geometry,3857)', as: "ST_Transform(geom, 3857)", stored: true, :index => { :using => :gist }
|
210
|
+
t.virtual :feature_type, :type => :string, as: "CASE GeometryType(geog) WHEN 'POLYGON' THEN 'polygon' WHEN 'MULTIPOLYGON' THEN 'polygon' WHEN 'GEOMETRYCOLLECTION' THEN 'polygon' WHEN 'LINESTRING' THEN 'line' WHEN 'MULTILINESTRING' THEN 'line' WHEN 'POINT' THEN 'point' WHEN 'MULTIPOINT' THEN 'point' END", stored: true, :index => true
|
211
|
+
t.virtual :centroid, :type => :geography, as: "ST_PointOnSurface(geog::geometry)", stored: true
|
212
|
+
t.virtual :area, :type => :decimal, as: "ST_Area(geog)", stored: true
|
213
|
+
t.virtual :north, :type => :decimal, as: "ST_YMax(geog::geometry)", stored: true
|
214
|
+
t.virtual :east, :type => :decimal, as: "ST_XMax(geog::geometry)", stored: true
|
215
|
+
t.virtual :south, :type => :decimal, as: "ST_YMin(geog::geometry)", stored: true
|
216
|
+
t.virtual :west, :type => :decimal, as: "ST_XMin(geog::geometry)", stored: true
|
217
|
+
end
|
218
|
+
```
|
219
|
+
|
203
220
|
## Testing
|
204
221
|
|
205
222
|
Create a postgres database:
|
@@ -13,20 +13,19 @@ class AbstractFeature < ActiveRecord::Base
|
|
13
13
|
|
14
14
|
FEATURE_TYPES = %w(polygon point line)
|
15
15
|
|
16
|
-
before_validation :sanitize_feature_type
|
17
16
|
validates_presence_of :geog
|
18
17
|
validate :validate_geometry, if: :will_save_change_to_geog?
|
19
18
|
before_save :sanitize, if: :will_save_change_to_geog?
|
20
19
|
after_save :cache_derivatives, :if => [:automatically_cache_derivatives?, :saved_change_to_geog?]
|
21
20
|
|
22
|
-
|
23
|
-
|
24
|
-
def self.collection_cache_key(_collection, _timestamp_column)
|
25
|
-
self.cache_key
|
21
|
+
def self.cache_key
|
22
|
+
collection_cache_key
|
26
23
|
end
|
27
24
|
|
28
|
-
|
29
|
-
|
25
|
+
# for Rails >= 5 ActiveRecord collections we override the collection_cache_key
|
26
|
+
# to prevent Rails doing its default query on `updated_at`
|
27
|
+
def self.collection_cache_key(collection = all, *)
|
28
|
+
"#{collection.maximum(:id)}-#{collection.count}"
|
30
29
|
end
|
31
30
|
|
32
31
|
def self.with_metadata(k, v)
|
@@ -113,13 +112,7 @@ class AbstractFeature < ActiveRecord::Base
|
|
113
112
|
|
114
113
|
def self.cache_derivatives(options = {})
|
115
114
|
update_all <<-SQL.squish
|
116
|
-
geom = ST_Transform(geog::geometry, #{detect_srid('geom')})
|
117
|
-
north = ST_YMax(geog::geometry),
|
118
|
-
east = ST_XMax(geog::geometry),
|
119
|
-
south = ST_YMin(geog::geometry),
|
120
|
-
west = ST_XMin(geog::geometry),
|
121
|
-
area = ST_Area(geog),
|
122
|
-
centroid = ST_PointOnSurface(geog::geometry)
|
115
|
+
geom = ST_Transform(geog::geometry, #{detect_srid('geom')})
|
123
116
|
SQL
|
124
117
|
|
125
118
|
invalid('geom').update_all <<-SQL.squish
|
@@ -127,8 +120,7 @@ class AbstractFeature < ActiveRecord::Base
|
|
127
120
|
SQL
|
128
121
|
|
129
122
|
update_all <<-SQL.squish
|
130
|
-
geom_lowres = ST_SimplifyPreserveTopology(geom, #{options.fetch(:lowres_simplification, lowres_simplification)})
|
131
|
-
tilegeom = ST_Transform(geom, 3857)
|
123
|
+
geom_lowres = ST_SimplifyPreserveTopology(geom, #{options.fetch(:lowres_simplification, lowres_simplification)})
|
132
124
|
SQL
|
133
125
|
|
134
126
|
invalid('geom_lowres').update_all <<-SQL.squish
|
@@ -286,10 +278,6 @@ class AbstractFeature < ActiveRecord::Base
|
|
286
278
|
return error.fetch('invalid_geometry_message') if error
|
287
279
|
end
|
288
280
|
|
289
|
-
def sanitize_feature_type
|
290
|
-
self.feature_type = FEATURE_TYPES.find {|type| self.feature_type.to_s.strip.downcase.include?(type) }
|
291
|
-
end
|
292
|
-
|
293
281
|
def sanitize_input_for_sql(input)
|
294
282
|
self.class.send(:sanitize_sql_for_conditions, input)
|
295
283
|
end
|
data/app/models/feature.rb
CHANGED
@@ -9,7 +9,7 @@ class Feature < AbstractFeature
|
|
9
9
|
|
10
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
|
-
|
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
|
|
@@ -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, **update_options)
|
5
5
|
Array.wrap(models).each do |model|
|
6
6
|
model.failed_feature_update_jobs.destroy_all
|
7
|
-
model.delay_update_features!
|
7
|
+
model.delay_update_features!(**update_options)
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
@@ -54,6 +54,10 @@ module SpatialFeatures
|
|
54
54
|
if skip_invalid
|
55
55
|
Rails.logger.warn "Error updating #{self.class} #{self.id}. #{e.message}"
|
56
56
|
return nil
|
57
|
+
elsif ENCODING_ERROR.match?(e.message)
|
58
|
+
raise ImportEncodingError,
|
59
|
+
"One or more features you are trying to import has text encoded in an un-supported format (#{e.message})",
|
60
|
+
e.backtrace
|
57
61
|
else
|
58
62
|
raise ImportError, e.message, e.backtrace
|
59
63
|
end
|
@@ -151,5 +155,5 @@ module SpatialFeatures
|
|
151
155
|
end
|
152
156
|
end
|
153
157
|
|
154
|
-
|
158
|
+
ENCODING_ERROR = /invalid byte sequence/i.freeze
|
155
159
|
end
|
@@ -1,17 +1,27 @@
|
|
1
1
|
module SpatialFeatures
|
2
2
|
module QueuedSpatialProcessing
|
3
3
|
extend ActiveSupport::Concern
|
4
|
+
mattr_accessor :priority_offset, default: 0 # Offsets the queued priority of spatial tasks. Lower numbers run with higher priority
|
4
5
|
|
5
|
-
def
|
6
|
-
|
6
|
+
def self.update_cached_status(record, method_name, state)
|
7
|
+
return unless record.has_attribute?(:spatial_processing_status_cache)
|
8
|
+
|
9
|
+
cache = record.spatial_processing_status_cache || {}
|
10
|
+
cache[method_name] = state
|
11
|
+
record.spatial_processing_status_cache = cache
|
12
|
+
record.update_column(:spatial_processing_status_cache, cache) if record.will_save_change_to_spatial_processing_status_cache?
|
13
|
+
end
|
14
|
+
|
15
|
+
def queue_update_spatial_cache(*args, priority: priority_offset + 1, **kwargs)
|
16
|
+
queue_spatial_task('update_spatial_cache', *args, priority:, **kwargs)
|
7
17
|
end
|
8
18
|
|
9
|
-
def delay_update_features!(*args)
|
10
|
-
queue_spatial_task('update_features!', *args)
|
19
|
+
def delay_update_features!(*args, priority: priority_offset + 0, **kwargs)
|
20
|
+
queue_spatial_task('update_features!', *args, priority:, **kwargs)
|
11
21
|
end
|
12
22
|
|
13
|
-
def updating_features?
|
14
|
-
case spatial_processing_status(:update_features
|
23
|
+
def updating_features?(**options)
|
24
|
+
case spatial_processing_status(:update_features!, **options)
|
15
25
|
when :queued, :processing
|
16
26
|
true
|
17
27
|
else
|
@@ -23,18 +33,37 @@ module SpatialFeatures
|
|
23
33
|
spatial_processing_status(:update_features!) == :failure
|
24
34
|
end
|
25
35
|
|
26
|
-
def spatial_processing_status(method_name)
|
36
|
+
def spatial_processing_status(method_name, use_cache: true)
|
27
37
|
if has_attribute?(:spatial_processing_status_cache)
|
38
|
+
update_spatial_processing_status(method_name) unless use_cache
|
28
39
|
spatial_processing_status_cache[method_name.to_s]&.to_sym
|
29
40
|
end
|
30
41
|
end
|
31
42
|
|
43
|
+
def update_spatial_processing_status(method_name)
|
44
|
+
latest_job = spatial_processing_jobs(method_name).last
|
45
|
+
|
46
|
+
if !latest_job
|
47
|
+
SpatialFeatures::QueuedSpatialProcessing.update_cached_status(self, method_name, nil)
|
48
|
+
elsif latest_job.failed_at?
|
49
|
+
SpatialFeatures::QueuedSpatialProcessing.update_cached_status(self, method_name, :failure)
|
50
|
+
elsif latest_job.locked_at?
|
51
|
+
SpatialFeatures::QueuedSpatialProcessing.update_cached_status(self, method_name, :processing)
|
52
|
+
else
|
53
|
+
SpatialFeatures::QueuedSpatialProcessing.update_cached_status(self, method_name, :queued)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
32
57
|
def feature_update_error
|
33
58
|
(failed_feature_update_jobs.first.try(:last_error) || '').split("\n").first
|
34
59
|
end
|
35
60
|
|
36
61
|
def running_feature_update_jobs
|
37
|
-
spatial_processing_jobs('update_features!').where(failed_at: nil)
|
62
|
+
spatial_processing_jobs('update_features!').where(failed_at: nil).where.not(locked_at: nil)
|
63
|
+
end
|
64
|
+
|
65
|
+
def queued_feature_update_jobs
|
66
|
+
spatial_processing_jobs('update_features!').where(failed_at: nil, locked_at: nil)
|
38
67
|
end
|
39
68
|
|
40
69
|
def failed_feature_update_jobs
|
@@ -47,8 +76,9 @@ module SpatialFeatures
|
|
47
76
|
|
48
77
|
private
|
49
78
|
|
50
|
-
def queue_spatial_task(method_name, *args)
|
51
|
-
Delayed::Job
|
79
|
+
def queue_spatial_task(method_name, *args, priority: 1, **kwargs)
|
80
|
+
# 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`.
|
81
|
+
Delayed::Job.enqueue SpatialProcessingJob.new(self, method_name, *args, kwargs), :queue => spatial_processing_queue_name + method_name, priority:
|
52
82
|
end
|
53
83
|
|
54
84
|
def spatial_processing_queue_name
|
@@ -68,9 +98,15 @@ module SpatialFeatures
|
|
68
98
|
update_cached_status(:queued)
|
69
99
|
end
|
70
100
|
|
101
|
+
def before(job)
|
102
|
+
ids = running_jobs.where.not(:id => job.id).pluck(:id)
|
103
|
+
raise "Already processing delayed jobs in this spatial queue: Delayed::Job #{ids.to_sentence}." if ids.present?
|
104
|
+
end
|
105
|
+
|
71
106
|
def perform
|
72
107
|
update_cached_status(:processing)
|
73
|
-
@
|
108
|
+
options = @args.extract_options!
|
109
|
+
@record.send(@method_name, *@args, **options)
|
74
110
|
end
|
75
111
|
|
76
112
|
def success(job)
|
@@ -88,11 +124,13 @@ module SpatialFeatures
|
|
88
124
|
private
|
89
125
|
|
90
126
|
def update_cached_status(state)
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
127
|
+
SpatialFeatures::QueuedSpatialProcessing.update_cached_status(@record, @method_name, state)
|
128
|
+
end
|
129
|
+
|
130
|
+
def running_jobs
|
131
|
+
@record.spatial_processing_jobs
|
132
|
+
.where(:locked_at => Delayed::Worker.max_run_time.ago..Time.current)
|
133
|
+
.where(:failed_at => nil)
|
96
134
|
end
|
97
135
|
end
|
98
136
|
end
|
@@ -29,7 +29,7 @@ module SpatialFeatures
|
|
29
29
|
delegate :has_spatial_features_hash?, :has_features_area?, :to => self
|
30
30
|
end
|
31
31
|
|
32
|
-
self.spatial_features_options = self.spatial_features_options.
|
32
|
+
self.spatial_features_options = self.spatial_features_options.deep_merge(options)
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
@@ -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
|
@@ -6,6 +6,8 @@ module SpatialFeatures
|
|
6
6
|
class Shapefile < Base
|
7
7
|
class_attribute :default_proj4_projection
|
8
8
|
|
9
|
+
PROJ4_4326 = '+proj=longlat +datum=WGS84 +no_defs'.freeze
|
10
|
+
|
9
11
|
def initialize(data, proj4: nil, **options)
|
10
12
|
super(data, **options)
|
11
13
|
@proj4 = proj4
|
@@ -25,15 +27,10 @@ module SpatialFeatures
|
|
25
27
|
|
26
28
|
private
|
27
29
|
|
28
|
-
def
|
29
|
-
|
30
|
-
super
|
31
|
-
end
|
32
|
-
|
33
|
-
def each_record(&block)
|
34
|
-
RGeo::Shapefile::Reader.open(file.path) do |records|
|
30
|
+
def each_record
|
31
|
+
open_shapefile(archive) do |records, proj4|
|
35
32
|
records.each do |record|
|
36
|
-
yield OpenStruct.new
|
33
|
+
yield OpenStruct.new data_from_record(record, proj4) if record.geometry.present?
|
37
34
|
end
|
38
35
|
end
|
39
36
|
rescue Errno::ENOENT => e
|
@@ -45,26 +42,61 @@ module SpatialFeatures
|
|
45
42
|
end
|
46
43
|
end
|
47
44
|
|
48
|
-
def
|
49
|
-
|
45
|
+
def data_from_record(record, proj4 = nil)
|
46
|
+
geometry = record.geometry
|
47
|
+
wkt = geometry.as_text
|
48
|
+
data = { :metadata => record.attributes }
|
49
|
+
|
50
|
+
if proj4 == PROJ4_4326
|
51
|
+
data[:geog] = wkt
|
52
|
+
else
|
53
|
+
data[:geog] = ActiveRecord::Base.connection.select_value <<-SQL
|
54
|
+
SELECT ST_Transform(ST_GeomFromText('#{wkt}'), '#{proj4}', 4326) AS geog
|
55
|
+
SQL
|
56
|
+
end
|
57
|
+
|
58
|
+
return data
|
50
59
|
end
|
51
60
|
|
52
|
-
def
|
53
|
-
#
|
54
|
-
|
55
|
-
|
56
|
-
|
61
|
+
def open_shapefile(file, &block)
|
62
|
+
# the individual SHP file for processing (automatically extracted from a ZIP archive if necessary)
|
63
|
+
file = possible_shp_files.first if Unzip.is_zip?(file)
|
64
|
+
projected_file = project_to_4326(file.path)
|
65
|
+
file = projected_file || file
|
66
|
+
validate_shapefile!(file.path)
|
67
|
+
proj4 = proj4_projection(file.path)
|
68
|
+
|
69
|
+
RGeo::Shapefile::Reader.open(file.path) do |records| # Fall back to unprojected geometry if projection fails
|
70
|
+
block.call records, proj4
|
71
|
+
end
|
72
|
+
ensure
|
73
|
+
if projected_file
|
74
|
+
projected_file.close
|
75
|
+
::File.delete(projected_file)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def proj4_projection(file_path)
|
80
|
+
proj4_from_file(file_path) || default_proj4_projection || raise(IndeterminateShapefileProjection, 'Could not determine shapefile projection. Check that `gdalsrsinfo` is installed.')
|
57
81
|
end
|
58
82
|
|
59
|
-
def
|
60
|
-
|
61
|
-
SELECT ST_Transform(ST_GeomFromText('#{wkt}'), '#{proj4}', 4326) AS geog, GeometryType(ST_GeomFromText('#{wkt}')) AS feature_type
|
62
|
-
SQL
|
83
|
+
def validate_shapefile!(file_path)
|
84
|
+
Validation.validate_shapefile!(::File.open(file_path), default_proj4_projection: default_proj4_projection)
|
63
85
|
end
|
64
86
|
|
65
|
-
#
|
66
|
-
def
|
67
|
-
|
87
|
+
# Use OGR2OGR to reproject into EPSG:4326 so we can skip the reprojection step per-feature
|
88
|
+
def project_to_4326(file_path)
|
89
|
+
output_path = Tempfile.create([::File.basename(file_path, '.shp') + '_epsg_4326_', '.shp']) { |file| file.path }
|
90
|
+
return unless (proj4 = proj4_from_file(file_path))
|
91
|
+
return unless system("ogr2ogr -s_srs '#{proj4}' -t_srs EPSG:4326 '#{output_path}' '#{file_path}'")
|
92
|
+
return ::File.open(output_path)
|
93
|
+
end
|
94
|
+
|
95
|
+
def proj4_from_file(file_path)
|
96
|
+
# Sanitize: "'+proj=utm +zone=11 +datum=NAD83 +units=m +no_defs '\n" and lately
|
97
|
+
# "+proj=utm +zone=11 +datum=NAD83 +units=m +no_defs \n" to
|
98
|
+
# "+proj=utm +zone=11 +datum=NAD83 +units=m +no_defs"
|
99
|
+
`gdalsrsinfo "#{file_path}" -o proj4`.strip.remove(/^'|'$/).presence
|
68
100
|
end
|
69
101
|
|
70
102
|
# a zip archive may contain multiple SHP files
|
@@ -76,10 +108,6 @@ module SpatialFeatures
|
|
76
108
|
end
|
77
109
|
end
|
78
110
|
|
79
|
-
def validate_shapefile!
|
80
|
-
Validation.validate_shapefile!(file, default_proj4_projection: default_proj4_projection)
|
81
|
-
end
|
82
|
-
|
83
111
|
def archive
|
84
112
|
@archive ||= Download.open(@data)
|
85
113
|
end
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spatial_features
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Wallace
|
8
8
|
- Nicholas Jakobsen
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2024-01-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -51,14 +51,14 @@ dependencies:
|
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '3.
|
54
|
+
version: '3.1'
|
55
55
|
type: :runtime
|
56
56
|
prerelease: false
|
57
57
|
version_requirements: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '3.
|
61
|
+
version: '3.1'
|
62
62
|
- !ruby/object:Gem::Dependency
|
63
63
|
name: rgeo-geojson
|
64
64
|
requirement: !ruby/object:Gem::Requirement
|
@@ -101,6 +101,26 @@ dependencies:
|
|
101
101
|
- - ">="
|
102
102
|
- !ruby/object:Gem::Version
|
103
103
|
version: '0'
|
104
|
+
- !ruby/object:Gem::Dependency
|
105
|
+
name: rails
|
106
|
+
requirement: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '7'
|
111
|
+
- - "<"
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '8'
|
114
|
+
type: :development
|
115
|
+
prerelease: false
|
116
|
+
version_requirements: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '7'
|
121
|
+
- - "<"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '8'
|
104
124
|
- !ruby/object:Gem::Dependency
|
105
125
|
name: pg
|
106
126
|
requirement: !ruby/object:Gem::Requirement
|
@@ -188,7 +208,7 @@ homepage: https://github.com/culturecode/spatial_features
|
|
188
208
|
licenses:
|
189
209
|
- MIT
|
190
210
|
metadata: {}
|
191
|
-
post_install_message:
|
211
|
+
post_install_message:
|
192
212
|
rdoc_options: []
|
193
213
|
require_paths:
|
194
214
|
- lib
|
@@ -203,8 +223,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
203
223
|
- !ruby/object:Gem::Version
|
204
224
|
version: '0'
|
205
225
|
requirements: []
|
206
|
-
rubygems_version: 3.
|
207
|
-
signing_key:
|
226
|
+
rubygems_version: 3.5.1
|
227
|
+
signing_key:
|
208
228
|
specification_version: 4
|
209
229
|
summary: Adds spatial methods to a model.
|
210
230
|
test_files: []
|