spatial_features 3.1.0 → 3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9578faf8d4205e8b25b7039b99df9ead3f03f13d82568277930c8824adb48980
4
- data.tar.gz: 16a5be2783383686f570f598edd8ecf3da6350fb92d5b393618f8be919ab8c79
3
+ metadata.gz: 7ba1ab9dfa18160f48292f26a63f75031187e44cc04aff73dba792832d482063
4
+ data.tar.gz: b25d8a6f4bd0412c3d2d9dd34a57c620a0593979304ddb9061c7f3b3e2d7f834
5
5
  SHA512:
6
- metadata.gz: 772ab77070751ca764ab8a06cf5260038be3cae8461c6e9c7fa894e644d2164cd8905281c43d0eab48b7ab2f06227b01da809687ec4ffb7bc1ade359c7097eea
7
- data.tar.gz: da9f6d66e79f85362f4c6af9e3dc8400b4f6fb51bf0cb5ba84114ccb47b4541e59614d162271877c6d20d9d342fbc9a86b48de7e8a5a1de15b27c7136c09f3b3
6
+ metadata.gz: 1be4b279b3341582262bdb8d802c0786b1eb3607cef070f2a03813b2bc987d21fb0aa92323d7b49dbec01aa8b2e128f0b9982ce356dc4836875ccd5ad89f7092
7
+ data.tar.gz: 8e70dfe6f6bad6545c29fc5dbf1406b308b9cb44ff48ebfc849469912af6cf30640c829c2baf363a6790e9207f78d3c039276c002a50ce6319ccf0d9450f9df7
data/README.md CHANGED
@@ -31,7 +31,6 @@ Adds spatial methods to a model.
31
31
  feature_type character varying(255),
32
32
  geog geography,
33
33
  geom geometry(Geometry,4326),
34
- geom_lowres geometry(Geometry,4326),
35
34
  tilegeom geometry(Geometry,3857),
36
35
  metadata hstore,
37
36
  area double precision,
@@ -50,7 +49,6 @@ Adds spatial methods to a model.
50
49
  CREATE INDEX index_features_on_feature_type ON features USING btree (feature_type);
51
50
  CREATE INDEX index_features_on_spatial_model_id_and_spatial_model_type ON features USING btree (spatial_model_id, spatial_model_type);
52
51
  CREATE INDEX index_features_on_geom ON features USING gist (geom);
53
- CREATE INDEX index_features_on_geom_lowres ON features USING gist (geom_lowres);
54
52
  CREATE INDEX index_features_on_tilegeom ON features USING gist (tilegeom);
55
53
 
