From 24035608340ac305aa61efcb9d0284f3615a4303 Mon Sep 17 00:00:00 2001 From: Elias Carvalho Date: Wed, 28 Jun 2023 16:23:55 -0300 Subject: [PATCH 1/7] Add 'agwrite' function --- src/GeoTables.jl | 4 +- src/agwrite.jl | 101 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 src/agwrite.jl diff --git a/src/GeoTables.jl b/src/GeoTables.jl index adcccb5..12fd907 100644 --- a/src/GeoTables.jl +++ b/src/GeoTables.jl @@ -17,6 +17,7 @@ import GeoInterface as GI include("conversion.jl") include("geotable.jl") +include("agwrite.jl") """ load(fname, layer=0, lazy=false, kwargs...) @@ -66,6 +67,7 @@ Optionally, specify keyword arguments accepted by - `*.shp` via Shapefile.jl - `*.geojson` via GeoJSON.jl +- Other formats via ArchGDAL.jl """ function save(fname, geotable; kwargs...) if endswith(fname, ".shp") @@ -73,7 +75,7 @@ function save(fname, geotable; kwargs...) elseif endswith(fname, ".geojson") GJS.write(fname, geotable; kwargs...) else - throw(ErrorException("file format not supported")) + agwrite(fname, geotable; kwargs...) end end diff --git a/src/agwrite.jl b/src/agwrite.jl new file mode 100644 index 0000000..2bd3c45 --- /dev/null +++ b/src/agwrite.jl @@ -0,0 +1,101 @@ +# adapted from https://github.com/evetion/GeoDataFrames.jl/blob/master/src/io.jl +# and from https://github.com/yeesian/ArchGDAL.jl/blob/master/test/test_tables.jl#L264 + +const DRIVER = AG.extensions() + +const SUPORTED = [ + ".arrow", + ".arrows", + ".db", + ".dbf", + ".feather", + ".fgb", + ".geojson", + ".geojsonl", + ".geojsons", + ".gml", + ".gpkg", + ".ipc", + ".jml", + ".kml", + ".mbtiles", + ".mvt", + ".nc", + ".parquet", + ".pbf", + ".pdf", + ".shp", + ".shz", + ".sql", + ".sqlite", + ".xml" +] + +const AGGEOM = Dict( + GI.PointTrait() => AG.wkbPoint, + GI.LineStringTrait() => AG.wkbLineString, + GI.LinearRingTrait() => AG.wkbMultiLineString, + GI.PolygonTrait() => AG.wkbPolygon, + GI.MultiPointTrait() => AG.wkbMultiPoint, + GI.MultiLineStringTrait() => AG.wkbMultiLineString, + GI.MultiPolygonTrait() => AG.wkbMultiPolygon +) + +asstrings(options::Dict{<:AbstractString,<:AbstractString}) = + [uppercase(String(k)) * "=" * String(v) for (k, v) in options] + +function agwrite(fname, geotable; layername="data", options=Dict("geometry_name" => "geometry")) + ext = last(splitext(fname)) + if ext ∉ SUPORTED + error("file format not supported") + end + + geoms = domain(geotable) + table = values(geotable) + rows = Tables.rows(table) + schema = Tables.schema(table) + + # Set geometry name in options + if !haskey(options, "geometry_name") + options["geometry_name"] = "geometry" + end + + driver = AG.getdriver(DRIVER[ext]) + trait = GI.geomtrait(first(geoms)) + aggeom = get(AGGEOM, trait, AG.wkbUnknown) + optionlist = asstrings(options) + agtypes = map(schema.types) do type + try + T = nonmissingtype(type) + convert(AG.OGRFieldType, T) + catch + error("type $type not supported") + end + end + + AG.create(fname; driver) do dataset + AG.createlayer(; dataset, name=layername, geom=aggeom, options=optionlist) do layer + for (name, type) in zip(schema.names, agtypes) + AG.addfielddefn!(layer, String(name), type) + end + + for (row, geom) in zip(rows, geoms) + AG.addfeature(layer) do feature + for name in schema.names + x = Tables.getcolumn(row, name) + i = AG.findfieldindex(feature, name) + if ismissing(x) + AG.setfieldnull!(feature, i) + else + AG.setfield!(feature, i, x) + end + end + + AG.setgeom!(feature, GI.convert(AG.IGeometry, geom)) + end + end + end + end + + fname +end From 01c645aec58f1f616e6f7f0a79dff0fe656f2500 Mon Sep 17 00:00:00 2001 From: Elias Carvalho Date: Wed, 28 Jun 2023 17:25:49 -0300 Subject: [PATCH 2/7] [WIP] Add tests --- test/runtests.jl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/runtests.jl b/test/runtests.jl index 486448e..a52f423 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -309,6 +309,16 @@ savedir = mktempdir() newtable = GeoTables.load(joinpath(savedir, "t$file.shp")) end end + + # @testset "agwrite" begin + # table = GeoTables.load(joinpath(datadir, "lines.geojson"), numbertype=Float64) + # for ext in GeoTables.SUPORTED + # GeoTables.agwrite(joinpath(savedir, "ag-lines$ext"), table) + # kwargs = ext == ".geojson" ? (; numbertype=Float64) : () + # agtable = GeoTables.load(joinpath(savedir, "ag-lines$ext"); kwargs...) + # @test agtable == table + # end + # end end @testset "gadm" begin From 95fbf8074be2f38d087df54eb15bad2c0dc8e24d Mon Sep 17 00:00:00 2001 From: Elias Carvalho Date: Wed, 28 Jun 2023 17:29:03 -0300 Subject: [PATCH 3/7] Fix typo --- src/agwrite.jl | 4 ++-- test/runtests.jl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/agwrite.jl b/src/agwrite.jl index 2bd3c45..df6c31d 100644 --- a/src/agwrite.jl +++ b/src/agwrite.jl @@ -3,7 +3,7 @@ const DRIVER = AG.extensions() -const SUPORTED = [ +const SUPPORTED = [ ".arrow", ".arrows", ".db", @@ -46,7 +46,7 @@ asstrings(options::Dict{<:AbstractString,<:AbstractString}) = function agwrite(fname, geotable; layername="data", options=Dict("geometry_name" => "geometry")) ext = last(splitext(fname)) - if ext ∉ SUPORTED + if ext ∉ SUPPORTED error("file format not supported") end diff --git a/test/runtests.jl b/test/runtests.jl index a52f423..7ce104f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -312,7 +312,7 @@ savedir = mktempdir() # @testset "agwrite" begin # table = GeoTables.load(joinpath(datadir, "lines.geojson"), numbertype=Float64) - # for ext in GeoTables.SUPORTED + # for ext in GeoTables.SUPPORTED # GeoTables.agwrite(joinpath(savedir, "ag-lines$ext"), table) # kwargs = ext == ".geojson" ? (; numbertype=Float64) : () # agtable = GeoTables.load(joinpath(savedir, "ag-lines$ext"); kwargs...) From 4255bb2490ac8d146d2395587c1e5bc0834ebab6 Mon Sep 17 00:00:00 2001 From: Elias Carvalho Date: Wed, 28 Jun 2023 18:31:13 -0300 Subject: [PATCH 4/7] Add support for GPKG only --- src/GeoTables.jl | 6 ++++-- src/agwrite.jl | 34 +--------------------------------- test/runtests.jl | 25 ++++++++++++++++--------- 3 files changed, 21 insertions(+), 44 deletions(-) diff --git a/src/GeoTables.jl b/src/GeoTables.jl index 12fd907..87526b2 100644 --- a/src/GeoTables.jl +++ b/src/GeoTables.jl @@ -67,15 +67,17 @@ Optionally, specify keyword arguments accepted by - `*.shp` via Shapefile.jl - `*.geojson` via GeoJSON.jl -- Other formats via ArchGDAL.jl +- `*.gpkg` via ArchGDAL.jl """ function save(fname, geotable; kwargs...) if endswith(fname, ".shp") SHP.write(fname, geotable; kwargs...) elseif endswith(fname, ".geojson") GJS.write(fname, geotable; kwargs...) - else + elseif endswith(fname, ".gpkg") agwrite(fname, geotable; kwargs...) + else + error("file format not supported") end end diff --git a/src/agwrite.jl b/src/agwrite.jl index df6c31d..035f0fa 100644 --- a/src/agwrite.jl +++ b/src/agwrite.jl @@ -3,34 +3,6 @@ const DRIVER = AG.extensions() -const SUPPORTED = [ - ".arrow", - ".arrows", - ".db", - ".dbf", - ".feather", - ".fgb", - ".geojson", - ".geojsonl", - ".geojsons", - ".gml", - ".gpkg", - ".ipc", - ".jml", - ".kml", - ".mbtiles", - ".mvt", - ".nc", - ".parquet", - ".pbf", - ".pdf", - ".shp", - ".shz", - ".sql", - ".sqlite", - ".xml" -] - const AGGEOM = Dict( GI.PointTrait() => AG.wkbPoint, GI.LineStringTrait() => AG.wkbLineString, @@ -45,11 +17,6 @@ asstrings(options::Dict{<:AbstractString,<:AbstractString}) = [uppercase(String(k)) * "=" * String(v) for (k, v) in options] function agwrite(fname, geotable; layername="data", options=Dict("geometry_name" => "geometry")) - ext = last(splitext(fname)) - if ext ∉ SUPPORTED - error("file format not supported") - end - geoms = domain(geotable) table = values(geotable) rows = Tables.rows(table) @@ -60,6 +27,7 @@ function agwrite(fname, geotable; layername="data", options=Dict("geometry_name" options["geometry_name"] = "geometry" end + ext = last(splitext(fname)) driver = AG.getdriver(DRIVER[ext]) trait = GI.geomtrait(first(geoms)) aggeom = get(AGGEOM, trait, AG.wkbUnknown) diff --git a/test/runtests.jl b/test/runtests.jl index 7ce104f..d198e9f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -310,15 +310,22 @@ savedir = mktempdir() end end - # @testset "agwrite" begin - # table = GeoTables.load(joinpath(datadir, "lines.geojson"), numbertype=Float64) - # for ext in GeoTables.SUPPORTED - # GeoTables.agwrite(joinpath(savedir, "ag-lines$ext"), table) - # kwargs = ext == ".geojson" ? (; numbertype=Float64) : () - # agtable = GeoTables.load(joinpath(savedir, "ag-lines$ext"); kwargs...) - # @test agtable == table - # end - # end + @testset "agwrite" begin + table = GeoTables.load(joinpath(datadir, "lines.geojson"), numbertype=Float64) + GeoTables.save(joinpath(savedir, "ag-lines.gpkg"), table) + agtable = GeoTables.load(joinpath(savedir, "ag-lines.gpkg")) + @test agtable == table + + table = GeoTables.load(joinpath(datadir, "points.geojson"), numbertype=Float64) + GeoTables.save(joinpath(savedir, "ag-points.gpkg"), table) + agtable = GeoTables.load(joinpath(savedir, "ag-points.gpkg")) + @test agtable == table + + table = GeoTables.load(joinpath(datadir, "polygons.geojson"), numbertype=Float64) + GeoTables.save(joinpath(savedir, "ag-polygons.gpkg"), table) + agtable = GeoTables.load(joinpath(savedir, "ag-polygons.gpkg")) + @test agtable == table + end end @testset "gadm" begin From 790b923a9f2cf753f875a0d327bd9d351079453c Mon Sep 17 00:00:00 2001 From: Elias Carvalho Date: Wed, 28 Jun 2023 18:46:33 -0300 Subject: [PATCH 5/7] Use 'geom=wkbUnknown' in layer creation --- src/agwrite.jl | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/agwrite.jl b/src/agwrite.jl index 035f0fa..86dab1c 100644 --- a/src/agwrite.jl +++ b/src/agwrite.jl @@ -3,16 +3,6 @@ const DRIVER = AG.extensions() -const AGGEOM = Dict( - GI.PointTrait() => AG.wkbPoint, - GI.LineStringTrait() => AG.wkbLineString, - GI.LinearRingTrait() => AG.wkbMultiLineString, - GI.PolygonTrait() => AG.wkbPolygon, - GI.MultiPointTrait() => AG.wkbMultiPoint, - GI.MultiLineStringTrait() => AG.wkbMultiLineString, - GI.MultiPolygonTrait() => AG.wkbMultiPolygon -) - asstrings(options::Dict{<:AbstractString,<:AbstractString}) = [uppercase(String(k)) * "=" * String(v) for (k, v) in options] @@ -29,8 +19,6 @@ function agwrite(fname, geotable; layername="data", options=Dict("geometry_name" ext = last(splitext(fname)) driver = AG.getdriver(DRIVER[ext]) - trait = GI.geomtrait(first(geoms)) - aggeom = get(AGGEOM, trait, AG.wkbUnknown) optionlist = asstrings(options) agtypes = map(schema.types) do type try @@ -42,7 +30,7 @@ function agwrite(fname, geotable; layername="data", options=Dict("geometry_name" end AG.create(fname; driver) do dataset - AG.createlayer(; dataset, name=layername, geom=aggeom, options=optionlist) do layer + AG.createlayer(; dataset, name=layername, options=optionlist) do layer for (name, type) in zip(schema.names, agtypes) AG.addfielddefn!(layer, String(name), type) end From 8347a0ea112ee0a030cadd376fe525c2e82feea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Hoffimann?= Date: Wed, 28 Jun 2023 18:56:42 -0300 Subject: [PATCH 6/7] Last fix --- src/GeoTables.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GeoTables.jl b/src/GeoTables.jl index 87526b2..a8e99f3 100644 --- a/src/GeoTables.jl +++ b/src/GeoTables.jl @@ -67,14 +67,14 @@ Optionally, specify keyword arguments accepted by - `*.shp` via Shapefile.jl - `*.geojson` via GeoJSON.jl -- `*.gpkg` via ArchGDAL.jl +- Other formats via ArchGDAL.jl """ function save(fname, geotable; kwargs...) if endswith(fname, ".shp") SHP.write(fname, geotable; kwargs...) elseif endswith(fname, ".geojson") GJS.write(fname, geotable; kwargs...) - elseif endswith(fname, ".gpkg") + else # fallback to GDAL agwrite(fname, geotable; kwargs...) else error("file format not supported") From 3c868590b571e27e9c693e1e5904bc9d08050c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Hoffimann?= Date: Wed, 28 Jun 2023 18:57:07 -0300 Subject: [PATCH 7/7] Last fix --- src/GeoTables.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/GeoTables.jl b/src/GeoTables.jl index a8e99f3..44447b0 100644 --- a/src/GeoTables.jl +++ b/src/GeoTables.jl @@ -76,8 +76,6 @@ function save(fname, geotable; kwargs...) GJS.write(fname, geotable; kwargs...) else # fallback to GDAL agwrite(fname, geotable; kwargs...) - else - error("file format not supported") end end