spatial_features 3.2.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 +4 -4
- data/app/models/abstract_feature.rb +9 -3
- data/config/initializers/mime_types.rb +4 -0
- data/lib/spatial_features/has_spatial_features/feature_import.rb +11 -5
- data/lib/spatial_features/has_spatial_features.rb +4 -8
- data/lib/spatial_features/importers/base.rb +1 -0
- data/lib/spatial_features/importers/file.rb +20 -5
- data/lib/spatial_features/importers/kml.rb +15 -0
- data/lib/spatial_features/importers/shapefile.rb +1 -1
- data/lib/spatial_features/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ba1ab9dfa18160f48292f26a63f75031187e44cc04aff73dba792832d482063
|
4
|
+
data.tar.gz: b25d8a6f4bd0412c3d2d9dd34a57c620a0593979304ddb9061c7f3b3e2d7f834
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1be4b279b3341582262bdb8d802c0786b1eb3607cef070f2a03813b2bc987d21fb0aa92323d7b49dbec01aa8b2e128f0b9982ce356dc4836875ccd5ad89f7092
|
7
|
+
data.tar.gz: 8e70dfe6f6bad6545c29fc5dbf1406b308b9cb44ff48ebfc849469912af6cf30640c829c2baf363a6790e9207f78d3c039276c002a50ce6319ccf0d9450f9df7
|
@@ -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
|
-
|
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')
|
@@ -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
|
@@ -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
|
@@ -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
|
+
|
44
|
+
if imports.present? && features.compact_blank.empty? && !allow_blank
|
45
|
+
raise EmptyImportError, "No spatial features were found when updating"
|
46
|
+
end
|
43
47
|
end
|
44
48
|
end
|
45
49
|
|
46
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
|
-
|
146
|
+
valid
|
141
147
|
end
|
142
148
|
|
143
149
|
def features_cache_key_matches?(cache_key)
|
@@ -235,14 +235,10 @@ module SpatialFeatures
|
|
235
235
|
end
|
236
236
|
|
237
237
|
def features_area_in_square_meters
|
238
|
-
@features_area_in_square_meters ||=
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
aggregate_feature&.area
|
243
|
-
else
|
244
|
-
aggregate_features.pluck(:area).first
|
245
|
-
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
|
246
242
|
end
|
247
243
|
|
248
244
|
def total_intersection_area_in_square_meters(other)
|
@@ -3,16 +3,23 @@ require 'open-uri'
|
|
3
3
|
module SpatialFeatures
|
4
4
|
module Importers
|
5
5
|
class File < SimpleDelegator
|
6
|
-
|
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
|
-
|
22
|
+
invalid_archive!(data)
|
16
23
|
end
|
17
24
|
|
18
25
|
# The File importer may be initialized multiple times by `::create_all` if it
|
@@ -25,10 +32,12 @@ module SpatialFeatures
|
|
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
|
-
|
35
|
+
invalid_archive!(data)
|
29
36
|
end
|
30
37
|
|
31
|
-
|
38
|
+
filename = current_file.path.downcase
|
39
|
+
|
40
|
+
case ::File.extname(filename)
|
32
41
|
when '.kml'
|
33
42
|
__setobj__(KMLFile.new(current_file, **options))
|
34
43
|
when '.shp'
|
@@ -36,9 +45,15 @@ module SpatialFeatures
|
|
36
45
|
when '.json', '.geojson'
|
37
46
|
__setobj__(ESRIGeoJSON.new(current_file.path, **options))
|
38
47
|
else
|
39
|
-
|
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
|
@@ -6,6 +6,12 @@ module SpatialFeatures
|
|
6
6
|
# <SimpleData name> keys that may contain <img> tags
|
7
7
|
IMAGE_METADATA_KEYS = %w[pdfmaps_photos].freeze
|
8
8
|
|
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
|
+
|
9
15
|
def initialize(data, base_dir: nil, **options)
|
10
16
|
@base_dir = base_dir
|
11
17
|
super data, **options
|
@@ -52,6 +58,8 @@ module SpatialFeatures
|
|
52
58
|
geom = nil
|
53
59
|
conn = nil
|
54
60
|
|
61
|
+
strip_altitude(kml)
|
62
|
+
|
55
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)
|
56
64
|
#
|
57
65
|
# We manually checkout a new connection since Rails re-uses DB connections across threads.
|
@@ -106,6 +114,13 @@ module SpatialFeatures
|
|
106
114
|
end
|
107
115
|
return metadata
|
108
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
|
109
124
|
end
|
110
125
|
end
|
111
126
|
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.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: 2023-
|
12
|
+
date: 2023-10-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|