spatial_features 3.11.1 → 3.11.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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0d90a3143573947c3e70f74845fbd475dcddbd871df0baff29dd028d236c9833
|
|
4
|
+
data.tar.gz: 37bf93eb6f87120a85f2e028efe77debb91772cc3cd77289f75e59deb5d5ab4e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0fa27a478c51fccc0a7c97ecfe5214faeea64e930b8ab0643cddc2fd4e3d2570905503308c6c2f0b1c54b5384054d774e82d48a3df6044fad135ff4688f5c9ea
|
|
7
|
+
data.tar.gz: e07a9760f62c00fd47dbb350b4b669b067ca4a98cb8c836d7fa8d7deb0432bd6836ae1d647ee025bcf5b494d9b4dbb189174be9cfa6f90efaccbe4947e5a9cd4
|
data/Rakefile
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'digest/md5'
|
|
2
|
+
require 'open3'
|
|
2
3
|
require 'spatial_features/importers/geo_json'
|
|
3
4
|
|
|
4
5
|
module SpatialFeatures
|
|
@@ -15,11 +16,9 @@ module SpatialFeatures
|
|
|
15
16
|
private
|
|
16
17
|
|
|
17
18
|
def esri_json_to_geojson(url)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
`ogr2ogr -t_srs EPSG:4326 -f GeoJSON /dev/stdout "#{url}" OGRGeoJSON`
|
|
22
|
-
end
|
|
19
|
+
args = ['ogr2ogr', '-t_srs', 'EPSG:4326', '-f', 'GeoJSON', '/dev/stdout', url]
|
|
20
|
+
args << 'OGRGeoJSON' unless URI.parse(url).relative? # A relative URL is a local file path
|
|
21
|
+
Open3.capture2(*args).first
|
|
23
22
|
end
|
|
24
23
|
end
|
|
25
24
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require 'ostruct'
|
|
2
2
|
require 'digest/md5'
|
|
3
|
+
require 'open3'
|
|
3
4
|
|
|
4
5
|
module SpatialFeatures
|
|
5
6
|
module Importers
|
|
@@ -55,8 +56,11 @@ module SpatialFeatures
|
|
|
55
56
|
if proj4 == PROJ4_4326
|
|
56
57
|
data[:geog] = wkt
|
|
57
58
|
else
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
# `proj4` is read out of the uploaded archive's .prj, so it is quoted before it
|
|
60
|
+
# reaches the statement.
|
|
61
|
+
conn = ActiveRecord::Base.connection
|
|
62
|
+
data[:geog] = conn.select_value <<-SQL
|
|
63
|
+
SELECT ST_Transform(ST_GeomFromText(#{conn.quote(wkt)}), #{conn.quote(proj4)}, 4326) AS geog
|
|
60
64
|
SQL
|
|
61
65
|
end
|
|
62
66
|
|
|
@@ -90,18 +94,27 @@ module SpatialFeatures
|
|
|
90
94
|
end
|
|
91
95
|
|
|
92
96
|
# Use OGR2OGR to reproject into EPSG:4326 so we can skip the reprojection step per-feature
|
|
97
|
+
#
|
|
98
|
+
# @note Both the projection and the path come from the uploaded archive, so they are
|
|
99
|
+
# passed as an argv list and no shell parses them. Assembling a command string here
|
|
100
|
+
# would let an uploader run commands of their choosing.
|
|
93
101
|
def project_to_4326(file_path)
|
|
94
102
|
output_path = Tempfile.create([::File.basename(file_path, '.shp') + '_epsg_4326_', '.shp']) { |file| file.path }
|
|
95
103
|
return unless (proj4 = proj4_from_file(file_path))
|
|
96
|
-
return unless system(
|
|
104
|
+
return unless system('ogr2ogr', '-s_srs', proj4, '-t_srs', 'EPSG:4326', output_path, file_path)
|
|
97
105
|
return ::File.open(output_path)
|
|
98
106
|
end
|
|
99
107
|
|
|
108
|
+
# Returns the PROJ.4 projection string GDAL reads out of the file's .prj, or nil when
|
|
109
|
+
# it can't determine one. Returns nil when `gdalsrsinfo` is not installed, which
|
|
110
|
+
# `proj4_projection` reports.
|
|
100
111
|
def proj4_from_file(file_path)
|
|
101
112
|
# Sanitize: "'+proj=utm +zone=11 +datum=NAD83 +units=m +no_defs '\n" and lately
|
|
102
113
|
# "+proj=utm +zone=11 +datum=NAD83 +units=m +no_defs \n" to
|
|
103
114
|
# "+proj=utm +zone=11 +datum=NAD83 +units=m +no_defs"
|
|
104
|
-
|
|
115
|
+
Open3.capture2('gdalsrsinfo', file_path, '-o', 'proj4').first.strip.remove(/^'|'$/).presence
|
|
116
|
+
rescue Errno::ENOENT
|
|
117
|
+
nil
|
|
105
118
|
end
|
|
106
119
|
|
|
107
120
|
# a zip archive may contain multiple SHP files
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'fileutils'
|
|
2
|
+
require 'pathname'
|
|
2
3
|
|
|
3
4
|
module SpatialFeatures
|
|
4
5
|
module Unzip
|
|
@@ -51,16 +52,26 @@ module SpatialFeatures
|
|
|
51
52
|
end
|
|
52
53
|
end
|
|
53
54
|
|
|
55
|
+
# Extracts the archive's entries and returns their paths, skipping entries that a name
|
|
56
|
+
# cannot place inside `tmpdir`.
|
|
57
|
+
#
|
|
58
|
+
# @param tmpdir [String] where to extract to. Must already exist, since the destination
|
|
59
|
+
# is resolved before anything is written. Defaults to a fresh temporary directory.
|
|
54
60
|
def self.extract(file_path, tmpdir: nil, downcase: false)
|
|
55
61
|
tmpdir ||= Dir.mktmpdir
|
|
62
|
+
root = Pathname.new(tmpdir).realpath
|
|
63
|
+
|
|
56
64
|
[].tap do |paths|
|
|
57
65
|
entries(file_path).each do |entry|
|
|
58
66
|
next if entry.name =~ IGNORED_ENTRY_PATHS
|
|
67
|
+
next if entry.symlink?
|
|
59
68
|
|
|
60
69
|
output_filename = entry.name
|
|
61
70
|
output_filename = output_filename.downcase if downcase
|
|
62
71
|
|
|
63
|
-
path =
|
|
72
|
+
path = contained_path(root, output_filename)
|
|
73
|
+
next unless path
|
|
74
|
+
|
|
64
75
|
directory = File.dirname(path)
|
|
65
76
|
basename = File.basename(path)
|
|
66
77
|
|
|
@@ -72,6 +83,24 @@ module SpatialFeatures
|
|
|
72
83
|
end
|
|
73
84
|
end
|
|
74
85
|
|
|
86
|
+
# Returns where an entry of this name lands under `root`, or nil when the name would place
|
|
87
|
+
# it outside. Entry names are stored in the archive verbatim, so they can carry `..`
|
|
88
|
+
# segments that climb out of the directory we extract into.
|
|
89
|
+
#
|
|
90
|
+
# @note `root` must already be a real path, and `::extract` skips symlink entries, so
|
|
91
|
+
# nothing beneath it is a symlink. `cleanpath` resolves `..` lexically, so a symlink
|
|
92
|
+
# under `root` would let an entry name walk back out of the directory unnoticed.
|
|
93
|
+
def self.contained_path(root, output_filename)
|
|
94
|
+
path = root.join(output_filename).cleanpath
|
|
95
|
+
|
|
96
|
+
return unless path.to_s.start_with?("#{root}#{File::SEPARATOR}")
|
|
97
|
+
|
|
98
|
+
# `cleanpath` drops the trailing separator that marks a directory entry, which
|
|
99
|
+
# `PathNotFound#extensions` reads to tell directories from the files it reports.
|
|
100
|
+
output_filename.end_with?(File::SEPARATOR) ? "#{path}#{File::SEPARATOR}" : path.to_s
|
|
101
|
+
end
|
|
102
|
+
private_class_method :contained_path
|
|
103
|
+
|
|
75
104
|
def self.names(file_path)
|
|
76
105
|
entries(file_path).collect(&:name)
|
|
77
106
|
end
|