56
54
  CREATE TABLE spatial_caches (
@@ -182,6 +180,14 @@ add_index :features, :tilegeom, :using => :gist
182
180
  Feature.update_all('tilegeom = ST_Transform(geom, 3857)')
183
181
  ```
184
182
 
183
+ ## Upgrading From 3.0/3.1 to 3.2
184
+ SpatialProximity now expects the `model_a` and `model_b` records to be decided based on the name of the record type so
185
+ queries can be optimized. Migrate existing SpatialProximity rows to this new scheme by running the code below.
186
+
187
+ ```ruby
188
+ SpatialProximity.normalize
189
+ ```
190
+
185
191
  ## Testing
186
192
 
187
193
  Create a postgres database:
@@ -49,8 +49,14 @@ class AbstractFeature < ActiveRecord::Base
49
49
  where(:feature_type => 'point')
50
50
  end
51
51
 
52
- def self.within_distance_of_point(lat, lng, distance_in_meters)
53
- where("ST_DWithin(features.geog, ST_Point(:lng, :lat), :distance)", :lat => lat, :lng => lng, :distance => distance_in_meters)
52
+ def self.within_distance_of_point(lat, lng, distance_in_meters, geom = 'geom_lowres')
53
+ point_sql =
54
+ case geom.to_s
55
+ when 'geog' then 'ST_Point(:lng, :lat)'
56
+ else "ST_Transform(ST_SetSRID(ST_Point(:lng, :lat), 4326), #{detect_srid(geom)})"
57
+ end
58
+
59
+ where("ST_DWithin(features.#{geom}, #{point_sql}, :distance)", :lat => lat, :lng => lng, :distance => distance_in_meters)
54
60
  end
55
61
 
56
62
  def self.area_in_square_meters(geom = 'geom_lowres')
@@ -69,11 +75,11 @@ class AbstractFeature < ActiveRecord::Base
69
75
  end
70
76
 
71
77
  def self.intersecting(other)
72
- join_other_features(other).where('ST_Intersects(features.geom_lowres, other_features.geom_lowres)').uniq
78
+ join_other_features(other).where('ST_Intersects(features.geom_lowres, other_features.geom_lowres)').distinct
73
79
  end
74
80
 
75
81
  def self.within_distance(other, distance_in_meters)
76
- join_other_features(other).where('ST_DWithin(features.geom_lowres, other_features.geom_lowres, ?)', distance_in_meters).uniq
82
+ join_other_features(other).where('ST_DWithin(features.geom_lowres, other_features.geom_lowres, ?)', distance_in_meters).distinct
77
83
  end
78
84
 
79
85
  def self.invalid(column = 'geog::geometry')
@@ -126,8 +132,8 @@ class AbstractFeature < ActiveRecord::Base
126
132
  SQL
127
133
  end
128
134
 
129
- def self.mvt(*args)
130
- select_sql = mvt_sql(*args)
135
+ def self.mvt(*args, **kwargs)
136
+ select_sql = mvt_sql(*args, **kwargs)
131
137
 
132
138
  # Result is a hex string representing the desired binary output so we need to convert it to binary
133
139
  result = SpatialFeatures::Utils.select_db_value(select_sql)
@@ -248,7 +254,7 @@ class AbstractFeature < ActiveRecord::Base
248
254
  self.geog = SpatialFeatures::Utils.select_db_value("SELECT ST_Force2D('#{geog}')")
249
255
  end
250
256
 
251
- SRID_CACHE = {}
257
+ SRID_CACHE = HashWithIndifferentAccess.new
252
258
  def self.detect_srid(column_name)
253
259
  SRID_CACHE[column_name] ||= SpatialFeatures::Utils.select_db_value("SELECT Find_SRID('public', '#{table_name}', '#{column_name}')")
254
260
  end
@@ -2,10 +2,34 @@ class SpatialProximity < ActiveRecord::Base
2
2
  belongs_to :model_a, :polymorphic => true
3
3
  belongs_to :model_b, :polymorphic => true
4
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')})
5
+ def self.between(scope1, scope2)
6
+ where condition_sql(scope1, scope2, <<~SQL.squish)
7
+ (#{SpatialFeatures::Utils.polymorphic_condition(scope1, 'model_a')} AND #{SpatialFeatures::Utils.polymorphic_condition(scope2, 'model_b')})
9
8
  SQL
10
9
  end
10
+
11
+ def self.condition_sql(scope1, scope2, template, pattern_a = 'model_a', pattern_b = 'model_b')
12
+ scope1_type = SpatialFeatures::Utils.base_class_of(scope1).to_s
13
+ scope2_type = SpatialFeatures::Utils.base_class_of(scope2).to_s
14
+
15
+ if scope1_type < scope2_type
16
+ template
17
+ elsif scope1_type > scope2_type
18
+ template.gsub(pattern_a, 'model_c').gsub(pattern_b, pattern_a).gsub('model_c', pattern_b)
19
+ else
20
+ <<~SQL.squish
21
+ (#{template}) OR (#{template.gsub(pattern_a, 'model_c').gsub(pattern_b, pattern_a).gsub('model_c', pattern_b)})
22
+ SQL
23
+ end
24
+ end
25
+
26
+ # Ensure the 'earliest' model is always model a
27
+ def self.normalize
28
+ unnormalized
29
+ .update_all('model_a_type = model_b_type, model_b_type = model_a_type, model_a_id = model_b_id, model_b_id = model_a_id')
30
+ end
31
+
32
+ def self.unnormalized
33
+ where('model_a_type > model_b_type OR (model_a_type = model_b_type AND model_a_id > model_b_id)')
34
+ end
11
35
  end
@@ -3,3 +3,7 @@ Mime::Type.register "application/vnd.bounds+json", :bounds
3
3
  Mime::Type.register "application/vnd.mapbox-vector-tile", :mvt
4
4
  Mime::Type.register "application/vnd.google-earth.kml+xml", :kml
5
5
  Mime::Type.register "application/vnd.google-earth.kmz", :kmz
6
+ Mime::Type.register "application/zip", :zip
7
+ Mime::Type.register "application/zip", :piz
8
+ Mime::Type.register "application/zip", :zap
9
+ Mime::Type.register "application/zip", :shpz
@@ -84,11 +84,13 @@ module SpatialFeatures
84
84
  results.each do |id, distance, area|
85
85
  klass_record.id = id
86
86
  SpatialProximity.create! do |proximity|
87
+ # Always make the spatial model earliest type and id be model a so we can optimize queries
88
+ data = [[Utils.base_class(record).to_s, record.id], [Utils.base_class(klass_record).to_s, klass_record.id]]
89
+ data.sort!
90
+
87
91
  # Set id and type instead of model to avoid autosaving the klass_record
88
- proximity.model_a_id = record.id
89
- proximity.model_a_type = Utils.base_class(record)
90
- proximity.model_b_id = klass_record.id
91
- proximity.model_b_type = Utils.base_class(klass_record)
92
+ proximity.model_a_type, proximity.model_a_id = data.first
93
+ proximity.model_b_type, proximity.model_b_id = data.second
92
94
  proximity.distance_in_meters = distance
93
95
  proximity.intersection_area_in_square_meters = area
94
96
  end
@@ -4,14 +4,14 @@ module SpatialFeatures
4
4
  module Download
5
5
  # file can be a url, path, or file, any of which can return be a zipped archive
6
6
  def self.open(file)
7
- file = Kernel.open(file)
7
+ file = URI.open(file)
8
8
  file = normalize_file(file) if file.is_a?(StringIO)
9
9
  return file
10
10
  end
11
11
 
12
12
  # file can be a url, path, or file, any of which can return be a zipped archive
13
- def self.open_each(file, unzip: nil, **unzip_options)
14
- file = Download.open(file)
13
+ def self.open_each(path_or_url, unzip: nil, **unzip_options)
14
+ file = Download.open(path_or_url)
15
15
  files = if unzip && Unzip.is_zip?(file)
16
16
  find_in_zip(file, find: unzip, **unzip_options)
17
17
  else
@@ -13,14 +13,14 @@ module SpatialFeatures
13
13
  end
14
14
 
15
15
  module ClassMethods
16
- def update_features!(skip_invalid: false, **options)
16
+ def update_features!(skip_invalid: false, allow_blank: false, **options)
17
17
  find_each do |record|
18
- record.update_features!(skip_invalid: skip_invalid, **options)
18
+ record.update_features!(skip_invalid: skip_invalid, allow_blank: allow_blank, **options)
19
19
  end
20
20
  end
21
21
  end
22
22
 
23
- def update_features!(skip_invalid: false, **options)
23
+ def update_features!(skip_invalid: false, allow_blank: false, **options)
24
24
  options = options.reverse_merge(spatial_features_options)
25
25
  tmpdir = options.fetch(:tmpdir) { Dir.mktmpdir("ruby_spatial_features") }
26
26
 
@@ -31,7 +31,7 @@ module SpatialFeatures
31
31
  return if features_cache_key_matches?(cache_key)
32
32
 
33
33
  run_callbacks :update_features do
34
- import_features(imports, skip_invalid)
34
+ features = import_features(imports, skip_invalid)
35
35
  update_features_cache_key(cache_key)
36
36
  update_features_area
37
37
 
@@ -40,11 +40,17 @@ module SpatialFeatures
40
40
  else
41
41
  update_spatial_cache(options.slice(:spatial_cache))
42
42
  end
43
- end
44
43
 
45
- return true
44
+ if imports.present? && features.compact_blank.empty? && !allow_blank
45
+ raise EmptyImportError, "No spatial features were found when updating"
46
+ end
47
+ end
46
48
  end
49
+
50
+ return true
47
51
  rescue StandardError => e
52
+ raise e if e.is_a?(EmptyImportError)
53
+
48
54
  if skip_invalid
49
55
  Rails.logger.warn "Error updating #{self.class} #{self.id}. #{e.message}"
50
56
  return nil
@@ -137,7 +143,7 @@ module SpatialFeatures
137
143
  raise ImportError, "Error updating #{self.class} #{self.id}. #{errors.to_sentence}"
138
144
  end
139
145
 
140
- return features
146
+ valid
141
147
  end
142
148
 
143
149
  def features_cache_key_matches?(cache_key)
@@ -13,14 +13,14 @@ module SpatialFeatures
13
13
  has_many :features, lambda { extending FeaturesAssociationExtensions }, :as => :spatial_model, :dependent => :delete_all
14
14
  has_one :aggregate_feature, lambda { extending FeaturesAssociationExtensions }, :as => :spatial_model, :dependent => :delete
15
15
 
16
- scope :with_features, lambda { joins(:features).uniq }
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(:spatial_caches => { :intersection_model_type => Utils.class_name_with_ancestors(klass) }).uniq }
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
- scope :with_stale_spatial_cache, lambda { joins(:spatial_caches).where("#{table_name}.features_hash != spatial_caches.features_hash").uniq } if has_spatial_features_hash?
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
25
  has_many :spatial_caches, :as => :spatial_model, :dependent => :delete_all, :class_name => 'SpatialCache'
26
26
  has_many :model_a_spatial_proximities, :as => :model_a, :class_name => 'SpatialProximity', :dependent => :delete_all
@@ -136,10 +136,8 @@ module SpatialFeatures
136
136
  other_class = Utils.base_class_of(other)
137
137
  self_class = Utils.base_class_of(self)
138
138
 
139
- joins <<~SQL
140
- INNER JOIN spatial_proximities
141
- ON (spatial_proximities.model_a_type = '#{self_class}' AND spatial_proximities.model_a_id = #{table_name}.id AND spatial_proximities.model_b_type = '#{other_class}' AND spatial_proximities.model_b_id IN (#{Utils.id_sql(other)}))
142
- OR (spatial_proximities.model_b_type = '#{self_class}' AND spatial_proximities.model_b_id = #{table_name}.id AND spatial_proximities.model_a_type = '#{other_class}' AND spatial_proximities.model_a_id IN (#{Utils.id_sql(other)}))
139
+ joins "INNER JOIN spatial_proximities ON " + SpatialProximity.condition_sql(self, other, <<~SQL.squish)
140
+ (spatial_proximities.model_a_type = '#{self_class}' AND spatial_proximities.model_a_id = #{table_name}.id AND spatial_proximities.model_b_type = '#{other_class}' AND spatial_proximities.model_b_id IN (#{Utils.id_sql(other)}))
143
141
  SQL
144
142
  end
145
143
 
@@ -228,20 +226,19 @@ module SpatialFeatures
228
226
  end
229
227
 
230
228
  def total_intersection_area_percentage(klass)
231
- return 0.0 unless features_area_in_square_meters > 0
229
+ total_area = features_area_in_square_meters
232
230
 
233
- ((total_intersection_area_in_square_meters(klass) / features_area_in_square_meters) * 100).round(1)
231
+ return if total_area.nil?
232
+ return 0.0 if total_area.zero?
233
+
234
+ ((total_intersection_area_in_square_meters(klass) / total_area) * 100).round(1)
234
235
  end
235
236
 
236
237
  def features_area_in_square_meters
237
- @features_area_in_square_meters ||=
238
- if has_attribute?(:area)
239
- area
240
- elsif association(:aggregate_feature).loaded?
241
- aggregate_feature&.area
242
- else
243
- aggregate_features.pluck(:area).first
244
- end
238
+ @features_area_in_square_meters ||= area if has_attribute?(:area) # Calculated area column
239
+ @features_area_in_square_meters ||= features_area if has_attribute?(:features_area) # Cached area column
240
+ @features_area_in_square_meters ||= aggregate_feature&.area if association(:aggregate_feature).loaded?
241
+ @features_area_in_square_meters ||= aggregate_features.pluck(:area).first
245
242
  end
246
243
 
247
244
  def total_intersection_area_in_square_meters(other)
@@ -56,4 +56,5 @@ module SpatialFeatures
56
56
  # EXCEPTIONS
57
57
 
58
58
  class ImportError < StandardError; end
59
+ class EmptyImportError < ImportError; end
59
60
  end
@@ -3,16 +3,23 @@ require 'open-uri'
3
3
  module SpatialFeatures
4
4
  module Importers
5
5
  class File < SimpleDelegator
6
- INVALID_ARCHIVE = "Archive did not contain a .kml, .shp, .json, or .geojson file.".freeze
6
+ SUPPORTED_SPATIAL_FORMATS = %w(application/zip application/x-shp+zip application/vnd.google-earth.kml+xml application/vnd.google-earth.kmz application/vnd.geo+json).freeze
7
+ SUPPORTED_SPATIAL_FILE_EXTENSIONS = %w(.zip .zap .piz .shpz .kml .kmz .json .geojson).freeze
7
8
  SUPPORTED_FORMATS = "Supported formats are KMZ, KML, zipped ArcGIS shapefiles, ESRI JSON, and GeoJSON.".freeze
8
9
 
10
+ def self.invalid_archive!(filename)
11
+ filename = ::File.basename(filename.to_s)
12
+ raise ImportError, "#{filename} did not contain a spatial file. #{SUPPORTED_FORMATS}"
13
+ end
14
+ delegate :invalid_archive!, :to => :class
15
+
9
16
  FILE_PATTERNS = [/\.kml$/, /\.shp$/, /\.json$/, /\.geojson$/]
10
17
  def self.create_all(data, **options)
11
18
  Download.open_each(data, unzip: FILE_PATTERNS, downcase: true, tmpdir: options[:tmpdir]).map do |file|
12
19
  new(data, **options, current_file: file)
13
20
  end
14
21
  rescue Unzip::PathNotFound
15
- raise ImportError, INVALID_ARCHIVE + " " + SUPPORTED_FORMATS
22
+ invalid_archive!(data)
16
23
  end
17
24
 
18
25
  # The File importer may be initialized multiple times by `::create_all` if it
@@ -21,24 +28,32 @@ module SpatialFeatures
21
28
  # processed.
22
29
  #
23
30
  # If no `current_file` is passed then we just take the first valid file that we find.
24
- def initialize(data, *args, current_file: nil, **options)
31
+ def initialize(data, current_file: nil, **options)
25
32
  begin
26
33
  current_file ||= Download.open_each(data, unzip: FILE_PATTERNS, downcase: true, tmpdir: options[:tmpdir]).first
27
34
  rescue Unzip::PathNotFound
28
- raise ImportError, INVALID_ARCHIVE
35
+ invalid_archive!(data)
29
36
  end
30
37
 
31
- case ::File.extname(current_file.path.downcase)
38
+ filename = current_file.path.downcase
39
+
40
+ case ::File.extname(filename)
32
41
  when '.kml'
33
- __setobj__(KMLFile.new(current_file, *args, **options))
42
+ __setobj__(KMLFile.new(current_file, **options))
34
43
  when '.shp'
35
- __setobj__(Shapefile.new(current_file, *args, **options))
44
+ __setobj__(Shapefile.new(current_file, **options))
36
45
  when '.json', '.geojson'
37
- __setobj__(ESRIGeoJSON.new(current_file.path, *args, **options))
46
+ __setobj__(ESRIGeoJSON.new(current_file.path, **options))
38
47
  else
39
- raise ImportError, "Could not import file. " + SUPPORTED_FORMATS
48
+ import_error!(filename)
40
49
  end
41
50
  end
51
+
52
+ private
53
+
54
+ def import_error!(filename)
55
+ raise ImportError, "Could not import #{filename}. " + SUPPORTED_FORMATS
56
+ end
42
57
  end
43
58
  end
44
59
  end
@@ -1,8 +1,8 @@
1
1
  module SpatialFeatures
2
2
  module Importers
3
3
  class Geomark < KMLFile
4
- def initialize(geomark, *args)
5
- super geomark_url(geomark), *args
4
+ def initialize(geomark, **options)
5
+ super geomark_url(geomark), **options
6
6
  end
7
7
 
8
8
  private
@@ -6,9 +6,15 @@ module SpatialFeatures
6
6
  # <SimpleData name> keys that may contain <img> tags
7
7
  IMAGE_METADATA_KEYS = %w[pdfmaps_photos].freeze
8
8
 
9
- def initialize(data, base_dir: nil, **args)
9
+ # matches a coordinate pair with an optional altitude, including invalid altitudes like NaN
10
+ # -118.1,50.9,NaN
11
+ # -118.1,50.9,0
12
+ # -118.1,50.9
13
+ COORDINATES_WITH_ALTITUDE = /((-?\d+\.\d+,-?\d+\.\d+)(,-?[a-zA-Z\d\.]+))/.freeze
14
+
15
+ def initialize(data, base_dir: nil, **options)
10
16
  @base_dir = base_dir
11
- super data, **args
17
+ super data, **options
12
18
  end
13
19
 
14
20
  private
@@ -39,6 +45,7 @@ module SpatialFeatures
39
45
  @kml_document ||= begin
40
46
  doc = Nokogiri::XML(@data)
41
47
  raise ImportError, "Invalid KML document (root node was '#{doc.root&.name}')" unless doc.root&.name.to_s.casecmp?('kml')
48
+ raise ImportError, "NetworkLink elements are not supported" unless doc.search('NetworkLink').empty?
42
49
  doc
43
50
  end
44
51
  end
@@ -51,6 +58,8 @@ module SpatialFeatures
51
58
  geom = nil
52
59
  conn = nil
53
60
 
61
+ strip_altitude(kml)
62
+
54
63
  # Do query in a new thread so we use a new connection (if the query fails it will poison the transaction of the current connection)
55
64
  #
56
65
  # We manually checkout a new connection since Rails re-uses DB connections across threads.
@@ -105,6 +114,13 @@ module SpatialFeatures
105
114
  end
106
115
  return metadata
107
116
  end
117
+
118
+ def strip_altitude(kml)
119
+ kml.css('coordinates').each do |coordinates|
120
+ next unless COORDINATES_WITH_ALTITUDE.match?(coordinates.content)
121
+ coordinates.content = coordinates.content.gsub(COORDINATES_WITH_ALTITUDE, '\2')
122
+ end
123
+ end
108
124
  end
109
125
  end
110
126
  end
@@ -3,7 +3,7 @@ require 'ostruct'
3
3
  module SpatialFeatures
4
4
  module Importers
5
5
  class KMLFileArcGIS < KMLFile
6
- def initialize(data, *args)
6
+ def initialize(data, **options)
7
7
  super
8
8
 
9
9
  rescue SocketError, Errno::ECONNREFUSED
@@ -6,7 +6,7 @@ module SpatialFeatures
6
6
  class Shapefile < Base
7
7
  class_attribute :default_proj4_projection
8
8
 
9
- def initialize(data, *args, proj4: nil, **options)
9
+ def initialize(data, proj4: nil, **options)
10
10
  super(data, **options)
11
11
  @proj4 = proj4
12
12
  end
@@ -20,7 +20,7 @@ module SpatialFeatures
20
20
  new(file, **options)
21
21
  end
22
22
  rescue Unzip::PathNotFound
23
- raise ImportError, INVALID_ARCHIVE
23
+ invalid_archive!(data)
24
24
  end
25
25
 
26
26
  private
@@ -56,7 +56,7 @@ module SpatialFeatures
56
56
  # Instantiate objects to hold the kml and records for each venn polygon
57
57
  polygons.group_by{|row| row['kml']}.collect do |kml, rows|
58
58
  # 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
- 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 } }
59
+ 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
60
  OpenStruct.new(:kml => kml, :records => records)
61
61
  end
62
62
  end
@@ -1,3 +1,3 @@
1
1
  module SpatialFeatures
2
- VERSION = "3.1.0"
2
+ VERSION = "3.3.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: 3.1.0
4
+ version: 3.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: 2022-05-24 00:00:00.000000000 Z
12
+ date: 2023-10-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -203,7 +203,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
203
203
  - !ruby/object:Gem::Version
204
204
  version: '0'
205
205
  requirements: []
206
- rubygems_version: 3.0.3
206
+ rubygems_version: 3.3.23
207
207
  signing_key:
208
208
  specification_version: 4
209
209
  summary: Adds spatial methods to a model.