spatial_features 3.10.0 → 3.10.2

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: a5ada735515d4ec029917c5d00994254804e9b270c65093f32672d851fb1a452
4
- data.tar.gz: 1828f79799ab6e91bfcd0503aba6091b9e0120d33aa28a05158f729dc1738e0f
3
+ metadata.gz: fdeeed7e535906a617d6fcfdf50ddf8b05acc0a588e14cade7f9ca62700b593c
4
+ data.tar.gz: 20886debc5f838c5cc9cc1d096867d10b1c32ad8884a836021d53ee5bc161c64
5
5
  SHA512:
6
- metadata.gz: ff7491b8acd5875ad23909d2ce988ea793d00707be4ece7d9cb7b700c4a1e064db949778ccc029f948f3ea389d91b7e8c54dceac55fb3206bd928bd97319e760
7
- data.tar.gz: f1256bec3bb256e2789e3e7e0462f8f710f23fc1a493814b8a9126c3cd5495680394f588ca5121908875847d5ae3748a10a0e65d5b0f84d7ee65a5304a238c4b
6
+ metadata.gz: 447c879b49a4e46931f2c2ce64f3acb3c1642f35909bf95e2c6d683eadd6205b0e393acd90cb77b90fd602b5b56e02cf2a87f511f5f8026c7cb46367a0dcfe7d
7
+ data.tar.gz: d5dd31db08df30837aaf1edea842e309e8d1008067cfb255c3134fdc5c1193d6d97943e8c11cb25cb9658e81de28836fbfe53ac5bd33c1e1e494d945fe4cf2ee
@@ -41,8 +41,15 @@ module SpatialFeatures
41
41
  update_spatial_cache(options.slice(:spatial_cache))
42
42
  end
43
43
 
44
+ # Attribute each warning to the file it came from (e.g. `archive.zip/layer.kml`)
45
+ # so a multi-file or multi-source import makes clear which file was affected.
46
+ import_warnings = imports.flat_map do |import|
47
+ import.warnings.map {|warning| [import.source_identifier.presence, warning].compact.join(': ') }
48
+ end
49
+ store_feature_update_warnings(import_warnings)
50
+
44
51
  if imports.present? && features.compact_blank.empty? && !allow_blank
45
- raise EmptyImportError, "No spatial features were found when updating"
52
+ raise EmptyImportError, ["No spatial features were found when updating.", *import_warnings].join(' ')
46
53
  end
47
54
  end
48
55
  end
@@ -45,6 +45,30 @@ module SpatialFeatures
45
45
  spatial_processing_status(:update_features!) == :failure
46
46
  end
47
47
 
48
+ # Non-fatal messages from the most recent successful feature import (e.g. parts of
49
+ # the source that were skipped). Stored alongside the status cache so they survive
50
+ # job completion, since successful Delayed::Jobs are deleted and can't be read back.
51
+ WARNINGS_CACHE_KEY = 'feature_update_warnings'.freeze
52
+
53
+ def feature_update_warnings
54
+ return [] unless has_attribute?(:spatial_processing_status_cache)
55
+ Array(spatial_processing_status_cache[WARNINGS_CACHE_KEY])
56
+ end
57
+
58
+ def store_feature_update_warnings(warnings)
59
+ return unless has_attribute?(:spatial_processing_status_cache)
60
+
61
+ cache = spatial_processing_status_cache
62
+ warnings = Array(warnings).reject(&:blank?)
63
+ if warnings.present?
64
+ cache[WARNINGS_CACHE_KEY] = warnings
65
+ else
66
+ cache.delete(WARNINGS_CACHE_KEY)
67
+ end
68
+ self.spatial_processing_status_cache = cache
69
+ update_column(:spatial_processing_status_cache, cache) if persisted? && will_save_change_to_spatial_processing_status_cache?
70
+ end
71
+
48
72
  def spatial_processing_status(method_name, use_cache: true)
49
73
  if has_attribute?(:spatial_processing_status_cache)
50
74
  update_spatial_processing_status(method_name) unless use_cache
@@ -4,12 +4,19 @@ module SpatialFeatures
4
4
  module Importers
5
5
  class Base
6
6
  attr_reader :errors
7
+
8
+ # Non-fatal messages about parts of the source that could not be imported but
9
+ # did not prevent the remaining features from importing (e.g. unsupported
10
+ # elements). Unlike `errors`, warnings never abort the import.
11
+ attr_reader :warnings
12
+
7
13
  attr_accessor :source_identifier # An identifier for the source of the features. Used to differentiate groups of features on the spatial model.
