spatial_features 2.17.2 → 2.17.3
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/README.md +5 -1
- data/app/models/abstract_feature.rb +6 -6
- data/app/models/aggregate_feature.rb +1 -1
- data/app/models/feature.rb +10 -0
- data/lib/spatial_features/download.rb +16 -6
- data/lib/spatial_features/has_spatial_features/feature_import.rb +2 -2
- data/lib/spatial_features/importers/base.rb +6 -0
- data/lib/spatial_features/importers/file.rb +22 -6
- data/lib/spatial_features/importers/geojson.rb +29 -0
- data/lib/spatial_features/importers/json_arcgis.rb +1 -5
- data/lib/spatial_features/importers/kml.rb +1 -1
- data/lib/spatial_features/importers/shapefile.rb +26 -8
- data/lib/spatial_features/unzip.rb +3 -3
- data/lib/spatial_features/utils.rb +9 -0
- data/lib/spatial_features/validation.rb +36 -42
- data/lib/spatial_features/version.rb +1 -1
- data/lib/spatial_features.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e0a8fb7f4886f07f02b8b110f7975d91538324d6cf2c5a70b3b247dfb6719c98
|
4
|
+
data.tar.gz: 15c681c5de6fccb10a515c8ff1f8a423a7c9fa1c1fded8955c2072dd76f5d4f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0a9224909db6dd85bf20cf9b9561e78cdc12f094423c86b0280f0e2a96e2dad9ae24030c4a4014b448f566e232b7dd37620d2a8890c411af97487a0047261346
|
7
|
+
data.tar.gz: 740174a0882bc82edd053e4adf31318c6afc51860a66a3d50068e3c106cc8ac9351061f9d4990c652c5f41704815e2d5b8c8ef3d6b7aaa47525d9663f01acce4
|
data/README.md
CHANGED
@@ -110,7 +110,7 @@ You can specify multiple import sources for geometry. Each key is a method that
|
|
110
110
|
each value is the Importer to use to parse the data. See each Importer for more details.
|
111
111
|
```ruby
|
112
112
|
class Location < ActiveRecord::Base
|
113
|
-
has_spatial_features :import => { :remote_kml_url => 'KMLFile', :file => 'File' }
|
113
|
+
has_spatial_features :import => { :remote_kml_url => 'KMLFile', :file => 'File', :geojson => 'GeoJSON' }
|
114
114
|
|
115
115
|
def remote_kml_url
|
116
116
|
"www.test.com/kml/#{id}.kml"
|
@@ -119,6 +119,10 @@ class Location < ActiveRecord::Base
|
|
119
119
|
def file
|
120
120
|
File.open('local/files/my_kml')
|
121
121
|
end
|
122
|
+
|
123
|
+
def geojson
|
124
|
+
{ "type" => "FeatureCollection", "features" => [] }
|
125
|
+
end
|
122
126
|
end
|
123
127
|
```
|
124
128
|
|
@@ -56,7 +56,7 @@ class AbstractFeature < ActiveRecord::Base
|
|
56
56
|
|
57
57
|
def self.area_in_square_meters(geom = 'geom_lowres')
|
58
58
|
current_scope = all.polygons
|
59
|
-
unscoped {
|
59
|
+
unscoped { SpatialFeatures::Utils.select_db_value(select("ST_Area(ST_Union(#{geom}))").from(current_scope, :features)).to_f }
|
60
60
|
end
|
61
61
|
|
62
62
|
def self.total_intersection_area_in_square_meters(other_features, geom = 'geom_lowres')
|
@@ -66,7 +66,7 @@ class AbstractFeature < ActiveRecord::Base
|
|
66
66
|
query = base_class.unscoped.select('ST_Area(ST_Intersection(ST_Union(features.geom), ST_Union(other_features.geom)))')
|
67
67
|
.from(scope, "features")
|
68
68
|
.joins("INNER JOIN (#{other_scope.to_sql}) AS other_features ON ST_Intersects(features.geom, other_features.geom)")
|
69
|
-
return
|
69
|
+
return SpatialFeatures::Utils.select_db_value(query).to_f
|
70
70
|
end
|
71
71
|
|
72
72
|
def self.intersecting(other)
|
@@ -147,7 +147,7 @@ class AbstractFeature < ActiveRecord::Base
|
|
147
147
|
)
|
148
148
|
)
|
149
149
|
SQL
|
150
|
-
|
150
|
+
SpatialFeatures::Utils.select_db_value(all.select(sql))
|
151
151
|
end
|
152
152
|
|
153
153
|
def feature_bounds
|
@@ -175,17 +175,17 @@ class AbstractFeature < ActiveRecord::Base
|
|
175
175
|
private
|
176
176
|
|
177
177
|
def make_valid
|
178
|
-
self.geog =
|
178
|
+
self.geog = SpatialFeatures::Utils.select_db_value("SELECT ST_Buffer('#{sanitize}', 0)")
|
179
179
|
end
|
180
180
|
|
181
181
|
# Use ST_Force2D to discard z-coordinates that cause failures later in the process
|
182
182
|
def sanitize
|
183
|
-
self.geog =
|
183
|
+
self.geog = SpatialFeatures::Utils.select_db_value("SELECT ST_Force2D('#{geog}')")
|
184
184
|
end
|
185
185
|
|
186
186
|
SRID_CACHE = {}
|
187
187
|
def self.detect_srid(column_name)
|
188
|
-
SRID_CACHE[column_name] ||=
|
188
|
+
SRID_CACHE[column_name] ||= SpatialFeatures::Utils.select_db_value("SELECT Find_SRID('public', '#{table_name}', '#{column_name}')")
|
189
189
|
end
|
190
190
|
|
191
191
|
def self.join_other_features(other)
|
@@ -14,7 +14,7 @@ class AggregateFeature < AbstractFeature
|
|
14
14
|
SQL
|
15
15
|
|
16
16
|
# Remove empty features so ST_COLLECT doesn't choke. This seems to be a difference between PostGIS 2.x and 3.x
|
17
|
-
self.geog =
|
17
|
+
self.geog = SpatialFeatures::Utils.select_db_value <<~SQL
|
18
18
|
SELECT COALESCE(ST_Collect(unnest)::geography, ST_GeogFromText('MULTIPOLYGON EMPTY'))
|
19
19
|
FROM (SELECT unnest(#{feature_array_sql})) AS features
|
20
20
|
WHERE NOT ST_IsEmpty(unnest)
|
data/app/models/feature.rb
CHANGED
@@ -9,6 +9,8 @@ class Feature < AbstractFeature
|
|
9
9
|
|
10
10
|
validates_inclusion_of :feature_type, :in => FEATURE_TYPES
|
11
11
|
|
12
|
+
before_save :truncate_name
|
13
|
+
|
12
14
|
after_save :refresh_aggregate, if: :automatically_refresh_aggregate?
|
13
15
|
|
14
16
|
# Features are used for display so we also cache their KML representation
|
@@ -60,4 +62,12 @@ class Feature < AbstractFeature
|
|
60
62
|
# this field blank.
|
61
63
|
spatial_model_id? && automatically_refresh_aggregate && saved_change_to_geog?
|
62
64
|
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def truncate_name
|
69
|
+
return unless name?
|
70
|
+
col_size = Feature.columns_hash["name"]&.limit || 255
|
71
|
+
self.name = name.to_s.truncate(col_size)
|
72
|
+
end
|
63
73
|
end
|
@@ -4,20 +4,30 @@ 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.read(file, unzip: nil, **unzip_options)
|
7
|
-
file =
|
7
|
+
file = Download.open_each(file, unzip: unzip, **unzip_options).first
|
8
8
|
path = ::File.path(file)
|
9
9
|
return ::File.read(path)
|
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(file)
|
13
14
|
file = Kernel.open(file)
|
14
15
|
file = normalize_file(file) if file.is_a?(StringIO)
|
15
|
-
if unzip && Unzip.is_zip?(file)
|
16
|
-
file = find_in_zip(file, find: unzip, **unzip_options)
|
17
|
-
end
|
18
16
|
return file
|
19
17
|
end
|
20
18
|
|
19
|
+
# file can be a url, path, or file, any of which can return be a zipped archive
|
20
|
+
def self.open_each(file, unzip: nil, **unzip_options)
|
21
|
+
file = Download.open(file)
|
22
|
+
files = if unzip && Unzip.is_zip?(file)
|
23
|
+
find_in_zip(file, find: unzip, **unzip_options)
|
24
|
+
else
|
25
|
+
[file]
|
26
|
+
end
|
27
|
+
|
28
|
+
return files.map { |f| File.open(f) }
|
29
|
+
end
|
30
|
+
|
21
31
|
def self.normalize_file(file)
|
22
32
|
Tempfile.new.tap do |temp|
|
23
33
|
temp.binmode
|
@@ -33,7 +43,7 @@ module SpatialFeatures
|
|
33
43
|
end
|
34
44
|
|
35
45
|
def self.find_in_zip(file, find:, **unzip_options)
|
36
|
-
|
46
|
+
Unzip.paths(file, :find => find, **unzip_options)
|
37
47
|
end
|
38
48
|
end
|
39
49
|
end
|
@@ -68,8 +68,8 @@ module SpatialFeatures
|
|
68
68
|
|
69
69
|
def spatial_feature_imports(import_options, make_valid)
|
70
70
|
import_options.flat_map do |data_method, importer_name|
|
71
|
-
Array.wrap(send(data_method)).
|
72
|
-
spatial_importer_from_name(importer_name).
|
71
|
+
Array.wrap(send(data_method)).flat_map do |data|
|
72
|
+
spatial_importer_from_name(importer_name).create_all(data, :make_valid => make_valid) if data.present?
|
73
73
|
end
|
74
74
|
end.compact
|
75
75
|
end
|
@@ -19,6 +19,12 @@ module SpatialFeatures
|
|
19
19
|
@cache_key ||= Digest::MD5.hexdigest(@data)
|
20
20
|
end
|
21
21
|
|
22
|
+
# factory method that should always be used instead of `new` when creating importers
|
23
|
+
# returns an array of Importer::* objects
|
24
|
+
def self.create_all(data, **options)
|
25
|
+
[new(data, **options)]
|
26
|
+
end
|
27
|
+
|
22
28
|
private
|
23
29
|
|
24
30
|
def build_features
|
@@ -3,18 +3,34 @@ require 'open-uri'
|
|
3
3
|
module SpatialFeatures
|
4
4
|
module Importers
|
5
5
|
class File < SimpleDelegator
|
6
|
-
|
6
|
+
INVALID_ARCHIVE = "Archive did not contain a .kml or .shp file. Supported formats are KMZ, KML, and zipped ArcGIS shapefiles.".freeze
|
7
|
+
|
8
|
+
def self.create_all(data, **options)
|
9
|
+
Download.open_each(data, unzip: [/\.kml$/, /\.shp$/], downcase: true).map do |file|
|
10
|
+
new(data, **options, current_file: file)
|
11
|
+
end
|
12
|
+
rescue Unzip::PathNotFound
|
13
|
+
raise ImportError, INVALID_ARCHIVE
|
14
|
+
end
|
15
|
+
|
16
|
+
# The File importer may be initialized multiple times by `::create_all` if it
|
17
|
+
# receives ZIP data containing multiple KML or SHP files. We use `current_file`
|
18
|
+
# to distinguish which file in the archive is currently being
|
19
|
+
# processed.
|
20
|
+
#
|
21
|
+
# If no `current_file` is passed then we just take the first valid file that we find.
|
22
|
+
def initialize(data, *args, current_file: nil, **options)
|
7
23
|
begin
|
8
|
-
|
24
|
+
current_file ||= Download.open_each(data, unzip: [/\.kml$/, /\.shp$/], downcase: true).first
|
9
25
|
rescue Unzip::PathNotFound
|
10
|
-
raise ImportError,
|
26
|
+
raise ImportError, INVALID_ARCHIVE
|
11
27
|
end
|
12
28
|
|
13
|
-
case ::File.extname(
|
29
|
+
case ::File.extname(current_file.path.downcase)
|
14
30
|
when '.kml'
|
15
|
-
__setobj__(KMLFile.new(
|
31
|
+
__setobj__(KMLFile.new(current_file, *args, **options))
|
16
32
|
when '.shp'
|
17
|
-
__setobj__(Shapefile.new(
|
33
|
+
__setobj__(Shapefile.new(current_file, *args, **options))
|
18
34
|
else
|
19
35
|
raise ImportError, "Could not import file. Supported formats are KMZ, KML, and zipped ArcGIS shapefiles"
|
20
36
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'digest/md5'
|
3
|
+
|
4
|
+
module SpatialFeatures
|
5
|
+
module Importers
|
6
|
+
class GeoJSON < Base
|
7
|
+
def cache_key
|
8
|
+
@cache_key ||= Digest::MD5.hexdigest(@data.to_json)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def each_record(&block)
|
14
|
+
return unless @data
|
15
|
+
|
16
|
+
@data.fetch('features', []).each do |record|
|
17
|
+
metadata = record['properties'] || {}
|
18
|
+
name = metadata.delete('name')
|
19
|
+
yield OpenStruct.new(
|
20
|
+
:feature_type => record['geometry']['type'],
|
21
|
+
:geog => SpatialFeatures::Utils.geom_from_json(record['geometry']),
|
22
|
+
:name => name,
|
23
|
+
:metadata => metadata
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -15,7 +15,7 @@ module SpatialFeatures
|
|
15
15
|
json['features'].each do |record|
|
16
16
|
yield OpenStruct.new(
|
17
17
|
:feature_type => record['geometry']['type'],
|
18
|
-
:geog => geom_from_json(record['geometry']),
|
18
|
+
:geog => SpatialFeatures::Utils.geom_from_json(record['geometry']),
|
19
19
|
:metadata => record['properties']
|
20
20
|
)
|
21
21
|
end
|
@@ -24,10 +24,6 @@ module SpatialFeatures
|
|
24
24
|
def esri_json_to_geojson(url)
|
25
25
|
JSON.parse(`ogr2ogr -f GeoJSON /dev/stdout "#{url}" OGRGeoJSON`)
|
26
26
|
end
|
27
|
-
|
28
|
-
def geom_from_json(geometry)
|
29
|
-
ActiveRecord::Base.connection.select_value("SELECT ST_GeomFromGeoJSON('#{geometry.to_json}')")
|
30
|
-
end
|
31
27
|
end
|
32
28
|
end
|
33
29
|
end
|
@@ -35,7 +35,7 @@ module SpatialFeatures
|
|
35
35
|
|
36
36
|
# 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)
|
37
37
|
Thread.new do
|
38
|
-
geom =
|
38
|
+
geom = SpatialFeatures::Utils.select_db_value("SELECT ST_GeomFromKML(#{ActiveRecord::Base.connection.quote(kml.to_s)})")
|
39
39
|
rescue ActiveRecord::StatementInvalid => e # Discard Invalid KML features
|
40
40
|
geom = nil
|
41
41
|
end.join
|
@@ -7,7 +7,7 @@ module SpatialFeatures
|
|
7
7
|
class_attribute :default_proj4_projection
|
8
8
|
|
9
9
|
def initialize(data, *args, proj4: nil, **options)
|
10
|
-
super(data,
|
10
|
+
super(data, **options)
|
11
11
|
@proj4 = proj4
|
12
12
|
end
|
13
13
|
|
@@ -15,8 +15,21 @@ module SpatialFeatures
|
|
15
15
|
@cache_key ||= Digest::MD5.file(archive).to_s
|
16
16
|
end
|
17
17
|
|
18
|
+
def self.create_all(data, **options)
|
19
|
+
Download.open_each(data, unzip: [/\.shp$/], downcase: true).map do |file|
|
20
|
+
new(file, **options)
|
21
|
+
end
|
22
|
+
rescue Unzip::PathNotFound
|
23
|
+
raise ImportError, INVALID_ARCHIVE
|
24
|
+
end
|
25
|
+
|
18
26
|
private
|
19
27
|
|
28
|
+
def build_features
|
29
|
+
validate_shapefile!
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
20
33
|
def each_record(&block)
|
21
34
|
RGeo::Shapefile::Reader.open(file.path) do |records|
|
22
35
|
records.each do |record|
|
@@ -49,17 +62,22 @@ module SpatialFeatures
|
|
49
62
|
SQL
|
50
63
|
end
|
51
64
|
|
52
|
-
|
65
|
+
# the individual SHP file for processing (automatically extracted from a ZIP archive if necessary)
|
53
66
|
def file
|
54
|
-
@file ||=
|
55
|
-
|
56
|
-
|
67
|
+
@file ||= Unzip.is_zip?(archive) ? possible_shp_files.first : archive
|
68
|
+
end
|
69
|
+
|
70
|
+
# a zip archive may contain multiple SHP files
|
71
|
+
def possible_shp_files
|
72
|
+
@possible_shp_files ||= begin
|
73
|
+
Download.open_each(archive, unzip: /\.shp$/, downcase: true)
|
74
|
+
rescue Unzip::PathNotFound
|
75
|
+
raise ::SpatialFeatures::Importers::IncompleteShapefileArchive, "Shapefile archive is missing a SHP file"
|
57
76
|
end
|
58
77
|
end
|
59
78
|
|
60
|
-
def
|
61
|
-
|
62
|
-
Validation.validate_shapefile_archive!(Download.entries(archive), default_proj4_projection: default_proj4_projection)
|
79
|
+
def validate_shapefile!
|
80
|
+
Validation.validate_shapefile!(file, default_proj4_projection: default_proj4_projection)
|
63
81
|
end
|
64
82
|
|
65
83
|
def archive
|
@@ -6,11 +6,11 @@ module SpatialFeatures
|
|
6
6
|
paths = extract(file_path, **extract_options)
|
7
7
|
|
8
8
|
if find = Array.wrap(find).presence
|
9
|
-
paths = paths.
|
10
|
-
raise(PathNotFound, "Archive did not contain a file matching #{find}")
|
9
|
+
paths = paths.select {|path| find.any? {|pattern| path.index(pattern) } }
|
10
|
+
raise(PathNotFound, "Archive did not contain a file matching #{find}") if paths.empty?
|
11
11
|
end
|
12
12
|
|
13
|
-
return paths
|
13
|
+
return Array(paths)
|
14
14
|
end
|
15
15
|
|
16
16
|
def self.extract(file_path, output_dir = Dir.mktmpdir, downcase: false)
|
@@ -52,5 +52,14 @@ module SpatialFeatures
|
|
52
52
|
object.unscope(:select).select(:id).to_sql
|
53
53
|
end
|
54
54
|
end
|
55
|
+
|
56
|
+
def select_db_value(query)
|
57
|
+
ActiveRecord::Base.connection.select_value(query)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Convert a hash of GeoJSON data into a PostGIS geometry object
|
61
|
+
def geom_from_json(geometry)
|
62
|
+
select_db_value("SELECT ST_GeomFromGeoJSON('#{geometry.to_json}')")
|
63
|
+
end
|
55
64
|
end
|
56
65
|
end
|
@@ -1,55 +1,49 @@
|
|
1
1
|
module SpatialFeatures
|
2
2
|
module Validation
|
3
|
-
# SHP file must come first
|
4
3
|
REQUIRED_SHAPEFILE_COMPONENT_EXTENSIONS = %w[shp shx dbf prj].freeze
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
5
|
+
class << self
|
6
|
+
# Check if a shapefile includes the required component files, otherwise
|
7
|
+
# raise an exception.
|
8
|
+
#
|
9
|
+
# This validation operates by checking sibling files in the same directory,
|
10
|
+
# similar to how `rgeo-shapefile` validates SHP files.
|
11
|
+
#
|
12
|
+
# @param [File] shp_file A File object
|
13
|
+
# @param [String] default_proj4_projection Optional, if supplied we don't raise an exception when we're missing a .PRJ file
|
14
|
+
def validate_shapefile!(shp_file, default_proj4_projection: nil)
|
15
|
+
basename = File.basename(shp_file.path, '.*')
|
16
|
+
path = shp_file.path.to_s.sub(/\.shp$/i, "")
|
17
|
+
|
18
|
+
required_extensions = REQUIRED_SHAPEFILE_COMPONENT_EXTENSIONS
|
19
|
+
required_extensions -= ['prj'] if default_proj4_projection
|
20
|
+
|
21
|
+
required_extensions.each do |ext|
|
22
|
+
component_path = "#{path}.#{ext}"
|
23
|
+
next if ::File.file?(component_path) && ::File.readable?(component_path)
|
24
|
+
|
25
|
+
case ext
|
26
|
+
when "prj"
|
27
|
+
raise ::SpatialFeatures::Importers::IndeterminateShapefileProjection, "Shapefile archive is missing a projection file: #{File.basename(component_path)}"
|
28
|
+
else
|
29
|
+
raise ::SpatialFeatures::Importers::IncompleteShapefileArchive, "Shapefile archive is missing a required file: #{File.basename(component_path)}"
|
30
|
+
end
|
19
31
|
end
|
20
32
|
|
21
|
-
|
33
|
+
true
|
22
34
|
end
|
23
35
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
|
-
REQUIRED_SHAPEFILE_COMPONENT_EXTENSIONS[1..-1].each do |ext|
|
33
|
-
ext_basename = zip_file_entries[ext]
|
34
|
-
next if ext_basename&.casecmp?(shapefile_basename)
|
35
|
-
|
36
|
-
case ext
|
37
|
-
when "prj"
|
38
|
-
# special case for missing projection files to allow using default_proj4_projection
|
39
|
-
next if default_proj4_projection
|
40
|
-
|
41
|
-
raise ::SpatialFeatures::Importers::IndeterminateShapefileProjection, "Shapefile archive is missing a projection file: #{expected_component_path(shapefile_basename, ext)}"
|
42
|
-
else
|
43
|
-
# for all un-handled cases of missing files raise the more generic error
|
44
|
-
raise ::SpatialFeatures::Importers::IncompleteShapefileArchive, "Shapefile archive is missing a required file: #{expected_component_path(shapefile_basename, ext)}"
|
36
|
+
# Validation helper that takes examines an entire ZIP file
|
37
|
+
#
|
38
|
+
# Useful for validating before persisting records but not used internally
|
39
|
+
def validate_shapefile_archive!(path, default_proj4_projection: nil, allow_generic_zip_files: false)
|
40
|
+
Download.open_each(path, unzip: /\.shp$/, downcase: true).each do |shp_file|
|
41
|
+
validate_shapefile!(shp_file, default_proj4_projection: default_proj4_projection)
|
45
42
|
end
|
43
|
+
rescue Unzip::PathNotFound
|
44
|
+
raise ::SpatialFeatures::Importers::IncompleteShapefileArchive, "Shapefile archive is missing a SHP file" \
|
45
|
+
unless allow_generic_zip_files
|
46
46
|
end
|
47
|
-
|
48
|
-
true
|
49
|
-
end
|
50
|
-
|
51
|
-
def self.expected_component_path(basename, ext)
|
52
|
-
"#{basename}.#{ext}"
|
53
47
|
end
|
54
48
|
end
|
55
49
|
end
|
data/lib/spatial_features.rb
CHANGED
@@ -20,6 +20,7 @@ require 'spatial_features/has_spatial_features/feature_import'
|
|
20
20
|
|
21
21
|
require 'spatial_features/importers/base'
|
22
22
|
require 'spatial_features/importers/file'
|
23
|
+
require 'spatial_features/importers/geojson'
|
23
24
|
require 'spatial_features/importers/kml'
|
24
25
|
require 'spatial_features/importers/kml_file'
|
25
26
|
require 'spatial_features/importers/kml_file_arcgis'
|
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: 2.17.
|
4
|
+
version: 2.17.3
|
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: 2021-
|
12
|
+
date: 2021-10-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -157,6 +157,7 @@ files:
|
|
157
157
|
- lib/spatial_features/has_spatial_features/queued_spatial_processing.rb
|
158
158
|
- lib/spatial_features/importers/base.rb
|
159
159
|
- lib/spatial_features/importers/file.rb
|
160
|
+
- lib/spatial_features/importers/geojson.rb
|
160
161
|
- lib/spatial_features/importers/geomark.rb
|
161
162
|
- lib/spatial_features/importers/json_arcgis.rb
|
162
163
|
- lib/spatial_features/importers/kml.rb
|