Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into fix/empty-layer
Browse files Browse the repository at this point in the history
  • Loading branch information
evetion committed Nov 14, 2024
2 parents d5cd7ac + 58e2c03 commit 2fd7390
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 106 deletions.
16 changes: 10 additions & 6 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ GeoInterfaceRecipes = "0329782f-3d07-4b52-b9f6-d3137cf03c7a"
ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"

[weakdeps]
JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"

[extensions]
ArchGDALMakieExt = "Makie"
ArchGDALJLD2Ext = "JLD2"

[compat]
CEnum = "0.4, 0.5"
ColorTypes = "0.10, 0.11, 0.12"
Expand All @@ -33,13 +41,9 @@ GeoInterfaceRecipes = "1.0"
ImageCore = "0.8, 0.9, 0.10"
Makie = "0.20, 0.21"
Tables = "1"
JLD2 = "0.4, 0.5"
julia = "1.6"

[extensions]
ArchGDALMakieExt = "Makie"

[extras]
Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"

[weakdeps]
Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"
JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
23 changes: 23 additions & 0 deletions ext/ArchGDALJLD2Ext.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module ArchGDALJLD2Ext

import ArchGDAL as AG
import GeoInterface as GI
import JLD2

struct ArchGDALSerializedGeometry
# TODO: add spatial reference
wkb::Vector{UInt8}
end


JLD2.writeas(::Type{<: AG.AbstractGeometry}) = ArchGDALSerializedGeometry

function JLD2.wconvert(::Type{<: ArchGDALSerializedGeometry}, x::AG.AbstractGeometry)
return ArchGDALSerializedGeometry(AG.toWKB(x))
end

function JLD2.rconvert(::Type{<: AG.AbstractGeometry}, x::ArchGDALSerializedGeometry)
return AG.fromWKB(x.wkb)
end