8
14
 
9
15
  def initialize(data, make_valid: false, tmpdir: nil, source_identifier: nil, feature_name: ->(record) { record.name })
10
16
  @make_valid = make_valid
11
17
  @data = data
12
18
  @errors = []
19
+ @warnings = []
13
20
  @tmpdir = tmpdir
14
21
  @source_identifier = source_identifier
15
22
  @feature_name = feature_name
@@ -46,34 +46,50 @@ module SpatialFeatures
46
46
  doc = Nokogiri::XML(@data)
47
47
  doc.remove_namespaces! # We don't care about namespaces since the document is going to be filled with placemark geometry and we want it all without needing to deal with namespaces
48
48
  raise ImportError, "Invalid KML document (root node was '#{doc.root&.name}')" unless doc.root&.name.to_s.casecmp?('kml')
49
- raise ImportError, "NetworkLink elements are not supported" unless doc.search('NetworkLink').empty?
49
+ discard_network_links(doc)
50
50
  doc
51
51
  end
52
52
  end
53
53
 
54
+ # NetworkLinks reference geometry hosted elsewhere (e.g. a remote KMZ) rather
55
+ # than embedding it, so there is nothing for us to import from them. Rather than
56
+ # failing the whole file, we drop them and record a warning so any embedded
57
+ # geometry still imports and the user is told which layers were skipped. If the
58
+ # file contained nothing but NetworkLinks the import ends up empty and the
59
+ # EmptyImportError surfaces the warning as the reason.
60
+ def discard_network_links(doc)
61
+ network_links = doc.search('NetworkLink')
62
+ return if network_links.empty?
63
+
64
+ names = network_links.map {|link| link.at_css('name')&.text.presence }.compact.uniq
65
+ described = names.any? ? ": #{names.to_sentence}" : ''
66
+ @warnings << "Skipped #{network_links.size} network-linked #{'layer'.pluralize(network_links.size)} that reference remote data and cannot be imported#{described}."
67
+
68
+ network_links.remove
69
+ end
70
+
54
71
  def blank_feature?(feature)
55
72
  feature.css('coordinates').text.blank?
56
73
  end
57
74
 
58
75
  def geom_from_kml(kml)
59
- geom = nil
60
- conn = nil
61
-
62
76
  strip_altitude(kml)
63
77
 
64
- # 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)
65
- #
66
- # We manually checkout a new connection since Rails re-uses DB connections across threads.
67
- Thread.new do
68
- conn = ActiveRecord::Base.connection_pool.checkout
69
- geom = conn.select_value("SELECT ST_GeomFromKML(#{conn.quote(kml.to_s)})")
70
- rescue ActiveRecord::StatementInvalid => e # Discard Invalid KML features
71
- geom = nil
72
- ensure
73
- ActiveRecord::Base.connection_pool.checkin(conn) if conn
74
- end.join
75
-
76
- return geom
78
+ # Run the parse inside a SAVEPOINT so a `ST_GeomFromKML` failure on a single
79
+ # invalid feature rolls back only that savepoint, not the surrounding
80
+ # transaction. The previous implementation spawned a thread and checked out
81
+ # a fresh connection for the same isolation reason, but on Rails 8 that
82
+ # pattern deadlocks under `use_transactional_fixtures` — the test pins its
83
+ # connection, the spawned thread blocks indefinitely waiting on
84
+ # `ActiveRecord::Base.connection_pool.checkout`. A nested transaction
85
+ # (`requires_new: true`) gives identical fault containment without crossing
86
+ # thread boundaries.
87
+ ActiveRecord::Base.transaction(requires_new: true) do
88
+ conn = ActiveRecord::Base.connection
89
+ conn.select_value("SELECT ST_GeomFromKML(#{conn.quote(kml.to_s)})")
90
+ end
91
+ rescue ActiveRecord::StatementInvalid # Discard invalid KML features
92
+ nil
77
93
  end
78
94
 
79
95
  def images_from_metadata(metadata)
@@ -1,3 +1,3 @@
1
1
  module SpatialFeatures
2
- VERSION = "3.10.0"
2
+ VERSION = "3.10.2"
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.10.0
4
+ version: 3.10.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Wallace
@@ -235,7 +235,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
235
235
  - !ruby/object:Gem::Version
236
236
  version: '0'
237
237
  requirements: []
238
- rubygems_version: 3.7.2
238
+ rubygems_version: 4.0.5
239
239
  specification_version: 4
240
240
  summary: Adds spatial methods to a model.
241
241
  test_files: []