diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index c4ee141f..dcc1a2d9 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -9,7 +9,7 @@ jobs: - uses: actions/checkout@v2 - uses: julia-actions/setup-julia@latest with: - version: 1.6 + version: 1 - uses: julia-actions/julia-buildpkg@latest - name: Install dependencies run: julia -e 'using Pkg; pkg"add PkgBenchmark BenchmarkCI@0.1"' diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl index ca277ee3..7292d5b5 100644 --- a/benchmark/benchmarks.jl +++ b/benchmark/benchmarks.jl @@ -24,16 +24,20 @@ road_shapefile_file = joinpath(road_shapefile_dir, splitpath(road_shapefile_dir)[end] * ".shp") # Benchmarks -SUITE["shapefile_to_table"]["frenchroads_with_GDAL.jl_via_vsizip"] = - @benchmarkable Tables.columns( - AG.getlayer( - AG.read("/vsizip/" * relpath($road_shapefile_ziparchive)), # relpath is a workaround in case there are spaces in local fullpath (incompatible with /vsizip usage) when benchmarkpkg is run locally - 0, - ), +SUITE["shapefile_to_table"]["frenchroads_ArchGDAL_vsizip"] = + @benchmarkable Tables.columntable( + AG.getlayer(AG.read("/vsizip/" * relpath($road_shapefile_ziparchive))), # relpath is a workaround in case there are spaces in local fullpath (incompatible with /vsizip usage) when benchmarkpkg is run locally ) -SUITE["shapefile_to_table"]["frenchroads_with_GDAL.jl"] = - @benchmarkable Tables.columns(AG.getlayer(AG.read($road_shapefile_file), 0)) -SUITE["shapefile_to_table"]["frenchroads_with_Shapefile.jl"] = - @benchmarkable begin - Tables.columns(Shapefile.Table($road_shapefile_file)) - end +SUITE["shapefile_to_table"]["frenchroads_ArchGDAL"] = + @benchmarkable Tables.columntable( + AG.getlayer(AG.read($road_shapefile_file), 0), + ) +SUITE["shapefile_to_table"]["frenchroads_ArchGDAL_new_Tables_interface_vsizip"] = + @benchmarkable Tables.columntable( + AG.Table(AG.read("/vsizip/" * relpath($road_shapefile_ziparchive))), # relpath is a workaround in case there are spaces in local fullpath (incompatible with /vsizip usage) when benchmarkpkg is run locally + ) +SUITE["shapefile_to_table"]["frenchroads_ArchGDAL_new_Tables_interface"] = + @benchmarkable Tables.columntable(AG.Table($road_shapefile_file)) +SUITE["shapefile_to_table"]["frenchroads_Shapefile"] = @benchmarkable begin + Tables.columntable(Shapefile.Table($road_shapefile_file)) +end diff --git a/benchmark/remotefiles.jl b/benchmark/remotefiles.jl index a5af3477..d0e7d40b 100644 --- a/benchmark/remotefiles.jl +++ b/benchmark/remotefiles.jl @@ -16,12 +16,10 @@ julia> open(filepath/filename) do f end ``` """ -remotefiles = [ - ( - "data/road.zip", - "058bdc549d0fc5bfb6deaef138e48758ca79ae20df79c2fb4c40cb878f48bfd8", - ), -] +remotefiles = [( + "data/road.zip", + "058bdc549d0fc5bfb6deaef138e48758ca79ae20df79c2fb4c40cb878f48bfd8", +)] function verify(path::AbstractString, hash::AbstractString) @assert occursin(r"^[0-9a-f]{64}$", hash) diff --git a/src/ArchGDAL.jl b/src/ArchGDAL.jl index 955f0b54..622cbfda 100644 --- a/src/ArchGDAL.jl +++ b/src/ArchGDAL.jl @@ -35,6 +35,7 @@ include("context.jl") include("base/iterators.jl") include("base/display.jl") include("tables.jl") +include("tables2.jl") include("geointerface.jl") include("convert.jl") diff --git a/src/base/display.jl b/src/base/display.jl index 3f7cbe11..3dcf81f5 100644 --- a/src/base/display.jl +++ b/src/base/display.jl @@ -107,7 +107,7 @@ function Base.show( end # assumes that the layer is reset, and will reset it after display -function Base.show(io::IO, layer::AbstractFeatureLayer)::Nothing +function Base.show(io::IO, layer::DUAL_AbstractFeatureLayer) if layer.ptr == C_NULL print(io, "NULL FeatureLayer") return nothing @@ -172,7 +172,7 @@ function Base.show(io::IO, layer::AbstractFeatureLayer)::Nothing return nothing end -function Base.show(io::IO, featuredefn::AbstractFeatureDefn)::Nothing +function Base.show(io::IO, featuredefn::DUAL_AbstractFeatureDefn)::Nothing if featuredefn.ptr == C_NULL print(io, "NULL FeatureDefn") return nothing @@ -195,7 +195,7 @@ function Base.show(io::IO, featuredefn::AbstractFeatureDefn)::Nothing return nothing end -function Base.show(io::IO, fd::AbstractFieldDefn)::Nothing +function Base.show(io::IO, fd::DUAL_AbstractFieldDefn)::Nothing if fd.ptr == C_NULL print(io, "NULL FieldDefn") return nothing @@ -204,7 +204,7 @@ function Base.show(io::IO, fd::AbstractFieldDefn)::Nothing return nothing end -function Base.show(io::IO, gfd::AbstractGeomFieldDefn)::Nothing +function Base.show(io::IO, gfd::DUAL_AbstractGeomFieldDefn)::Nothing if gfd.ptr == C_NULL print(io, "NULL GeomFieldDefn") return nothing @@ -213,7 +213,7 @@ function Base.show(io::IO, gfd::AbstractGeomFieldDefn)::Nothing return nothing end -function Base.show(io::IO, feature::Feature)::Nothing +function Base.show(io::IO, feature::DUAL_AbstractFeature)::Nothing if feature.ptr == C_NULL print(io, "NULL Feature") return nothing diff --git a/src/base/iterators.jl b/src/base/iterators.jl index 5beb30c0..4d8d9fb5 100644 --- a/src/base/iterators.jl +++ b/src/base/iterators.jl @@ -15,9 +15,9 @@ end Base.eltype(layer::AbstractFeatureLayer)::DataType = Feature -Base.IteratorSize(::Type{<:AbstractFeatureLayer}) = Base.SizeUnknown() +Base.IteratorSize(::Type{<:DUAL_AbstractFeatureLayer}) = Base.SizeUnknown() -Base.length(layer::AbstractFeatureLayer)::Integer = nfeature(layer, true) +Base.length(layer::DUAL_AbstractFeatureLayer)::Integer = nfeature(layer, true) struct BlockIterator{T<:Integer} rows::T diff --git a/src/constants.jl b/src/constants.jl index 73eb41b6..feb3a0b8 100644 --- a/src/constants.jl +++ b/src/constants.jl @@ -298,80 +298,88 @@ convert(GDAL.OGRwkbGeometryType, ArchGDAL.wkbUnknown) wkbUnknown::OGRwkbGeometryType = 0x00000000 ``` """ -@enum( - OGRwkbGeometryType, - wkbUnknown = 0, - wkbPoint = 1, - wkbLineString = 2, - wkbPolygon = 3, - wkbMultiPoint = 4, - wkbMultiLineString = 5, - wkbMultiPolygon = 6, - wkbGeometryCollection = 7, - wkbCircularString = 8, - wkbCompoundCurve = 9, - wkbCurvePolygon = 10, - wkbMultiCurve = 11, - wkbMultiSurface = 12, - wkbCurve = 13, - wkbSurface = 14, - wkbPolyhedralSurface = 15, - wkbTIN = 16, - wkbTriangle = 17, - wkbNone = 18, - wkbLinearRing = 19, - wkbCircularStringZ = 20, - wkbCompoundCurveZ = 21, - wkbCurvePolygonZ = 22, - wkbMultiCurveZ = 23, - wkbMultiSurfaceZ = 24, - wkbCurveZ = 25, - wkbSurfaceZ = 26, - wkbPolyhedralSurfaceZ = 27, - wkbTINZ = 28, - wkbTriangleZ = 29, - wkbPointM = 30, - wkbLineStringM = 31, - wkbPolygonM = 32, - wkbMultiPointM = 33, - wkbMultiLineStringM = 34, - wkbMultiPolygonM = 35, - wkbGeometryCollectionM = 36, - wkbCircularStringM = 37, - wkbCompoundCurveM = 38, - wkbCurvePolygonM = 39, - wkbMultiCurveM = 40, - wkbMultiSurfaceM = 41, - wkbCurveM = 42, - wkbSurfaceM = 43, - wkbPolyhedralSurfaceM = 44, - wkbTINM = 45, - wkbTriangleM = 46, - wkbPointZM = 47, - wkbLineStringZM = 48, - wkbPolygonZM = 49, - wkbMultiPointZM = 50, - wkbMultiLineStringZM = 51, - wkbMultiPolygonZM = 52, - wkbGeometryCollectionZM = 53, - wkbCircularStringZM = 54, - wkbCompoundCurveZM = 55, - wkbCurvePolygonZM = 56, - wkbMultiCurveZM = 57, - wkbMultiSurfaceZM = 58, - wkbCurveZM = 59, - wkbSurfaceZM = 60, - wkbPolyhedralSurfaceZM = 61, - wkbTINZM = 62, - wkbTriangleZM = 63, - wkbPoint25D = 64, - wkbLineString25D = 65, - wkbPolygon25D = 66, - wkbMultiPoint25D = 67, - wkbMultiLineString25D = 68, - wkbMultiPolygon25D = 69, - wkbGeometryCollection25D = 70, -) +@enum OGRwkbGeometryType::UInt32 begin + wkbUnknown = 0x00000000 + wkbPoint = 0x00000001 + wkbLineString = 0x00000002 + wkbPolygon = 0x00000003 + wkbMultiPoint = 0x00000004 + wkbMultiLineString = 0x00000005 + wkbMultiPolygon = 0x00000006 + wkbGeometryCollection = 0x00000007 + wkbCircularString = 0x00000008 + wkbCompoundCurve = 0x00000009 + wkbCurvePolygon = 0x0000000a + wkbMultiCurve = 0x0000000b + wkbMultiSurface = 0x0000000c + wkbCurve = 0x0000000d + wkbSurface = 0x0000000e + wkbPolyhedralSurface = 0x0000000f + wkbTIN = 0x00000010 + wkbTriangle = 0x00000011 + wkbNone = 0x00000064 + wkbLinearRing = 0x00000065 + wkbCircularStringZ = 0x000003f0 + wkbCompoundCurveZ = 0x000003f1 + wkbCurvePolygonZ = 0x000003f2 + wkbMultiCurveZ = 0x000003f3 + wkbMultiSurfaceZ = 0x000003f4 + wkbCurveZ = 0x000003f5 + wkbSurfaceZ = 0x000003f6 + wkbPolyhedralSurfaceZ = 0x000003f7 + wkbTINZ = 0x000003f8 + wkbTriangleZ = 0x000003f9 + wkbPointM = 0x000007d1 + wkbLineStringM = 0x000007d2 + wkbPolygonM = 0x000007d3 + wkbMultiPointM = 0x000007d4 + wkbMultiLineStringM = 0x000007d5 + wkbMultiPolygonM = 0x000007d6 + wkbGeometryCollectionM = 0x000007d7 + wkbCircularStringM = 0x000007d8 + wkbCompoundCurveM = 0x000007d9 + wkbCurvePolygonM = 0x000007da + wkbMultiCurveM = 0x000007db + wkbMultiSurfaceM = 0x000007dc + wkbCurveM = 0x000007dd + wkbSurfaceM = 0x000007de + wkbPolyhedralSurfaceM = 0x000007df + wkbTINM = 0x000007e0 + wkbTriangleM = 0x000007e1 + wkbPointZM = 0x00000bb9 + wkbLineStringZM = 0x00000bba + wkbPolygonZM = 0x00000bbb + wkbMultiPointZM = 0x00000bbc + wkbMultiLineStringZM = 0x00000bbd + wkbMultiPolygonZM = 0x00000bbe + wkbGeometryCollectionZM = 0x00000bbf + wkbCircularStringZM = 0x00000bc0 + wkbCompoundCurveZM = 0x00000bc1 + wkbCurvePolygonZM = 0x00000bc2 + wkbMultiCurveZM = 0x00000bc3 + wkbMultiSurfaceZM = 0x00000bc4 + wkbCurveZM = 0x00000bc5 + wkbSurfaceZM = 0x00000bc6 + wkbPolyhedralSurfaceZM = 0x00000bc7 + wkbTINZM = 0x00000bc8 + wkbTriangleZM = 0x00000bc9 + wkbPoint25D = 0x80000001 + wkbLineString25D = 0x80000002 + wkbPolygon25D = 0x80000003 + wkbMultiPoint25D = 0x80000004 + wkbMultiLineString25D = 0x80000005 + wkbMultiPolygon25D = 0x80000006 + wkbGeometryCollection25D = 0x80000007 +end +@assert begin + all( + string.(instances(OGRwkbGeometryType)) .== + string.(instances(GDAL.OGRwkbGeometryType)), + ) && all( + Integer.(instances(OGRwkbGeometryType)) .== + Integer.(instances(GDAL.OGRwkbGeometryType)), + ) +end """ The value of `OGRwkbByteOrder` could be different from `GDAL.OGRwkbByteOrder`. diff --git a/src/dataset.jl b/src/dataset.jl index 2c935f20..57a8b32d 100644 --- a/src/dataset.jl +++ b/src/dataset.jl @@ -524,13 +524,10 @@ Fetch the first layer and raise an error if `dataset` contains more than one lay The returned layer remains owned by the `dataset` and should not be deleted by the application. """ -function getlayer(dataset::AbstractDataset)::IFeatureLayer +function getlayer(dataset::AbstractDataset) nlayer(dataset) == 1 || error("Dataset has multiple layers. Specify the layer number or name") - return IFeatureLayer( - GDAL.gdaldatasetgetlayer(dataset.ptr, 0), - ownedby = dataset, - ) + return getlayer(dataset, 0) end unsafe_getlayer(dataset::AbstractDataset, i::Integer)::FeatureLayer = @@ -538,7 +535,7 @@ unsafe_getlayer(dataset::AbstractDataset, i::Integer)::FeatureLayer = function unsafe_getlayer(dataset::AbstractDataset)::FeatureLayer nlayer(dataset) == 1 || error("Dataset has multiple layers. Specify the layer number or name") - return FeatureLayer(GDAL.gdaldatasetgetlayer(dataset.ptr, 0)) + return unsafe_getlayer(dataset, 0) end """ diff --git a/src/ogr/feature.jl b/src/ogr/feature.jl index 42d5c9ab..569b98dd 100644 --- a/src/ogr/feature.jl +++ b/src/ogr/feature.jl @@ -53,7 +53,7 @@ end Returns a clone of the geometry corresponding to the feature. """ -function getgeom(feature::Feature)::IGeometry +function getgeom(feature::DUAL_AbstractFeature)::IGeometry result = GDAL.ogr_f_getgeometryref(feature.ptr) return if result == C_NULL IGeometry() @@ -112,7 +112,7 @@ the field index, or `nothing` if no matching field is found. This is a cover for the `OGRFeatureDefn::GetFieldIndex()` method. """ function findfieldindex( - feature::Feature, + feature::AbstractFeature, name::Union{AbstractString,Symbol}, )::Union{Integer,Nothing} i = GDAL.ogr_f_getfieldindex(feature.ptr, name) @@ -132,7 +132,7 @@ Test if a field has ever been assigned a value or not. * `feature`: the feature that owned the field. * `i`: the field to fetch, from 0 to GetFieldCount()-1. """ -isfieldset(feature::Feature, i::Integer)::Bool = +isfieldset(feature::DUAL_AbstractFeature, i::Integer)::Bool = Bool(GDAL.ogr_f_isfieldset(feature.ptr, i)) """ @@ -164,7 +164,7 @@ Test if a field is null. ### References * https://gdal.org/development/rfc/rfc67_nullfieldvalues.html """ -isfieldnull(feature::Feature, i::Integer)::Bool = +isfieldnull(feature::DUAL_AbstractFeature, i::Integer)::Bool = Bool(GDAL.ogr_f_isfieldnull(feature.ptr, i)) """ @@ -227,7 +227,7 @@ Fetch field value as integer. * `feature`: the feature that owned the field. * `i`: the field to fetch, from 0 to GetFieldCount()-1. """ -asint(feature::Feature, i::Integer)::Int32 = +asint(feature::DUAL_AbstractFeature, i::Integer)::Int32 = GDAL.ogr_f_getfieldasinteger(feature.ptr, i) """ @@ -239,7 +239,7 @@ Fetch field value as integer 64 bit. * `feature`: the feature that owned the field. * `i`: the field to fetch, from 0 to GetFieldCount()-1. """ -asint64(feature::Feature, i::Integer)::Int64 = +asint64(feature::DUAL_AbstractFeature, i::Integer)::Int64 = GDAL.ogr_f_getfieldasinteger64(feature.ptr, i) """ @@ -251,7 +251,7 @@ Fetch field value as a double. * `feature`: the feature that owned the field. * `i`: the field to fetch, from 0 to GetFieldCount()-1. """ -asdouble(feature::Feature, i::Integer)::Float64 = +asdouble(feature::DUAL_AbstractFeature, i::Integer)::Float64 = GDAL.ogr_f_getfieldasdouble(feature.ptr, i) """ @@ -263,7 +263,7 @@ Fetch field value as a string. * `feature`: the feature that owned the field. * `i`: the field to fetch, from 0 to GetFieldCount()-1. """ -asstring(feature::Feature, i::Integer)::String = +asstring(feature::DUAL_AbstractFeature, i::Integer)::String = GDAL.ogr_f_getfieldasstring(feature.ptr, i) """ @@ -281,7 +281,7 @@ the field value. This list is internal, and should not be modified, or freed. Its lifetime may be very brief. If *pnCount is zero on return the returned pointer may be NULL or non-NULL. """ -function asintlist(feature::Feature, i::Integer)::Vector{Int32} +function asintlist(feature::DUAL_AbstractFeature, i::Integer)::Vector{Int32} n = Ref{Cint}() ptr = GDAL.ogr_f_getfieldasintegerlist(feature.ptr, i, n) return (n.x == 0) ? Int32[] : unsafe_wrap(Vector{Int32}, ptr, n.x) @@ -302,7 +302,7 @@ the field value. This list is internal, and should not be modified, or freed. Its lifetime may be very brief. If *pnCount is zero on return the returned pointer may be NULL or non-NULL. """ -function asint64list(feature::Feature, i::Integer)::Vector{Int64} +function asint64list(feature::DUAL_AbstractFeature, i::Integer)::Vector{Int64} n = Ref{Cint}() ptr = GDAL.ogr_f_getfieldasinteger64list(feature.ptr, i, n) return (n.x == 0) ? Int64[] : unsafe_wrap(Vector{Int64}, ptr, n.x) @@ -323,7 +323,10 @@ the field value. This list is internal, and should not be modified, or freed. Its lifetime may be very brief. If *pnCount is zero on return the returned pointer may be NULL or non-NULL. """ -function asdoublelist(feature::Feature, i::Integer)::Vector{Float64} +function asdoublelist( + feature::DUAL_AbstractFeature, + i::Integer, +)::Vector{Float64} n = Ref{Cint}() ptr = GDAL.ogr_f_getfieldasdoublelist(feature.ptr, i, n) return (n.x == 0) ? Float64[] : unsafe_wrap(Vector{Float64}, ptr, n.x) @@ -342,7 +345,7 @@ Fetch field value as a list of strings. the field value. This list is internal, and should not be modified, or freed. Its lifetime may be very brief. """ -asstringlist(feature::Feature, i::Integer)::Vector{String} = +asstringlist(feature::DUAL_AbstractFeature, i::Integer)::Vector{String} = GDAL.ogr_f_getfieldasstringlist(feature.ptr, i) """ @@ -358,7 +361,7 @@ Fetch field value as binary. the field value. This list is internal, and should not be modified, or freed. Its lifetime may be very brief. """ -function asbinary(feature::Feature, i::Integer)::Vector{UInt8} +function asbinary(feature::DUAL_AbstractFeature, i::Integer)::Vector{UInt8} n = Ref{Cint}() ptr = GDAL.ogr_f_getfieldasbinary(feature.ptr, i, n) return (n.x == 0) ? UInt8[] : unsafe_wrap(Vector{UInt8}, ptr, n.x) @@ -377,7 +380,7 @@ OFTDate, OFTTime and OFTDateTime fields. ### Returns `true` on success or `false` on failure. """ -function asdatetime(feature::Feature, i::Integer)::DateTime +function asdatetime(feature::DUAL_AbstractFeature, i::Integer)::DateTime pyr = Ref{Cint}() pmth = Ref{Cint}() pday = Ref{Cint}() @@ -438,7 +441,7 @@ function getdefault(feature::Feature, i::Integer)::Union{String,Nothing} return getdefault(getfielddefn(feature, i)) end -getfield(feature::Feature, i::Nothing)::Missing = missing +getfield(feature::DUAL_AbstractFeature, i::Nothing)::Missing = missing const _FETCHFIELD = Dict{OGRFieldType,Function}( OFTInteger => asint, @@ -673,7 +676,7 @@ the geometry field index, or -1 if no matching geometry field is found. This is a cover for the `OGRFeatureDefn::GetGeomFieldIndex()` method. """ function findgeomindex( - feature::Feature, + feature::AbstractFeature, name::Union{AbstractString,Symbol} = "", )::Integer return GDAL.ogr_f_getgeomfieldindex(feature.ptr, name) @@ -688,7 +691,7 @@ Returns a clone of the feature geometry at index `i`. * `feature`: the feature to get geometry from. * `i`: geometry field to get. """ -function getgeom(feature::Feature, i::Integer)::IGeometry +function getgeom(feature::DUAL_AbstractFeature, i::Integer)::IGeometry result = GDAL.ogr_f_getgeomfieldref(feature.ptr, i) return if result == C_NULL IGeometry() @@ -707,7 +710,7 @@ function unsafe_getgeom(feature::Feature, i::Integer)::Geometry end function getgeom( - feature::Feature, + feature::DUAL_AbstractFeature, name::Union{AbstractString,Symbol}, )::IGeometry i = findgeomindex(feature, name) diff --git a/src/ogr/featuredefn.jl b/src/ogr/featuredefn.jl index bc7e40ec..444a0f3f 100644 --- a/src/ogr/featuredefn.jl +++ b/src/ogr/featuredefn.jl @@ -271,7 +271,7 @@ Fetch geometry field definition of the passed feature definition. an internal field definition object or `NULL` if invalid index. This object should not be modified or freed by the application. """ -getgeomdefn(featuredefn::FeatureDefn, i::Integer = 0)::GeomFieldDefn = +getgeomdefn(featuredefn::AbstractFeatureDefn, i::Integer = 0)::GeomFieldDefn = GeomFieldDefn(GDAL.ogr_fd_getgeomfielddefn(featuredefn.ptr, i)) getgeomdefn(featuredefn::IFeatureDefnView, i::Integer = 0)::IGeomFieldDefnView = diff --git a/src/ogr/featurelayer.jl b/src/ogr/featurelayer.jl index d1c1e19b..92de8258 100644 --- a/src/ogr/featurelayer.jl +++ b/src/ogr/featurelayer.jl @@ -1,4 +1,3 @@ - function destroy(layer::AbstractFeatureLayer)::Nothing layer.ptr = C_NULL return nothing @@ -110,14 +109,15 @@ end Return the layer name. """ -getname(layer::AbstractFeatureLayer)::String = GDAL.ogr_l_getname(layer.ptr) +getname(layer::DUAL_AbstractFeatureLayer)::String = + GDAL.ogr_l_getname(layer.ptr) """ getgeomtype(layer::AbstractFeatureLayer) Return the layer geometry type. """ -getgeomtype(layer::AbstractFeatureLayer)::OGRwkbGeometryType = +getgeomtype(layer::DUAL_AbstractFeatureLayer)::OGRwkbGeometryType = GDAL.ogr_l_getgeomtype(layer.ptr) """ @@ -355,7 +355,7 @@ Reset feature reading to start on the first feature. This affects `nextfeature()`. """ -function resetreading!(layer::L)::L where {L<:AbstractFeatureLayer} +function resetreading!(layer::L)::L where {L<:DUAL_AbstractFeatureLayer} GDAL.ogr_l_resetreading(layer.ptr) return layer end @@ -585,7 +585,7 @@ Fetch the feature count in this layer, or `-1` if the count is not known. * `force`: flag indicating whether the count should be computed even if it is expensive. (`false` by default.) """ -nfeature(layer::AbstractFeatureLayer, force::Bool = false)::Integer = +nfeature(layer::DUAL_AbstractFeatureLayer, force::Bool = false)::Integer = GDAL.ogr_l_getfeaturecount(layer.ptr, force) """ diff --git a/src/ogr/fielddefn.jl b/src/ogr/fielddefn.jl index 1ec4feab..0d0690bd 100644 --- a/src/ogr/fielddefn.jl +++ b/src/ogr/fielddefn.jl @@ -27,7 +27,7 @@ function setname!(fielddefn::FieldDefn, name::AbstractString)::FieldDefn end "Fetch the name of this field." -getname(fielddefn::AbstractFieldDefn)::String = +getname(fielddefn::DUAL_AbstractFieldDefn)::String = GDAL.ogr_fld_getnameref(fielddefn.ptr) "Fetch the type of this field." @@ -349,11 +349,11 @@ function setname!(geomdefn::GeomFieldDefn, name::AbstractString)::GeomFieldDefn end "Fetch name of this field." -getname(geomdefn::AbstractGeomFieldDefn)::String = +getname(geomdefn::DUAL_AbstractGeomFieldDefn)::String = GDAL.ogr_gfld_getnameref(geomdefn.ptr) "Fetch geometry type of this field." -gettype(geomdefn::AbstractGeomFieldDefn)::OGRwkbGeometryType = +gettype(geomdefn::DUAL_AbstractGeomFieldDefn)::OGRwkbGeometryType = GDAL.ogr_gfld_gettype(geomdefn.ptr) "Set the geometry type of this field." diff --git a/src/tables.jl b/src/tables.jl index 7d42f0fa..a56e67ff 100644 --- a/src/tables.jl +++ b/src/tables.jl @@ -1,7 +1,21 @@ -function Tables.schema(layer::AbstractFeatureLayer)::Nothing +function Tables.schema(::AbstractFeatureLayer)::Nothing return nothing end +function gdal_schema(layer::AbstractFeatureLayer) + geom_names, field_names, featuredefn, fielddefns = + schema_names(layerdefn(layer)) + ngeom = ArchGDAL.ngeom(featuredefn) + geom_types = + (IGeometry{gettype(getgeomdefn(featuredefn, i))} for i in 0:ngeom-1) + field_types = + (convert(DataType, gettype(fielddefn)) for fielddefn in fielddefns) + return Tables.Schema( + (geom_names..., field_names...), + (geom_types..., field_types...), + ) +end + Tables.istable(::Type{<:AbstractFeatureLayer})::Bool = true Tables.rowaccess(::Type{<:AbstractFeatureLayer})::Bool = true @@ -9,13 +23,13 @@ function Tables.rows(layer::T)::T where {T<:AbstractFeatureLayer} return layer end -function Tables.getcolumn(row::Feature, i::Int) - if i > nfield(row) - return getgeom(row, i - nfield(row) - 1) - elseif i > 0 - return getfield(row, i - 1) +function Tables.getcolumn(row::AbstractFeature, i::Int) + ng = ngeom(row) + return if i <= ng + geom = getgeom(row, i - 1) + geom.ptr != C_NULL ? geom : missing else - return missing + getfield(row, i - ng - 1) end end @@ -32,7 +46,7 @@ function Tables.getcolumn(row::Feature, name::Symbol) end function Tables.columnnames( - row::Feature, + row::AbstractFeature, )::NTuple{Int64(nfield(row) + ngeom(row)),Symbol} geom_names, field_names = schema_names(getfeaturedefn(row)) return (geom_names..., field_names...) @@ -47,3 +61,58 @@ function schema_names(featuredefn::IFeatureDefnView) ) return (geom_names, field_names, featuredefn, fielddefns) end + +############################################################# +# Tables.columns on AbstractFeatture layer for normal layer # +############################################################# + +function f2c(feature::AbstractFeature, i::Int, cols::Vector{Vector{T} where T}) + ng = ngeom(feature) + nf = nfield(feature) + @inbounds for j in 1:(nf+ng) + cols[j][i] = Tables.getcolumn(feature, j) + end + return nothing +end + +function fillcolumns!( + layer::AbstractFeatureLayer, + cols::Vector{Vector{T} where T}, +) + state = 0 + while true + next = iterate(layer, state) + next === nothing && break + feature, state = next + f2c(feature, state, cols) + end +end + +function Tables.columns(layer::AbstractFeatureLayer) + len = length(layer) + gdal_sch = gdal_schema(layer) + ng = ngeom(layer) + cols = [ + [Vector{Union{Missing,IGeometry}}(missing, len) for _ in 1:ng] + [ + Vector{Union{Missing,Nothing,T}}(missing, len) for + T in gdal_sch.types[ng+1:end] + ] + ] + fillcolumns!(layer, cols) + return if VERSION < v"1.7" + NamedTuple{gdal_sch.names}( + NTuple{length(gdal_sch.names),Vector{T} where T}([ + convert( + Vector{promote_type(unique(typeof(e) for e in c)...)}, + c, + ) for c in cols + ]), + ) + else + NamedTuple{gdal_sch.names}( + convert(Vector{promote_type(unique(typeof(e) for e in c)...)}, c) + for c in cols + ) + end +end diff --git a/src/tables2.jl b/src/tables2.jl new file mode 100644 index 00000000..390cce3d --- /dev/null +++ b/src/tables2.jl @@ -0,0 +1,921 @@ +#*############################################################################# +#* Parametric ArchGDAL vector data types for ArchGDAL.Table # +#*############################################################################# +# 1. Definition and associated methods of FType, GType and FDType. +# Used as parameters for parametric ArchGDAL vector data types +# 2. Types hierarchy of parametric ArchGDAL vector data types +# 3. Parametric ArchGDAL vector data types definitions +# In parenthesis: objects with commented definition or to define +# - GFTP_GeomFieldDefn and GFTP_IGeomFieldDefnView +# - FTP_FieldDefn and FTP_IFieldDefnView +# - FDP_FeatureDefn and FDP_IFeatureDefnView +# - FDP_Feature (and FDP_IFeatureView) +# - (GP_Geometry and GP_IGeometry) +# - (FDP_FeatureLayer and) FDP_IFeatureLayer +# 4. Conversion function for parametric ArchGDAL vector data types +# 5. Subset of ArchGDAL vector functions adpated and optimized for +# parametric ArchGDAL vector data types +# a. Methods for GFTP_AbstractGeomFieldDefn => none found useful yet +# b. Methofs for FTP_AbstractFieldDefn +# c. Methods for FDP_AbstractFeatureDefn +# d. Methods for FDP_AbstractFeature +# e. Methods for FDP_AbstractFeatureLayer +############################################################################### + +############################################################################### +# 1. Definition of FType, GType and FDType definition # +############################################################################### + +#! AbstractOFType could also be a non parameterized abstract type with +#! OFType{OGRFieldType, OGRFieldSubType} instead of +#! OFType{T,OGRFieldSubType} <: AbstractOFType{T} +abstract type AbstractFType{OGRFieldType} end +struct FType{T,OGRFieldSubType} <: AbstractFType{T} end +function getFType(ptr::GDAL.OGRFieldDefnH) + return FType{ + convert(OGRFieldType, GDAL.ogr_fld_gettype(ptr)), + convert(OGRFieldSubType, GDAL.ogr_fld_getsubtype(ptr)), + } +end +abstract type AbstractGType end +struct GType{OGRwkbGeometryType} <: AbstractGType end +function getGType(ptr::GDAL.OGRGeomFieldDefnH) + return GType{convert(OGRwkbGeometryType, GDAL.ogr_gfld_gettype(ptr))} +end + +#! NEW simple FeatureDefn type, could later maybe(?) replaced by full +#! FeatureDefn type in the definitions below +#TODO delete: FDType = Tuple{NTuple{NG,GType} where NG,NTuple{NF,FType} where NF} #! Type alias for FD parameter +FDType = Tuple{ + NamedTuple{NG,<:Tuple{Vararg{GType}}} where NG, + NamedTuple{NF,<:Tuple{Vararg{FType}}} where NF, +} +@generated function _ngt(::Type{T}) where {T<:FDType} + return :(length($T.types[1].types)) +end +@generated function _gtnames(::Type{T}) where {T<:FDType} + return :(tuple($T.types[1].parameters[1]...)) +end +@generated function _gttypes(::Type{T}) where {T<:FDType} + return :(tuple($T.types[1].types...)) +end +@generated function _nft(::Type{T}) where {T<:FDType} + return :(length($T.types[2].types)) +end +@generated function _ftnames(::Type{T}) where {T<:FDType} + return :(tuple($T.types[2].parameters[1]...)) +end +@generated function _fttypes(::Type{T}) where {T<:FDType} + return :(tuple($T.types[2].types...)) +end +#! There no type difference between GDAL.OGRFeatureDefnH and GDAL.OGRLayerH +#! (both Ptr{Cvoid})) and we cannot dispatch on it +function _getFDType(ptr::GDAL.OGRFeatureDefnH) + ng = GDAL.ogr_fd_getgeomfieldcount(ptr)::Int32 + gflddefn_ptrs = (GDAL.ogr_fd_getgeomfielddefn(ptr, i - 1) for i in 1:ng) + NG = tuple( + ( + Symbol(GDAL.ogr_gfld_getnameref(gflddefn_ptr)::String) for + gflddefn_ptr in gflddefn_ptrs + )..., + ) + TG = Tuple{(getGType(gflddefn_ptr) for gflddefn_ptr in gflddefn_ptrs)...} + nf = GDAL.ogr_fd_getfieldcount(ptr)::Int32 + flddefn_ptrs = (GDAL.ogr_fd_getfielddefn(ptr, i - 1) for i in 1:nf) + NF = tuple( + ( + Symbol(GDAL.ogr_fld_getnameref(flddefn_ptr)::String) for + flddefn_ptr in flddefn_ptrs + )..., + ) + TF = Tuple{(getFType(flddefn_ptr) for flddefn_ptr in flddefn_ptrs)...} + # TF = Tuple{ntuple(i -> getFType(flddefn_ptrs[i]), nf)...} => to use in case later conversion from FType to DataType has to be implemented + return Tuple{NamedTuple{NG,TG},NamedTuple{NF,TF}} +end + +############################################################################### +# 2. Types hierarchy of parametric ArchGDAL vector data types # +############################################################################### + +abstract type GFTP_AbstractGeomFieldDefn{GFT<:GType} <: + DUAL_AbstractGeomFieldDefn end +abstract type FTP_AbstractFieldDefn{FT<:FType} <: DUAL_AbstractFieldDefn end +abstract type FDP_AbstractFeatureDefn{FD<:FDType} <: DUAL_AbstractFeatureDefn end +abstract type FDP_AbstractFeature{FD<:FDType} <: DUAL_AbstractFeature end +abstract type FDP_AbstractFeatureLayer{FD<:FDType} <: DUAL_AbstractFeatureLayer end + +############################################################################### +# 3. Definition of parametric ArchGDAL vector data types # +############################################################################### + +#! NEW GFTP_GeomFieldDefn and GFTP_IGeomFieldDefnView +#! Unsafe version disabled as there is no usage for Table struct +# mutable struct GFTP_GeomFieldDefn{GFT} <: GFTP_AbstractGeomFieldDefn{GFT} +# ptr::GDAL.OGRGeomFieldDefnH +# ownedby::Union{Nothing,FDP_AbstractFeatureDefn} +# spatialref::Union{Nothing,AbstractSpatialRef} + +# function GFTP_GeomFieldDefn{GFT}( +# ptr::GDAL.OGRGeomFieldDefnH = C_NULL; +# ownedby::Union{Nothing,FDP_AbstractFeatureDefn} = nothing, +# spatialref::Union{Nothing,AbstractSpatialRef} = nothing, +# ) where {GFT<:GType} +# return new(ptr, ownedby, spatialref) +# end +# end + +# function destroy(gftp_geomfielddefn::GFTP_GeomFieldDefn) +# GDAL.ogr_gfld_destroy(gftp_geomfielddefn) +# gftp_geomfielddefn.ptr = C_NULL +# gftp_geomfielddefn.ownedby = nothing +# gftp_geomfielddefn.spatialref = nothing +# return nothing +# end + +mutable struct GFTP_IGeomFieldDefnView{GFT} <: GFTP_AbstractGeomFieldDefn{GFT} + ptr::GDAL.OGRGeomFieldDefnH + ownedby::Union{Nothing,FDP_AbstractFeatureDefn} + spatialref::Union{Nothing,AbstractSpatialRef} + + function GFTP_IGeomFieldDefnView{GFT}( + ptr::GDAL.OGRGeomFieldDefnH = C_NULL; + ownedby::Union{Nothing,FDP_AbstractFeatureDefn} = nothing, + spatialref::Union{Nothing,AbstractSpatialRef} = nothing, + ) where {GFT<:GType} + gftp_igeomfielddefnview = new(ptr, ownedby, spatialref) + finalizer(destroy, gftp_igeomfielddefnview) + return gftp_igeomfielddefnview + end +end + +function destroy(gftp_igeomfielddefnview::GFTP_IGeomFieldDefnView) + gftp_igeomfielddefnview.ptr = C_NULL + gftp_igeomfielddefnview.ownedby = nothing + gftp_igeomfielddefnview.spatialref = nothing + return nothing +end + +#! NEW FTP_FieldDefn and FTP_IFieldDefnView +#! Unsafe version disabled as there is no usage for Table struct +# mutable struct FTP_FieldDefn{FT} <: FTP_AbstractFieldDefn{FT} +# ptr::GDAL.OGRFieldDefnH +# ownedby::Union{Nothing,FDP_AbstractFeatureDefn} + +# function FTP_FieldDefn{FT}( +# ptr::GDAL.OGRFieldDefnH = C_NULL; +# ownedby::Union{Nothing,FDP_AbstractFeatureDefn} = nothing, +# ) where {FT<:FType} +# return new(ptr, ownedby) +# end +# end + +# function destroy(ftp_fielddefn::FTP_FieldDefn) +# GDAL.ogr_fld_destroy(ftp_fielddefn) +# ftp_fielddefn.ptr = C_NULL +# ftp_fielddefn.ownedby = nothing +# return nothing +# end + +mutable struct FTP_IFieldDefnView{FT} <: FTP_AbstractFieldDefn{FT} + ptr::GDAL.OGRFieldDefnH + ownedby::Union{Nothing,FDP_AbstractFeatureDefn} + + function FTP_IFieldDefnView{FT}( + ptr::GDAL.OGRFieldDefnH = C_NULL; + ownedby::Union{Nothing,FDP_AbstractFeatureDefn} = nothing, + ) where {FT<:FType} + ftp_ifielddefnview = new(ptr, ownedby) + finalizer(destroy, ftp_ifielddefnview) + return ftp_ifielddefnview + end +end + +function destroy(ftp_fielddefn::FTP_IFieldDefnView) + ftp_fielddefn.ptr = C_NULL + ftp_fielddefn.ownedby = nothing + return nothing +end + +#! NEW FeatureDefn parameterized FeatureDefn and IFeatureDefnView +#! Unsafe version disabled as there is no usage for Table struct +# mutable struct FDP_FeatureDefn{FD} <: FDP_AbstractFeatureDefn{FD} +# ptr::GDAL.OGRFeatureDefnH +# ownedby::Union{Nothing,FDP_AbstractFeatureLayer{FD}} + +# function FDP_FeatureDefn{FD}( +# ptr::GDAL.OGRFeatureDefnH = C_NULL; +# ownedby::Union{Nothing,FDP_AbstractFeatureLayer{FD}} = nothing, +# ) where {FD<:FDType} +# return new(ptr, ownedby) +# end +# end + +# function destroy(fdp_featuredefn::FDP_FeatureDefn) +# GDAL.ogr_fd_destroy(fdp_featuredefn.ptr) +# fdp_featuredefn.ptr = C_NULL +# fdp_featuredefn.ownedby = nothing +# return nothing +# end + +mutable struct FDP_IFeatureDefnView{FD} <: FDP_AbstractFeatureDefn{FD} + ptr::GDAL.OGRFeatureDefnH + ownedby::Union{Nothing,FDP_AbstractFeatureLayer{FD}} + + function FDP_IFeatureDefnView{FD}( + ptr::GDAL.OGRFeatureDefnH = C_NULL; + ownedby::Union{Nothing,FDP_AbstractFeatureLayer{FD}} = nothing, + ) where {FD<:FDType} + fdp_ifeaturedefnview = new(ptr, ownedby) + finalizer(destroy, fdp_ifeaturedefnview) + return fdp_ifeaturedefnview + end +end + +function destroy(fdp_ifeaturedefnview::FDP_IFeatureDefnView) + fdp_ifeaturedefnview.ptr = C_NULL + fdp_ifeaturedefnview.ownedby = nothing + return nothing +end + +#! NEW FeatureDefn parameterized Feature and IFeature +#! Unsafe version disabled as there is no usage for Table struct +# mutable struct FDP_Feature{FD} <: FDP_AbstractFeature{FD} +# ptr::GDAL.OGRFeatureH +# ownedby::Union{Nothing,FDP_AbstractFeatureLayer} + +# function FDP_Feature{FD}( +# ptr::GDAL.OGRFeatureH = C_NULL; +# ownedby::Union{Nothing,FDP_AbstractFeatureLayer} = nothing, +# ) where {FD<:FDType} +# return new(ptr, ownedby) +# end +# end + +function destroy(fdp_feature::FDP_AbstractFeature) + GDAL.ogr_f_destroy(fdp_feature.ptr) + fdp_feature.ptr = C_NULL + fdp_feature.ownedby = nothing + return nothing +end + +mutable struct FDP_IFeature{FD} <: FDP_AbstractFeature{FD} + ptr::GDAL.OGRFeatureH + ownedby::Union{Nothing,FDP_AbstractFeatureLayer} + + function FDP_IFeature{FD}( + ptr::GDAL.OGRFeatureH = C_NULL; + ownedby::Union{Nothing,FDP_AbstractFeatureLayer} = nothing, + ) where {FD<:FDType} + fdp_ifeature = new(ptr, ownedby) + finalizer(destroy, fdp_ifeature) + return fdp_ifeature + end +end + +#! NEW Geometry and IGeometry => disabled since no performance gain identified yet +# abstract type GP_AbstractGeometry{G<:GType} <: GeoInterface.AbstractGeometry end + +# function _inferGType(ptr::GDAL.OGRGeometryH = C_NULL)::Type{<:GType} +# return ptr != C_NULL ? +# GType{OGRwkbGeometryType(Int32(GDAL.ogr_g_getgeometrytype(ptr)))} : +# GType{wkbUnknown} +# end + +# mutable struct GP_Geometry{G} <: GP_AbstractGeometry{G} +# ptr::GDAL.OGRGeometryH +# ownedby::Union{Nothing,FDP_AbstractFeature} + +# function GP_Geometry{G}( +# ptr::GDAL.OGRGeometryH = C_NULL, +# ownedby::Union{Nothing,FDP_AbstractFeature} = nothing, +# ) where {G<:GType} +# return GP_Geometry{_inferGType(ptr)}(ptr, ownedby) +# end +# end + +# mutable struct GP_IGeometry{G} <: GP_AbstractGeometry{G} +# ptr::GDAL.OGRGeometryH +# ownedby::Union{Nothing,FDP_AbstractFeature} + +# function GP_IGeometry{G}( +# ptr::GDAL.OGRGeometryH = C_NULL, +# ownedby::Union{Nothing,FDP_AbstractFeature} = nothing, +# ) where {G<:GType} +# gp_igeometry = new{_inferGType(ptr)}(ptr, ownedby) +# finalizer(destroy, gp_igeometry) +# return gp_igeometry +# end +# end + +#! NEW FeatureDefn parameterized FeatureLayer and IFeatureLayer +#! Unsafe version disabled as there is no usage for Table struct +# mutable struct FDP_FeatureLayer{FD} <: FDP_AbstractFeatureLayer{FD} +# ptr::GDAL.OGRLayerH +# ownedby::AbstractDataset +# spatialref::Union{Nothing,AbstractSpatialRef} + +# function FDP_FeatureLayer{FD}( +# ptr::GDAL.OGRLayerH = C_NULL; +# ownedby::AbstractDataset = Dataset(), +# spatialref::Union{Nothing,AbstractSpatialRef} = nothing, +# ) where {FD<:FDType} +# return new(ptr, ownedby, spatialref) +# end +# end + +mutable struct FDP_IFeatureLayer{FD} <: FDP_AbstractFeatureLayer{FD} + ptr::GDAL.OGRLayerH + ownedby::Union{Nothing,AbstractDataset} + spatialref::Union{Nothing,AbstractSpatialRef} + + function FDP_IFeatureLayer{FD}( + ptr::GDAL.OGRLayerH = C_NULL; + ownedby::AbstractDataset = Dataset(), + spatialref::Union{Nothing,AbstractSpatialRef} = nothing, + ) where {FD<:FDType} + fdp_layer = new(ptr, ownedby, spatialref) + finalizer(destroy, fdp_layer) + return fdp_layer + end +end + +function destroy(fdp_layer::FDP_AbstractFeatureLayer) + # No specific GDAL object destructor for layer, it will be handled by the dataset closing + fdp_layer.ptr = C_NULL + fdp_layer.ownedby = nothing + fdp_layer.spatialref = nothing + return nothing +end + +############################################################################### +# 4. Conversion function for parametric ArchGDAL vector data types # +############################################################################### + +# Default DataType = LAST, for duplicated (oftid, ofstid) values +const DataType_2_OGRFieldType_OGRFieldSubType_mapping = Base.ImmutableDict( + Bool => (OFTInteger, OFSTBoolean), + Int8 => (OFTInteger, OFSTNone), + Int16 => (OFTInteger, OFSTInt16), + Int32 => (OFTInteger, OFSTNone), # Default OFTInteger + Vector{Bool} => (OFTIntegerList, OFSTBoolean), + Vector{Int16} => (OFTIntegerList, OFSTInt16), + Vector{Int32} => (OFTIntegerList, OFSTNone), # Default OFTIntegerList + Float16 => (OFTReal, OFSTNone), + Float32 => (OFTReal, OFSTFloat32), + Float64 => (OFTReal, OFSTNone), # Default OFTReal + Vector{Float16} => (OFTRealList, OFSTNone), + Vector{Float32} => (OFTRealList, OFSTFloat32), + Vector{Float64} => (OFTRealList, OFSTNone), # Default OFTRealList + String => (OFTString, OFSTNone), + Vector{String} => (OFTStringList, OFSTNone), + Vector{UInt8} => (OFTBinary, OFSTNone), + Dates.Date => (OFTDate, OFSTNone), + Dates.Time => (OFTTime, OFSTNone), + Dates.DateTime => (OFTDateTime, OFSTNone), + Int64 => (OFTInteger64, OFSTNone), + Vector{Int64} => (OFTInteger64List, OFSTNone), +) + +const OGRField_DataTypes = Union{ + Missing, + Nothing, + keys(DataType_2_OGRFieldType_OGRFieldSubType_mapping)..., +} + +# Conversions from DataType to FType +const DataType2FType = Base.ImmutableDict( + ( + k => FType{v...} for + (k, v) in DataType_2_OGRFieldType_OGRFieldSubType_mapping + )..., +) +# GDALDataTypes = Union{keys(DataType2FType)...} +# @generated function convert(::Type{FType}, ::Type{T}) where {T<:GDALDataTypes} +# result = get(DataType2FType, T, missing) +# !ismissing(result) || throw(MethodError(convert, (FType, T))) +# return :($(result)) +# end +# #! Conversion from FType to DataType not implemented because it creates a mess +# #! use get(FType2DataType, FT, missing) instead +const FType2DataType = + Base.ImmutableDict((v => k for (k, v) in DataType2FType)...) +# # # GDALFTypes = Union{keys(FType2DataType)...} +# # @generated function convert(::Type{DataType}, ::Type{T}) where T<:FType +# # result = get(FType2DataType, T, missing) +# # result !== missing || error( +# # "$T is not an FType corresponding to a valid GDAL (OGRFieldType, OGRFieldSubType) couple. \nPlease use one of the following: \n$(join((FType{v...} for (_, v) in DataType_2_OGRFieldType_OGRFieldSubType_mapping), "\n"))", +# # ) +# # return :($(result)) +# # end + +# Conversion between Geometry or IGeometry subtypes and GType subtypes +# function convert(::Type{Geometry}, G::Type{GType{T}}) where {T} +# return Geometry{T} +# end +function convert(::Type{IGeometry}, ::Type{GType{T}}) where {T} + return IGeometry{T} +end +# function convert(::Type{GType}, ::Type{Geometry{T}}) where {T} +# return GType{T} +# end +# function convert(::Type{GType}, ::Type{IGeometry{T}}) where {T} +# return GType{T} +# end + +# Conversion between GP_Geometry or GP_IGeometry subtypes and GType subtypes +# function convert(::Type{GP_Geometry}, ::Type{G}) where {G<:GType} +# return GP_Geometry{G} +# end +# function convert(::Type{GType}, ::Type{GP_Geometry{G}}) where {G<:GType} +# return G +# end +# function convert(::Type{GP_IGeometry}, ::Type{G}) where {G<:GType} +# return GP_IGeometry{G} +# end +# function convert(::Type{GType}, ::Type{GP_IGeometry{G}}) where {G<:GType} +# return G +# end + +############################################################################### +# 5.a Methods for GFTP_AbstractGeomFieldDefn # +############################################################################### + +# None found useful to specialize yet + +############################################################################### +# 5.b Methods for FTP_AbstractFieldDefn # +############################################################################### + +@generated function gettype(::FTP_AbstractFieldDefn{FType{T,ST}}) where {T,ST} + return :($T) +end + +@generated function getsubtype( + ::FTP_AbstractFieldDefn{FType{T,ST}}, +) where {T,ST} + return :($ST) +end + +@generated function getfieldtype( + ::FTP_AbstractFieldDefn{FType{T,ST}}, +) where {T,ST} + return ST != OFSTNone ? :($ST) : :($T) +end + +############################################################################### +# 5.c Methods for FDP_AbstractFeatureDefn # +############################################################################### + +# Geometries methods +@generated function ngeom(::FDP_AbstractFeatureDefn{FD}) where {FD<:FDType} + return :($(_ngt(FD))) +end + +# function getgeomdefn( +# fdp_featuredefn::FDP_FeatureDefn{FD}, +# i::Integer = 0, +# ) where {FD<:FDType} +# return GFTP_GeomFieldDefn{_gttypes(FD)[i+1]}( +# GDAL.ogr_fd_getgeomfielddefn(fdp_featuredefn.ptr, i); +# ownedby = fdp_featuredefn, +# ) +# end + +function getgeomdefn( + fdp_ifeaturedefnview::FDP_IFeatureDefnView{FD}, + i::Integer = 0, +) where {FD<:FDType} + return GFTP_IGeomFieldDefnView{_gttypes(FD)[i+1]}( + GDAL.ogr_fd_getgeomfielddefn(fdp_ifeaturedefnview.ptr, i); + ownedby = fdp_ifeaturedefnview, + ) +end + +# @generated function findgeomindex( +# ::FDP_AbstractFeatureDefn{FD}, +# name::AbstractString = "", +# ) where {FD<:FDType} +# return return quote +# i = findfirst(isequal(Symbol(name)), $(_gtnames(FD))) +# return i !== nothing ? i - 1 : nothing +# end +# end + +# Fields methods +@generated function nfield(::FDP_AbstractFeatureDefn{FD}) where {FD<:FDType} + return :($(_nft(FD))) +end + +# function getfielddefn( +# fdp_featuredefn::FDP_FeatureDefn{FD}, +# i::Integer = 0, +# ) where {FD<:FDType} +# return FTP_FieldDefn{_fttypes(FD)[i+1]}( +# GDAL.ogr_fd_getfielddefn(fdp_featuredefn.ptr, i); +# ownedby = fdp_featuredefn, +# ) +# end + +function getfielddefn( + fdp_ifeaturedefnview::FDP_IFeatureDefnView{FD}, + i::Integer = 0, +) where {FD<:FDType} + return FTP_IFieldDefnView{_fttypes(FD)[i+1]}( + GDAL.ogr_fd_getfielddefn(fdp_ifeaturedefnview.ptr, i); + ownedby = fdp_ifeaturedefnview, + ) +end + +# @generated function findfieldindex( +# ::FDP_AbstractFeatureDefn{FD}, +# name::Union{AbstractString,Symbol}, +# ) where {FD<:FDType} +# return return quote +# i = findfirst(isequal(Symbol(name)), $(_ftnames(FD))) +# return i !== nothing ? i - 1 : nothing +# end +# end + +function getfeaturedefn(fdp_feature::FDP_IFeature{FD}) where {FD<:FDType} + return FDP_IFeatureDefnView{FD}( + GDAL.ogr_f_getdefnref(fdp_feature.ptr); + ownedby = fdp_feature.ownedby, + ) +end + +############################################################################### +# 5.d Methods for FDP_AbstractFeature # +############################################################################### + +# Geometries +@generated function ngeom(::FDP_AbstractFeature{FD}) where {FD<:FDType} + return :($(_ngt(FD))) +end + +@generated function findgeomindex( + ::FDP_AbstractFeature{FD}, + name::Union{AbstractString,Symbol} = "", +) where {FD<:FDType} + return return quote + i = findfirst(isequal(Symbol(name)), $(_gtnames(FD))) + return i !== nothing ? i - 1 : nothing + end +end + +function stealgeom( + fdp_feature::FDP_AbstractFeature{FD}, + i::Integer, +) where {FD<:FDType} + return i == 0 ? IGeometry(GDAL.ogr_f_stealgeometry(fdp_feature.ptr)) : + getgeom(fdp_feature, i) +end + +# function stealgeom( +# fdp_feature::FDP_AbstractFeature{FD}, +# name::Union{AbstractString,Symbol}, +# ) where {FD<:FDType} +# i = findgeomindex(fdp_feature, name) +# return i == 0 ? IGeometry(GDAL.ogr_f_stealgeometry(fdp_feature.ptr)) : +# getgeom(fdp_feature, i) +# end + +# Fields +@generated function nfield(::FDP_AbstractFeature{FD}) where {FD<:FDType} + return :($(_nft(FD))) +end + +function getfielddefn( + fdp_feature::FDP_IFeature{FD}, + i::Integer, +) where {FD<:FDType} + return FTP_IFieldDefnView{_fttypes(FD)[i+1]}( + GDAL.ogr_f_getfielddefnref(fdp_feature.ptr, i); + ownedby = getfeaturedefn(fdp_feature), + ) +end + +# @generated function findfieldindex( +# ::FDP_AbstractFeature{FD}, +# name::Union{AbstractString,Symbol}, +# ) where {FD<:FDType} +# return quote +# i = findfirst(isequal(Symbol(name)), $(_ftnames(FD))) +# return i !== nothing ? i - 1 : nothing +# end +# end + +@generated function _get_fields_asfuncs(::Type{FD}) where {FD<:FDType} + return ((_FETCHFIELD[T.parameters[1]] for T in _fttypes(FD))...,) +end + +@generated function getfield( + fdp_feature::FDP_AbstractFeature{FD}, + i::Integer, +) where {FD<:FDType} + return quote + return if !isfieldset(fdp_feature, i) + nothing + elseif isfieldnull(fdp_feature, i) + missing + else + $(_get_fields_asfuncs(FD))[i+1](fdp_feature, i) + end + end +end + +# @generated function getfield( +# fdp_feature::FDP_AbstractFeature{FD}, +# name::Union{AbstractString,Symbol}, +# ) where {FD<:FDType} +# return quote +# i = findfieldindex(fdp_feature, name) +# return if i === nothing +# missing +# elseif !isfieldset(fdp_feature, i) +# nothing +# elseif isfieldnull(fdp_feature, i) +# missing +# else +# @inbounds $(_get_fields_asfuncs(FD))[i+1](fdp_feature, i) +# end +# end +# end + +function getindex(row::FDP_AbstractFeature{FD}, i::Int) where {FD<:FDType} + ng = ngeom(row) + return if i <= ng + geom = getgeom(row, i - 1) + geom.ptr != C_NULL ? geom : missing + else + getfield(row, i - ng - 1) + end +end + +# function getindex(row::FDP_AbstractFeature{FD}, name::Symbol) where {FD<:FDType} +# field = getfield(row, name) +# if !ismissing(field) +# return field +# end +# geom = getgeom(row, name) +# if geom.ptr != C_NULL +# return geom +# end +# return missing +# end + +#! getindex which steals the geometry from the feature +function getindex!(row::FDP_AbstractFeature{FD}, i::Int) where {FD<:FDType} + ng = ngeom(row) + return if i <= ng + geom = stealgeom(row, i - 1) + geom.ptr != C_NULL ? geom : missing + else + getfield(row, i - ng - 1) + end +end + +# function getindex!(row::FDP_AbstractFeature{FD}, name::Symbol) where {FD<:FDType} +# field = getfield(row, name) +# if !ismissing(field) +# return field +# end +# geom = stealgeom(row, name) +# if geom.ptr != C_NULL +# return geom +# end +# return missing +# end + +############################################################################### +# 5.e Methods for FDP_AbstractFeatureLayer # +############################################################################### + +@generated function _getFD(::FDP_AbstractFeatureLayer{FD}) where {FD<:FDType} + return FD +end + +function getFDPlayer(dataset::AbstractDataset, i::Integer)::FDP_IFeatureLayer + ptr::GDAL.OGRLayerH = GDAL.gdaldatasetgetlayer(dataset.ptr, i) + fd_ptr = GDAL.ogr_l_getlayerdefn(ptr) + FD = _getFDType(fd_ptr) + return FDP_IFeatureLayer{FD}(ptr, ownedby = dataset) +end + +function getFDPlayer(dataset::AbstractDataset) + nlayer(dataset) == 1 || + error("Dataset has multiple layers. Specify the layer number or name") + return getFDPlayer(dataset, 0) +end + +@generated function Base.iterate( + layer::FDP_AbstractFeatureLayer{FD}, + state::Integer = 0, +) where {FD<:FDType} + return quote + layer.ptr == C_NULL && return nothing + state == 0 && resetreading!(layer) + ptr = GDAL.ogr_l_getnextfeature(layer.ptr) + return if ptr == C_NULL + resetreading!(layer) + nothing + else + (FDP_IFeature{$FD}(ptr; ownedby = layer), state + 1) + end + end +end + +# function Base.eltype(::FDP_AbstractFeatureLayer{FD}) where {FD<:FDType} +# return FDP_IFeature{FD} +# end + +function layerdefn(fdp_layer::FDP_AbstractFeatureLayer{FD}) where {FD<:FDType} + return FDP_IFeatureDefnView{FD}( + GDAL.ogr_l_getlayerdefn(fdp_layer.ptr); + ownedby = fdp_layer, + ) +end + +# @generated function findfieldindex( +# ::FDP_AbstractFeatureLayer{FD}, +# field::Union{AbstractString,Symbol}, +# #! Note that exactmatch::Bool is not used in GDAL except when OGRAPISPY_ENABLED is true => dropped +# ) where {FD<:FDType} +# return return quote +# i = findfirst(isequal(Symbol(field)), $(_ftnames(FD))) +# return i !== nothing ? i - 1 : nothing +# end +# end + +# @generated function ngeom(::FDP_AbstractFeatureLayer{FD}) where {FD<:FDType} +# return :($(_ngt(FD))) +# end + +# @generated function nfield(::FDP_AbstractFeatureLayer{FD}) where {FD<:FDType} +# return :($(_nft(FD))) +# end + +@generated function gdal_schema( + ::FDP_AbstractFeatureLayer{FD}, +) where {FD<:FDType} + gnames = _gtnames(FD) + fnames = _ftnames(FD) + gtypes = (convert(IGeometry, gt) for gt in _gttypes(FD)) + ftypes = (get(FType2DataType, ft, missing) for ft in _fttypes(FD)) + return Tables.Schema((gnames..., fnames...), (gtypes..., ftypes...)) +end + +####################################################################### +# Tables.columns on FDP_AbstractFeatureLayer with generated functions # +####################################################################### +# - Feature to columns line function: FDPf2c and FDP2c! (geometry stealing) +# - Feature layer to array of columns : FDPfillcolumns! with geometry stealing option +# - Feature layer to NamedTuple: _getcols with geometry stealing option + +@generated function FDPf2c( + fdp_feature::FDP_AbstractFeature{FD}, + i::Int, + cols::Vector{Vector{T} where T}, +) where {FD<:FDType} + ng = _ngt(FD) + nf = _nft(FD) + return quote + @inbounds for j in 1:($nf+$ng) + cols[j][i] = getindex(fdp_feature, j) + end + return nothing + end +end + +@generated function FDPf2c!( + fdp_feature::FDP_AbstractFeature{FD}, + i::Int, + cols::Vector{Vector{T} where T}, +) where {FD<:FDType} + ng = _ngt(FD) + nf = _nft(FD) + return quote + @inbounds for j in 1:($nf+$ng) + cols[j][i] = getindex!(fdp_feature, j) + end + return nothing + end +end + +function FDPfillcolumns!( + fdp_layer::FDP_AbstractFeatureLayer{FD}, + cols::Vector{Vector{T} where T}, + preserve::Bool = true, +) where {FD<:FDType} + state = 0 + if preserve + while true + next = iterate(fdp_layer, state) + next === nothing && break + fdp_feature, state = next + FDPf2c(fdp_feature, state, cols) + end + else + while true + next = iterate(fdp_layer, state) + next === nothing && break + fdp_feature, state = next + FDPf2c!(fdp_feature, state, cols) + end + end +end + +function _getcols( + fdp_layer::FDP_AbstractFeatureLayer{FD}; + preserve::Bool, +) where {FD<:FDType} + len = length(fdp_layer) + gdal_sch = gdal_schema(fdp_layer) + ng = _ngt(FD) + cols = [ + [Vector{Union{Missing,IGeometry}}(missing, len) for _ in 1:ng] + [ + Vector{Union{Missing,Nothing,T}}(missing, len) for + T in gdal_sch.types[ng+1:end] + ] + ] + FDPfillcolumns!(fdp_layer, cols, preserve) + return if VERSION < v"1.7" + NamedTuple{gdal_sch.names}( + NTuple{length(gdal_sch.names),Vector{T} where T}([ + convert( + Vector{promote_type(unique(typeof(e) for e in c)...)}, + c, + ) for c in cols + ]), + ) + else # Shorter code + NamedTuple{gdal_sch.names}( + convert(Vector{promote_type(unique(typeof(e) for e in c)...)}, c) + for c in cols + ) + end +end + +#*############################################################################# +#* ArchGDAL.Table object # +#*############################################################################# + +struct Table + cols::T where {T<:NamedTuple} +end + +# Table constructors +function Table(layer::AbstractFeatureLayer) + return Table( + _getcols( + FDP_IFeatureLayer{_getFDType(layerdefn(layer).ptr)}( + layer.ptr::GDAL.OGRLayerH; + ownedby = layer.ownedby, + ); + preserve = true, + ), + ) +end +function Table(dataset::AbstractDataset, i::Integer) + return Table(_getcols(getFDPlayer(dataset, i); preserve = false)) +end +function Table(dataset::AbstractDataset) + return Table(_getcols(getFDPlayer(dataset); preserve = false)) +end +function Table(file::String, i::Integer; kwargs...) + return Table( + _getcols(getFDPlayer(read(file; kwargs...), i); preserve = false), + ) +end +function Table(file::String; kwargs...) + return Table(_getcols(getFDPlayer(read(file; kwargs...)); preserve = false)) +end + +#*############################################################################# +#* Table's Tables.jl interface # +#*############################################################################# +# Usage of NamedTuples in Table struct brings native support of Tables.jl interface +# Usage of NamedTuples prevents extremely wide tables with # of columns > 67K +# which is due to Julia compiler limitation +# Should a need arise for larger tables, Table struct would have to be modified + +Tables.istable(::Table) = true +Tables.schema(table::Table) = Tables.schema(table.cols) +#TODO after completion of PR #243: Tables.materializer(table::Table) = XXX + +# Table Tables.Columns interface +Tables.columnaccess(::Table) = true +Tables.columns(table::Table) = table.cols + +# Table Tables.Rows interface +Tables.rowaccess(::Table) = true +function Tables.rows(table::Table) + return [ + NamedTuple{ + fieldnames(typeof(table.cols)), + Tuple{eltype.(fieldtypes(typeof(table.cols)))...}, + }( + Base.getindex(c, k) for c in table.cols + ) for k in 1:length(table.cols[begin]) + ] +end diff --git a/src/types.jl b/src/types.jl index a2936a2b..25d617d1 100644 --- a/src/types.jl +++ b/src/types.jl @@ -1,25 +1,34 @@ import DiskArrays: AbstractDiskArray import Base.convert -abstract type AbstractGeometry <: GeoInterface.AbstractGeometry end -# needs to have a `ptr::GDAL.OGRGeometryH` attribute - abstract type AbstractSpatialRef end # needs to have a `ptr::GDAL.OGRSpatialReferenceH` attribute abstract type AbstractDataset end # needs to have a `ptr::GDAL.GDALDatasetH` attribute -abstract type AbstractFeatureDefn end +abstract type DUAL_AbstractGeometry <: GeoInterface.AbstractGeometry end #! NEW abstract type supertype of AbstractGeometry and GP_AbstractGeometry +abstract type AbstractGeometry <: DUAL_AbstractGeometry end +# needs to have a `ptr::GDAL.OGRGeometryH` attribute + +abstract type DUAL_AbstractFeatureDefn end #! NEW abstract type supertype of AbstractFeatureDefn and FDP_AbstractFeatureDefn +abstract type AbstractFeatureDefn <: DUAL_AbstractFeatureDefn end # needs to have a `ptr::GDAL.OGRFeatureDefnH` attribute -abstract type AbstractFeatureLayer end +abstract type DUAL_AbstractFeatureLayer end #! NEW abstract type supertype of AbstractFeatureLayer and FDP_AbstractFeatureLayer +abstract type AbstractFeatureLayer <: DUAL_AbstractFeatureLayer end # needs to have a `ptr::GDAL.OGRLayerH` attribute -abstract type AbstractFieldDefn end +abstract type DUAL_AbstractFeature end #! NEW abstract type supertype of AbstractFeature and FDP_AbstractFeature +abstract type AbstractFeature <: DUAL_AbstractFeature end #! NEW abstract type to group Feature and IFeature (if created) +# needs to have a `ptr::GDAL.OGRFeatureH attribute + +abstract type DUAL_AbstractFieldDefn end #! NEW abstract type, supertype of AbstractFieldDefn and FTP_AbstractFieldDefn +abstract type AbstractFieldDefn <: DUAL_AbstractFieldDefn end # needs to have a `ptr::GDAL.OGRFieldDefnH` attribute -abstract type AbstractGeomFieldDefn end +abstract type DUAL_AbstractGeomFieldDefn end #! NEW abstract type, supertype of AbstractGeomFieldDefn and GFTP_AbstractGeomFieldDefn +abstract type AbstractGeomFieldDefn <: DUAL_AbstractGeomFieldDefn end # needs to have a `ptr::GDAL.OGRGeomFieldDefnH` attribute abstract type AbstractRasterBand{T} <: AbstractDiskArray{T,2} end @@ -139,7 +148,7 @@ mutable struct IFeatureLayer <: AbstractFeatureLayer end end -mutable struct Feature +mutable struct Feature <: AbstractFeature ptr::GDAL.OGRFeatureH end @@ -435,80 +444,16 @@ end OGRSTUInches::GDAL.OGRSTUInches, ) -@convert( - OGRwkbGeometryType::GDAL.OGRwkbGeometryType, - wkbUnknown::GDAL.wkbUnknown, - wkbPoint::GDAL.wkbPoint, - wkbLineString::GDAL.wkbLineString, - wkbPolygon::GDAL.wkbPolygon, - wkbMultiPoint::GDAL.wkbMultiPoint, - wkbMultiLineString::GDAL.wkbMultiLineString, - wkbMultiPolygon::GDAL.wkbMultiPolygon, - wkbGeometryCollection::GDAL.wkbGeometryCollection, - wkbCircularString::GDAL.wkbCircularString, - wkbCompoundCurve::GDAL.wkbCompoundCurve, - wkbCurvePolygon::GDAL.wkbCurvePolygon, - wkbMultiCurve::GDAL.wkbMultiCurve, - wkbMultiSurface::GDAL.wkbMultiSurface, - wkbCurve::GDAL.wkbCurve, - wkbSurface::GDAL.wkbSurface, - wkbPolyhedralSurface::GDAL.wkbPolyhedralSurface, - wkbTIN::GDAL.wkbTIN, - wkbTriangle::GDAL.wkbTriangle, - wkbNone::GDAL.wkbNone, - wkbLinearRing::GDAL.wkbLinearRing, - wkbCircularStringZ::GDAL.wkbCircularStringZ, - wkbCompoundCurveZ::GDAL.wkbCompoundCurveZ, - wkbCurvePolygonZ::GDAL.wkbCurvePolygonZ, - wkbMultiCurveZ::GDAL.wkbMultiCurveZ, - wkbMultiSurfaceZ::GDAL.wkbMultiSurfaceZ, - wkbCurveZ::GDAL.wkbCurveZ, - wkbSurfaceZ::GDAL.wkbSurfaceZ, - wkbPolyhedralSurfaceZ::GDAL.wkbPolyhedralSurfaceZ, - wkbTINZ::GDAL.wkbTINZ, - wkbTriangleZ::GDAL.wkbTriangleZ, - wkbPointM::GDAL.wkbPointM, - wkbLineStringM::GDAL.wkbLineStringM, - wkbPolygonM::GDAL.wkbPolygonM, - wkbMultiPointM::GDAL.wkbMultiPointM, - wkbMultiLineStringM::GDAL.wkbMultiLineStringM, - wkbMultiPolygonM::GDAL.wkbMultiPolygonM, - wkbGeometryCollectionM::GDAL.wkbGeometryCollectionM, - wkbCircularStringM::GDAL.wkbCircularStringM, - wkbCompoundCurveM::GDAL.wkbCompoundCurveM, - wkbCurvePolygonM::GDAL.wkbCurvePolygonM, - wkbMultiCurveM::GDAL.wkbMultiCurveM, - wkbMultiSurfaceM::GDAL.wkbMultiSurfaceM, - wkbCurveM::GDAL.wkbCurveM, - wkbSurfaceM::GDAL.wkbSurfaceM, - wkbPolyhedralSurfaceM::GDAL.wkbPolyhedralSurfaceM, - wkbTINM::GDAL.wkbTINM, - wkbTriangleM::GDAL.wkbTriangleM, - wkbPointZM::GDAL.wkbPointZM, - wkbLineStringZM::GDAL.wkbLineStringZM, - wkbPolygonZM::GDAL.wkbPolygonZM, - wkbMultiPointZM::GDAL.wkbMultiPointZM, - wkbMultiLineStringZM::GDAL.wkbMultiLineStringZM, - wkbMultiPolygonZM::GDAL.wkbMultiPolygonZM, - wkbGeometryCollectionZM::GDAL.wkbGeometryCollectionZM, - wkbCircularStringZM::GDAL.wkbCircularStringZM, - wkbCompoundCurveZM::GDAL.wkbCompoundCurveZM, - wkbCurvePolygonZM::GDAL.wkbCurvePolygonZM, - wkbMultiCurveZM::GDAL.wkbMultiCurveZM, - wkbMultiSurfaceZM::GDAL.wkbMultiSurfaceZM, - wkbCurveZM::GDAL.wkbCurveZM, - wkbSurfaceZM::GDAL.wkbSurfaceZM, - wkbPolyhedralSurfaceZM::GDAL.wkbPolyhedralSurfaceZM, - wkbTINZM::GDAL.wkbTINZM, - wkbTriangleZM::GDAL.wkbTriangleZM, - wkbPoint25D::GDAL.wkbPoint25D, - wkbLineString25D::GDAL.wkbLineString25D, - wkbPolygon25D::GDAL.wkbPolygon25D, - wkbMultiPoint25D::GDAL.wkbMultiPoint25D, - wkbMultiLineString25D::GDAL.wkbMultiLineString25D, - wkbMultiPolygon25D::GDAL.wkbMultiPolygon25D, - wkbGeometryCollection25D::GDAL.wkbGeometryCollection25D, -) +# Conversions below assume that both +# - OGRwkbGeometryType Enum instances and +# - GDAL.OGRwkbGeometryType CEnum.Cenum instances +# have same Integer assigned values +function convert(::Type{OGRwkbGeometryType}, gogtinst::GDAL.OGRwkbGeometryType) + return OGRwkbGeometryType(Integer(gogtinst)) +end +function convert(::Type{GDAL.OGRwkbGeometryType}, ogtinst::OGRwkbGeometryType) + return GDAL.OGRwkbGeometryType(Integer(ogtinst)) +end function basetype(gt::OGRwkbGeometryType)::OGRwkbGeometryType wkbGeomType = convert(GDAL.OGRwkbGeometryType, gt) diff --git a/test/remotefiles.jl b/test/remotefiles.jl index a8fe07de..a189ac4a 100644 --- a/test/remotefiles.jl +++ b/test/remotefiles.jl @@ -38,6 +38,14 @@ remotefiles = [ "data/utmsmall.tif", "f40dae6e8b5e18f3648e9f095e22a0d7027014bb463418d32f732c3756d8c54f", ), + ( + "data/unset_null_testcase.geojson", + "f4ebb3953ff0852723569d6fb1f8519632f5ff9f413e751fe63742a7f2b365b0", + ), + ( + "data/test_DUALxxx_methods.geojson", + "a42d0528e49ad8fa170e20d6c7d928d8403c4fd07076aea71f0dafb83c10582c", + ), ( "gdalworkshop/world.tif", "b376dc8af62f9894b5050a6a9273ac0763ae2990b556910d35d4a8f4753278bb", diff --git a/test/runtests.jl b/test/runtests.jl index e0ac72a6..c95ec859 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -10,6 +10,7 @@ include("remotefiles.jl") include("test_doctest.jl") include("test_convert.jl") include("test_tables.jl") + include("test_tables2.jl") include("test_gdal_tutorials.jl") include("test_geometry.jl") include("test_types.jl") diff --git a/test/test_tables.jl b/test/test_tables.jl index 47f91bd2..d23f0f02 100644 --- a/test/test_tables.jl +++ b/test/test_tables.jl @@ -38,11 +38,11 @@ using Tables (:point, :linestring, :id, :zoom, :location) @test ismissing(Tables.getcolumn(features[2], -5)) @test ismissing(Tables.getcolumn(features[2], 0)) - @test Tables.getcolumn(features[1], 1) == "5.1" - @test Tables.getcolumn(features[1], 2) == "1.0" - @test Tables.getcolumn(features[1], 3) == "Mumbai" - @test AG.toWKT(Tables.getcolumn(features[1], 4)) == "POINT (30 10)" - @test AG.toWKT(Tables.getcolumn(features[1], 5)) == + @test Tables.getcolumn(features[1], 3) == "5.1" + @test Tables.getcolumn(features[1], 4) == "1.0" + @test Tables.getcolumn(features[1], 5) == "Mumbai" + @test AG.toWKT(Tables.getcolumn(features[1], 1)) == "POINT (30 10)" + @test AG.toWKT(Tables.getcolumn(features[1], 2)) == "LINESTRING (30 10,10 30,40 40)" @test Tables.getcolumn(features[1], :id) == "5.1" @test Tables.getcolumn(features[1], :zoom) == "1.0" @@ -57,11 +57,11 @@ using Tables (:point, :linestring, :id, :zoom, :location) @test ismissing(Tables.getcolumn(features[2], -5)) @test ismissing(Tables.getcolumn(features[2], 0)) - @test Tables.getcolumn(features[2], 1) == "5.2" - @test Tables.getcolumn(features[2], 2) == "2.0" - @test Tables.getcolumn(features[2], 3) == "New Delhi" - @test AG.toWKT(Tables.getcolumn(features[2], 4)) == "POINT (35 15)" - @test AG.toWKT(Tables.getcolumn(features[2], 5)) == + @test Tables.getcolumn(features[2], 3) == "5.2" + @test Tables.getcolumn(features[2], 4) == "2.0" + @test Tables.getcolumn(features[2], 5) == "New Delhi" + @test AG.toWKT(Tables.getcolumn(features[2], 1)) == "POINT (35 15)" + @test AG.toWKT(Tables.getcolumn(features[2], 2)) == "LINESTRING (35 15,15 35,45 45)" @test Tables.getcolumn(features[2], :id) == "5.2" @test Tables.getcolumn(features[2], :zoom) == "2.0" diff --git a/test/test_tables2.jl b/test/test_tables2.jl new file mode 100644 index 00000000..b863be20 --- /dev/null +++ b/test/test_tables2.jl @@ -0,0 +1,337 @@ +using Test +import ArchGDAL +const AG = ArchGDAL +using Dates +using Tables + +@testset "Unit testing of parameterized types" begin + @testset "FType, GType and FDType helper functions" begin + FD = Tuple{ + NamedTuple{(Symbol(""),),Tuple{AG.GType{AG.wkbLineString}}}, + NamedTuple{ + (:gid, :roadcode), + Tuple{ + AG.FType{AG.OFTInteger,AG.OFSTNone}, + AG.FType{AG.OFTString,AG.OFSTNone}, + }, + }, + } + @test AG._ngt(FD) == 1 + @test AG._gtnames(FD) === (Symbol(""),) + @test AG._gttypes(FD) === (AG.GType{AG.wkbLineString},) + @test AG._nft(FD) == 2 + @test AG._ftnames(FD) === (:gid, :roadcode) + @test AG._fttypes(FD) === ( + AG.FType{AG.OFTInteger,AG.OFSTNone}, + AG.FType{AG.OFTString,AG.OFSTNone}, + ) + + @test AG.getGType( + AG.getgeomdefn( + AG.layerdefn( + AG.getFDPlayer( + AG.read( + "data/multi_geom.csv", + options = [ + "GEOM_POSSIBLE_NAMES=point,linestring", + "KEEP_GEOM_COLUMNS=NO", + ], + ), + 0, + ), + ), + ).ptr, + ) == AG.GType{AG.wkbUnknown} + + @test AG.getFType( + AG.getfielddefn( + AG.layerdefn( + AG.getFDPlayer( + AG.read( + "data/multi_geom.csv", + options = [ + "GEOM_POSSIBLE_NAMES=point,linestring", + "KEEP_GEOM_COLUMNS=NO", + ], + ), + 0, + ), + ), + ).ptr, + ) == AG.FType{AG.OFTString,AG.OFSTNone} + + @test AG._getFDType( + AG.layerdefn( + AG.getFDPlayer( + AG.read( + "data/multi_geom.csv", + options = [ + "GEOM_POSSIBLE_NAMES=point,linestring", + "KEEP_GEOM_COLUMNS=NO", + ], + ), + 0, + ), + ).ptr, + ) == Tuple{ + NamedTuple{ + (:point, :linestring), + Tuple{AG.GType{AG.wkbUnknown},AG.GType{AG.wkbUnknown}}, + }, + NamedTuple{ + (:id, :zoom, :location), + Tuple{ + AG.FType{AG.OFTString,AG.OFSTNone}, + AG.FType{AG.OFTString,AG.OFSTNone}, + AG.FType{AG.OFTString,AG.OFSTNone}, + }, + }, + } + + @test AG._getFD( + AG.getFDPlayer( + AG.read( + "data/multi_geom.csv", + options = [ + "GEOM_POSSIBLE_NAMES=point,linestring", + "KEEP_GEOM_COLUMNS=NO", + ], + ), + 0, + ), + ) == Tuple{ + NamedTuple{ + (:point, :linestring), + Tuple{AG.GType{AG.wkbUnknown},AG.GType{AG.wkbUnknown}}, + }, + NamedTuple{ + (:id, :zoom, :location), + Tuple{ + AG.FType{AG.OFTString,AG.OFSTNone}, + AG.FType{AG.OFTString,AG.OFSTNone}, + AG.FType{AG.OFTString,AG.OFSTNone}, + }, + }, + } + end + + @testset "Parameterized types constructors and destructors" begin + @testset "Tests for FTP_AbstractFieldDefn" begin + fielddefn = AG.getfielddefn( + AG.layerdefn( + AG.getFDPlayer( + AG.read( + "data/multi_geom.csv", + options = [ + "GEOM_POSSIBLE_NAMES=point,linestring", + "KEEP_GEOM_COLUMNS=NO", + ], + ), + 0, + ), + ), + ) + @test AG.getname(fielddefn) == "id" + @test AG.gettype(fielddefn) == AG.OFTString + @test AG.getsubtype(fielddefn) == AG.OFSTNone + end + + @testset "Tests for GFTP_AbstractGeomFieldDefn" begin + geomdefn = AG.getgeomdefn( + AG.layerdefn( + AG.getFDPlayer( + AG.read( + "data/multi_geom.csv", + options = [ + "GEOM_POSSIBLE_NAMES=point,linestring", + "KEEP_GEOM_COLUMNS=NO", + ], + ), + 0, + ), + ), + ) + @test AG.getname(geomdefn) == "point" + @test AG.gettype(geomdefn) == AG.wkbUnknown + end + end +end + +@testset "Unit testing of parametric feature layer types and associated methods" begin + fdp_layer = AG.getFDPlayer(AG.read("data/unset_null_testcase.geojson")) + + # Parametric types display tests + @test sprint(print, fdp_layer) == + "Layer: unset_null_testcase\n Geometry 0 (): [wkbPoint], POINT (100 0), POINT (100.2785 0.0893), ...\n Field 0 (FID): [OFTReal], 2.0, 3.0, 0.0, 3.0\n Field 1 (pointname): [OFTString], point-a, nothing, missing, b\n" + @test sprint(print, AG.layerdefn(fdp_layer)) == + " Geometry (index 0): (wkbPoint)\n Field (index 0): FID (OFTReal)\n Field (index 1): pointname (OFTString)\n" + @test sprint(print, iterate(fdp_layer)[1]) == + "Feature\n (index 0) geom => POINT\n (index 0) FID => 2.0\n (index 1) pointname => point-a\n" + + # Tests on methods for DUAL_xxx abstract types + @test Base.IteratorSize(fdp_layer) == Base.SizeUnknown() + @test Base.length(fdp_layer) == 4 + # Tests on methods for DUAL_xxx abstract types + fdp_feature = + iterate(AG.getFDPlayer(AG.read("data/test_DUALxxx_methods.geojson")))[1] + @test AG.asint(fdp_feature, 0) == typemax(Int32) + @test AG.asint64(fdp_feature, 1) == typemax(Int64) + @test AG.asdouble(fdp_feature, 2) == floatmax(Float64) + @test AG.asstring(fdp_feature, 3) == "Hello" + @test AG.asintlist(fdp_feature, 4) == [1, typemax(Int32)] + @test AG.asint64list(fdp_feature, 5) == [1, typemax(Int64)] + @test AG.asdoublelist(fdp_feature, 6) == [1.0, floatmax(Float64)] + @test AG.asstringlist(fdp_feature, 7) == ["Hello", "World"] + @test AG.asbinary(fdp_feature, 8) == Vector{UInt8}("Hello") + @test AG.asdatetime(fdp_feature, 9) == DateTime(2022, 1, 14, 7, 10, 1) + @test AG.getfield(fdp_feature, nothing) === missing + @test AG.toWKT(AG.getgeom(fdp_feature, 0)) == "POINT (100 0)" + @test AG.toWKT(AG.getgeom(fdp_feature, "")) == "POINT (100 0)" +end + +@testset "Unit testing of Table object and its Tables.jl interface" begin + # Helper functions + toWKT_withmissings(::Missing) = missing + toWKT_withmissings(x::AG.AbstractGeometry) = AG.toWKT(x) + toWKT_withmissings(x::Any) = x + function toWKT_withmissings( + x::T, + ) where {T<:NamedTuple{N,<:Tuple{Vararg{Vector}}}} where {N} + return NamedTuple([k => toWKT_withmissings.(x[k]) for k in keys(x)]) + end + + @testset "Unit testing of Table object constructors" begin + # Table constructor from AG standard layer type + @test string( + toWKT_withmissings( + AG.Table( + AG.getlayer(AG.read("data/unset_null_testcase.geojson")), + ).cols, + ), + ) == string( + NamedTuple([ + Symbol("") => Union{Missing,String}[ + "POINT (100 0)", + "POINT (100.2785 0.0893)", + "POINT (100 0)", + missing, + ], + :FID => [2.0, 3.0, 0.0, 3.0], + :pointname => Union{Missing,Nothing,String}[ + "point-a", + nothing, + missing, + "b", + ], + ]), + ) + + # Table constructors from from dataset + @test string( + toWKT_withmissings( + AG.Table(AG.read("data/unset_null_testcase.geojson"), 0).cols, + ), + ) == string( + NamedTuple([ + Symbol("") => Union{Missing,String}[ + "POINT (100 0)", + "POINT (100.2785 0.0893)", + "POINT (100 0)", + missing, + ], + :FID => [2.0, 3.0, 0.0, 3.0], + :pointname => Union{Missing,Nothing,String}[ + "point-a", + nothing, + missing, + "b", + ], + ]), + ) + @test string( + toWKT_withmissings( + AG.Table(AG.read("data/unset_null_testcase.geojson")).cols, + ), + ) == string( + NamedTuple([ + Symbol("") => Union{Missing,String}[ + "POINT (100 0)", + "POINT (100.2785 0.0893)", + "POINT (100 0)", + missing, + ], + :FID => [2.0, 3.0, 0.0, 3.0], + :pointname => Union{Missing,Nothing,String}[ + "point-a", + nothing, + missing, + "b", + ], + ]), + ) + + # Table constructors from a file + @test string( + toWKT_withmissings( + AG.Table("data/unset_null_testcase.geojson", 0).cols, + ), + ) == string( + NamedTuple([ + Symbol("") => Union{Missing,String}[ + "POINT (100 0)", + "POINT (100.2785 0.0893)", + "POINT (100 0)", + missing, + ], + :FID => [2.0, 3.0, 0.0, 3.0], + :pointname => Union{Missing,Nothing,String}[ + "point-a", + nothing, + missing, + "b", + ], + ]), + ) + @test string( + toWKT_withmissings( + AG.Table("data/unset_null_testcase.geojson").cols, + ), + ) == string( + NamedTuple([ + Symbol("") => Union{Missing,String}[ + "POINT (100 0)", + "POINT (100.2785 0.0893)", + "POINT (100 0)", + missing, + ], + :FID => [2.0, 3.0, 0.0, 3.0], + :pointname => Union{Missing,Nothing,String}[ + "point-a", + nothing, + missing, + "b", + ], + ]), + ) + end + + @testset "Tables interface for AG.Table" begin + table = AG.Table("data/unset_null_testcase.geojson") + @test Tables.istable(table) == true + @test Tables.schema(table) == Tables.Schema( + (Symbol(""), :FID, :pointname), + Tuple{ + Union{Missing,AG.IGeometry{AG.wkbPoint}}, + Float64, + Union{Missing,Nothing,String}, + }, + ) + @test Tables.rowaccess(table) == true + @test Tables.columnaccess(table) == true + @test string(toWKT_withmissings(Tables.columns(table))) == + string(toWKT_withmissings(table.cols)) + @test string( + toWKT_withmissings(Tables.columntable(Tables.rows(table))), + ) == string(toWKT_withmissings(table.cols)) + end +end