end
14 changes: 11 additions & 3 deletions src/ogr/feature.jl
Original file line number Diff line number Diff line change
Expand Up @@ -529,14 +529,22 @@ null, it will return `missing`.
"""
function getfield(feature::AbstractFeature, i::Integer)
return if !isfieldset(feature, i)
nothing
return nothing
elseif isfieldnull(feature, i)
missing
return missing
else
_fieldtype = getfieldtype(getfielddefn(feature, i))
try
_fetchfield = _FETCHFIELD[_fieldtype]
_fetchfield(feature, i)
if _fieldtype in (OFTIntegerList, OFTRealList, OFTStringList, OFTInteger64List)
# copy to ensure that GDAL does not free / overwrite the memory.
# the docs for the field fetcher functions mention that the returned
# pointer is not valid for very long.
return Base.copy(_fetchfield(feature, i))
else
# for static types, we can just return the returned value.
return _fetchfield(feature, i)
end
catch e
if e isa KeyError
error(
Expand Down
1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ GDAL = "add2ef01-049f-52c4-9ee2-e494f65e021a"
GeoFormatTypes = "68eda718-8dee-11e9-39e7-89f7f65f511f"
GeoInterface = "cf35fbd7-0cd7-5166-be24-54bfbe79505f"
ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534"
JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Expand Down
2 changes: 1 addition & 1 deletion test/test_drivers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ import ArchGDAL as AG
"RandomWrite" => false,
"RandomRead" => true,
"CurveGeometries" => false,
"FastGetExtent" => false,
"FastGetExtent" => true,
"Transactions" => false,
"AlterFieldDefn" => false,
)
Expand Down
170 changes: 86 additions & 84 deletions test/test_feature.jl
Original file line number Diff line number Diff line change
Expand Up @@ -97,99 +97,101 @@ end
@test AG.validate(f, AG.F_VAL_ALLOW_DIFFERENT_GEOM_DIM, false) ==
true

@testset "Missing and Null Semantics" begin
@test isnothing(AG.getdefault(f, 1))
AG.setdefault!(AG.getfielddefn(f, 1), "default value")
@test AG.getdefault(f, 1) == "default value"
# This testset is commented because GDAL 3.9 has disabled most of this behaviour in C
# https://gdal.org/en/latest/development/rfc/rfc97_feature_and_fielddefn_sealing.html
# @testset "Missing and Null Semantics" begin
# @test isnothing(AG.getdefault(f, 1))
# AG.setdefault!(AG.getfielddefn(f, 1), "default value")
# @test AG.getdefault(f, 1) == "default value"

@test AG.isfieldsetandnotnull(f, 1)
@test AG.isfieldset(f, 1)
@test !AG.isfieldnull(f, 1)
@test AG.getfield(f, 1) == "point-a"
# @test AG.isfieldsetandnotnull(f, 1)
# @test AG.isfieldset(f, 1)
# @test !AG.isfieldnull(f, 1)
# @test AG.getfield(f, 1) == "point-a"

AG.unsetfield!(f, 1)
@test !AG.isfieldset(f, 1)
@test !AG.isfieldnull(f, 1) # carried over from earlier
@test isnothing(AG.getfield(f, 1))
# AG.unsetfield!(f, 1)
# @test !AG.isfieldset(f, 1)
# @test !AG.isfieldnull(f, 1) # carried over from earlier
# @test isnothing(AG.getfield(f, 1))

# unset & notnull: missing
AG.fillunsetwithdefault!(f)
# nothing has changed
@test isnothing(AG.getfield(f, 1))
# because it is a nullable field
@test AG.isnullable(AG.getfielddefn(f, 1))
# even though it is not a null value
@test !AG.isfieldnull(f, 1)
# the field is still not set
@test !AG.isfieldset(f, 1)
# # unset & notnull: missing
# AG.fillunsetwithdefault!(f)
# # nothing has changed
# @test isnothing(AG.getfield(f, 1))
# # because it is a nullable field
# @test AG.isnullable(AG.getfielddefn(f, 1))
# # even though it is not a null value
# @test !AG.isfieldnull(f, 1)
# # the field is still not set
# @test !AG.isfieldset(f, 1)

# set & notnull: value
AG.fillunsetwithdefault!(f, notnull = false)
if !AG.isdefaultdriverspecific(AG.getfielddefn(f, 1))
# now the field is set to the default
@test AG.getfield(f, 1) == AG.getdefault(f, 1)
@test AG.isfieldset(f, 1) # the field is now set
end
@test !AG.isfieldnull(f, 1) # still as expected
# # set & notnull: value
# AG.fillunsetwithdefault!(f, notnull = false)
# if !AG.isdefaultdriverspecific(AG.getfielddefn(f, 1))
# # now the field is set to the default
# @test AG.getfield(f, 1) == AG.getdefault(f, 1)
# @test AG.isfieldset(f, 1) # the field is now set
# end
# @test !AG.isfieldnull(f, 1) # still as expected

# set the field to be notnullable
AG.setnullable!(AG.getfielddefn(f, 1), false)
# now if we unset the field
AG.unsetfield!(f, 1)
@test !AG.isfieldnull(f, 1)
@test !AG.isfieldset(f, 1)
@test isnothing(AG.getfield(f, 1))
# and we fill unset with default again
AG.fillunsetwithdefault!(f)
if !AG.isdefaultdriverspecific(AG.getfielddefn(f, 1))
# the field is set to the default
@test AG.getfield(f, 1) == AG.getdefault(f, 1)
@test AG.isfieldset(f, 1)
end
@test !AG.isfieldnull(f, 1)
# # set the field to be notnullable
# AG.setnullable!(AG.getfielddefn(f, 1), false)
# # now if we unset the field
# AG.unsetfield!(f, 1)
# @test !AG.isfieldnull(f, 1)
# @test !AG.isfieldset(f, 1)
# @test isnothing(AG.getfield(f, 1))
# # and we fill unset with default again
# AG.fillunsetwithdefault!(f)
# if !AG.isdefaultdriverspecific(AG.getfielddefn(f, 1))
# # the field is set to the default
# @test AG.getfield(f, 1) == AG.getdefault(f, 1)
# @test AG.isfieldset(f, 1)
# end
# @test !AG.isfieldnull(f, 1)

# set & null: missing
AG.setfieldnull!(f, 1)
@test AG.isfieldnull(f, 1)
@test AG.isfieldset(f, 1)
@test ismissing(AG.getfield(f, 1))
# # set & null: missing
# AG.setfieldnull!(f, 1)
# @test AG.isfieldnull(f, 1)
# @test AG.isfieldset(f, 1)
# @test ismissing(AG.getfield(f, 1))

# unset & null: N/A (but nothing otherwise)
AG.unsetfield!(f, 1)
# Observe that OGRUnset and OGRNull are mutually exclusive
@test !AG.isfieldset(f, 1)
@test !AG.isfieldnull(f, 1) # notice the field is notnull
# # unset & null: N/A (but nothing otherwise)
# AG.unsetfield!(f, 1)
# # Observe that OGRUnset and OGRNull are mutually exclusive
# @test !AG.isfieldset(f, 1)
# @test !AG.isfieldnull(f, 1) # notice the field is notnull

# setting the field for a notnullable column
AG.setnullable!(AG.getfielddefn(f, 1), false)
AG.setfield!(f, 1, "value")
@test AG.getfield(f, 1) == "value"
@test AG.isfieldset(f, 1)
@test !AG.isfieldnull(f, 1)
AG.setfield!(f, 1, missing)
@test AG.getfield(f, 1) == AG.getdefault(f, 1)
@test AG.isfieldset(f, 1)
@test !AG.isfieldnull(f, 1)
AG.setfield!(f, 1, nothing)
@test isnothing(AG.getfield(f, 1))
@test !AG.isfieldset(f, 1)
@test !AG.isfieldnull(f, 1)
# # setting the field for a notnullable column
# AG.setnullable!(AG.getfielddefn(f, 1), false)
# AG.setfield!(f, 1, "value")
# @test AG.getfield(f, 1) == "value"
# @test AG.isfieldset(f, 1)
# @test !AG.isfieldnull(f, 1)
# AG.setfield!(f, 1, missing)
# @test AG.getfield(f, 1) == AG.getdefault(f, 1)
# @test AG.isfieldset(f, 1)
# @test !AG.isfieldnull(f, 1)
# AG.setfield!(f, 1, nothing)
# @test isnothing(AG.getfield(f, 1))
# @test !AG.isfieldset(f, 1)
# @test !AG.isfieldnull(f, 1)

# setting the field for a nullable column
AG.setnullable!(AG.getfielddefn(f, 1), true)
AG.setfield!(f, 1, "value")
@test AG.getfield(f, 1) == "value"
@test AG.isfieldset(f, 1)
@test !AG.isfieldnull(f, 1)
AG.setfield!(f, 1, missing)
@test ismissing(AG.getfield(f, 1))
@test AG.isfieldset(f, 1)
@test AG.isfieldnull(f, 1) # different from that of notnullable
AG.setfield!(f, 1, nothing)
@test isnothing(AG.getfield(f, 1))
@test !AG.isfieldset(f, 1)
@test !AG.isfieldnull(f, 1)
end
# # setting the field for a nullable column
# AG.setnullable!(AG.getfielddefn(f, 1), true)
# AG.setfield!(f, 1, "value")
# @test AG.getfield(f, 1) == "value"
# @test AG.isfieldset(f, 1)
# @test !AG.isfieldnull(f, 1)
# AG.setfield!(f, 1, missing)
# @test ismissing(AG.getfield(f, 1))
# @test AG.isfieldset(f, 1)
# @test AG.isfieldnull(f, 1) # different from that of notnullable
# AG.setfield!(f, 1, nothing)
# @test isnothing(AG.getfield(f, 1))
# @test !AG.isfieldset(f, 1)
# @test !AG.isfieldnull(f, 1)
# end
end
end

Expand Down
30 changes: 18 additions & 12 deletions test/test_gdalutilities_errors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import ArchGDAL as AG
using Test

@testset "test_gdalutilities_errors.jl" begin
AG.read("data/utmsmall.tif") do ds_small
@testset "GDAL Error" begin
@testset "GDAL Error" begin
AG.read("data/utmsmall.tif") do ds_small
@test_throws GDAL.GDALError AG.gdalinfo(
ds_small,
["-novalidoption"],
Expand All @@ -17,11 +17,14 @@ using Test
[ds_small],
["-novalidoption"],
)
@test_throws GDAL.GDALError AG.unsafe_gdaldem(
ds_small,
"hillshade",
["-novalidoption"],
)
# This throws
# signal 6: Abort trap: 6
# libc++abi: terminating due to uncaught exception of type std::invalid_argument
# @test_throws GDAL.GDALError AG.unsafe_gdaldem(
# ds_small,
# "hillshade",
# ["-novalidoption"],
# )
@test_throws GDAL.GDALError AG.unsafe_gdalnearblack(
ds_small,
["-novalidoption"],
Expand All @@ -44,11 +47,14 @@ using Test
[ds_small],
["-novalidoption"],
)
@test_throws GDAL.GDALError AG.unsafe_gdaldem(
ds_small,
"hillshade",
["-novalidoption"],
)
# This throws
# signal 6: Abort trap: 6
# libc++abi: terminating due to uncaught exception of type std::invalid_argument
# @test_throws GDAL.GDALError AG.unsafe_gdaldem(
# ds_small,
# "hillshade",
# ["-novalidoption"],
# )
@test_throws GDAL.GDALError AG.unsafe_gdalnearblack(
ds_small,
["-novalidoption"],
Expand Down
14 changes: 14 additions & 0 deletions test/test_geometry.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ using Test
import GeoInterface as GI
import ArchGDAL as AG
import GeoFormatTypes as GFT
import JLD2

@testset "test_geometry.jl" begin
@testset "GeoInterface" begin
Expand Down Expand Up @@ -1028,4 +1029,17 @@ import GeoFormatTypes as GFT
GI.coordinates(ag_geom) ==
[[1, 2], [1, 2]]
end

@testset "JLD2 serialization" begin
filepath = joinpath(tempdir(), "test_geometry.jld2")
geom = AG.fromWKT("MULTIPOLYGON (" *
"((0 4 8,4 4 8,4 0 8,0 0 8,0 4 8)," *
"(3 1 8,3 3 8,1 3 8,1 1 8,3 1 8))," *
"((10 4 8,14 4 8,14 0 8,10 0 8,10 4 8)," *
"(13 1 8,13 3 8,11 3 8,11 1 8,13 1 8)))")

JLD2.save_object(filepath, geom)
geom2 = JLD2.load_object(filepath)
@test AG.toWKT(geom2) == AG.toWKT(geom)
end
end

0 comments on commit 2fd7390

Please sign in to comment.