spatial_features 3.1.0 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
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.