diff --git a/Project.toml b/Project.toml index 7b24f05e..dc07b79f 100644 --- a/Project.toml +++ b/Project.toml @@ -19,13 +19,19 @@ GeoInterfaceRecipes = "0329782f-3d07-4b52-b9f6-d3137cf03c7a" ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534" Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" +[weakdeps] +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" + +[extensions] +ArchGDALMakieExt = "Makie" + [compat] CEnum = "0.4, 0.5" ColorTypes = "0.10, 0.11" -Dates = "<0.0.1,1" +Dates = "<0.0.1, 1" DiskArrays = "0.3, 0.4" Extents = "0.1" -GDAL = "1.7" +GDAL = "1.8" GeoFormatTypes = "0.4.2" GeoInterface = "1" GeoInterfaceMakie = "0.1" @@ -35,11 +41,5 @@ Makie = "0.20, 0.21" Tables = "1" julia = "1.6" -[extensions] -ArchGDALMakieExt = "Makie" - [extras] Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" - -[weakdeps] -Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" diff --git a/src/ArchGDAL.jl b/src/ArchGDAL.jl index e7ad2ad8..a10592b8 100644 --- a/src/ArchGDAL.jl +++ b/src/ArchGDAL.jl @@ -31,6 +31,14 @@ include("ogr/featurelayer.jl") include("ogr/featuredefn.jl") include("ogr/fielddefn.jl") include("ogr/styletable.jl") +include("mdarray/types.jl") +include("mdarray/attribute.jl") +include("mdarray/dimension.jl") +include("mdarray/extendeddatatype.jl") +include("mdarray/global.jl") +include("mdarray/group.jl") +include("mdarray/highlevel.jl") +include("mdarray/mdarray.jl") include("utilities.jl") include("context.jl") include("base/iterators.jl") diff --git a/src/constants.jl b/src/constants.jl index beea25af..05f01705 100644 --- a/src/constants.jl +++ b/src/constants.jl @@ -392,28 +392,30 @@ wkbXDR::OGRwkbByteOrder = 0x00000000 @enum( GDALOpenFlag, - OF_READONLY = GDAL.GDAL_OF_READONLY, # 0x00 - OF_UPDATE = GDAL.GDAL_OF_UPDATE, # 0x01 - # OF_All = GDAL.GDAL_OF_ALL, # 0x00 - OF_RASTER = GDAL.GDAL_OF_RASTER, # 0x02 - OF_VECTOR = GDAL.GDAL_OF_VECTOR, # 0x04 - OF_GNM = GDAL.GDAL_OF_GNM, # 0x08 - OF_KIND_MASK = GDAL.GDAL_OF_KIND_MASK, # 0x1e - OF_SHARED = GDAL.GDAL_OF_SHARED, # 0x20 - OF_VERBOSE_ERROR = GDAL.GDAL_OF_VERBOSE_ERROR, # 0x40 - OF_INTERNAL = GDAL.GDAL_OF_INTERNAL, # 0x80 - # OF_DEFAULT_BLOCK_ACCESS = GDAL.GDAL_OF_DEFAULT_BLOCK_ACCESS, # 0 - OF_ARRAY_BLOCK_ACCESS = GDAL.GDAL_OF_ARRAY_BLOCK_ACCESS, # 0x0100 - OF_HASHSET_BLOCK_ACCESS = GDAL.GDAL_OF_HASHSET_BLOCK_ACCESS, # 0x0200 - # OF_RESERVED_1 = GDAL.GDAL_OF_RESERVED_1, # 0x0300 - OF_BLOCK_ACCESS_MASK = GDAL.GDAL_OF_BLOCK_ACCESS_MASK, # 0x0300 + OF_READONLY = GDAL.GDAL_OF_READONLY, # 0x00 + OF_UPDATE = GDAL.GDAL_OF_UPDATE, # 0x01 + # OF_ALL = GDAL.GDAL_OF_ALL, # 0x00 + OF_RASTER = GDAL.GDAL_OF_RASTER, # 0x02 + OF_VECTOR = GDAL.GDAL_OF_VECTOR, # 0x04 + OF_GNM = GDAL.GDAL_OF_GNM, # 0x08 + OF_MULTIDIM_RASTER = GDAL.GDAL_OF_MULTIDIM_RASTER, # 0x10 + OF_KIND_MASK = GDAL.GDAL_OF_KIND_MASK, # 0x1e + OF_SHARED = GDAL.GDAL_OF_SHARED, # 0x20 + OF_VERBOSE_ERROR = GDAL.GDAL_OF_VERBOSE_ERROR, # 0x40 + OF_INTERNAL = GDAL.GDAL_OF_INTERNAL, # 0x80 + # OF_DEFAULT_BLOCK_ACCESS = GDAL.GDAL_OF_DEFAULT_BLOCK_ACCESS, # 0 + OF_ARRAY_BLOCK_ACCESS = GDAL.GDAL_OF_ARRAY_BLOCK_ACCESS, # 0x0100 + OF_HASHSET_BLOCK_ACCESS = GDAL.GDAL_OF_HASHSET_BLOCK_ACCESS, # 0x0200 + # OF_RESERVED_1 = GDAL.GDAL_OF_RESERVED_1, # 0x0300 + OF_BLOCK_ACCESS_MASK = GDAL.GDAL_OF_BLOCK_ACCESS_MASK, # 0x0300 + # OF_FROM_GDALOPEN = GDAL.GDAL_OF_FROM_GDALOPEN, # 0x0400 ) @enum( FieldValidation, - F_VAL_NULL = GDAL.OGR_F_VAL_NULL, # 0x0001 - F_VAL_GEOM_TYPE = GDAL.OGR_F_VAL_GEOM_TYPE, # 0x0002 - F_VAL_WIDTH = GDAL.OGR_F_VAL_WIDTH, # 0x0004 - F_VAL_ALLOW_NULL_WHEN_DEFAULT = GDAL.OGR_F_VAL_ALLOW_NULL_WHEN_DEFAULT, # 0x0008 - F_VAL_ALLOW_DIFFERENT_GEOM_DIM = GDAL.OGR_F_VAL_ALLOW_DIFFERENT_GEOM_DIM, # 0x0010 + F_VAL_NULL = GDAL.OGR_F_VAL_NULL, # 0x0001 + F_VAL_GEOM_TYPE = GDAL.OGR_F_VAL_GEOM_TYPE, # 0x0002 + F_VAL_WIDTH = GDAL.OGR_F_VAL_WIDTH, # 0x0004 + F_VAL_ALLOW_NULL_WHEN_DEFAULT = GDAL.OGR_F_VAL_ALLOW_NULL_WHEN_DEFAULT, # 0x0008 + F_VAL_ALLOW_DIFFERENT_GEOM_DIM = GDAL.OGR_F_VAL_ALLOW_DIFFERENT_GEOM_DIM, # 0x0010 ) diff --git a/src/context.jl b/src/context.jl index 364193ea..98f9ed6f 100644 --- a/src/context.jl +++ b/src/context.jl @@ -182,37 +182,46 @@ function writegeomdefn( end for gdalfunc in ( + :asclassicdataset, + :asmdarray, :boundary, :buffer, :centroid, :clone, :convexhull, + :copy, :create, + :createRAT, + :createattribute, :createcolortable, :createcoordtrans, - :copy, + :createdimension, :createfeaturedefn, :createfielddefn, :creategeom, :creategeomcollection, - :creategeomfieldcollection, :creategeomdefn, + :creategeomfieldcollection, + :creategroup, :createlayer, :createlinearring, :createlinestring, + :createmdarray, + :createmultidimensional, :createmultilinestring, :createmultipoint, :createmultipolygon, :createmultipolygon_noholes, :createpoint, :createpolygon, - :createRAT, :createstylemanager, :createstyletable, :createstyletool, :curvegeom, :delaunaytriangulation, :difference, + :extendeddatatypecreate, + :extendeddatatypecreatestring, :forceto, :fromGML, :fromJSON, @@ -226,40 +235,64 @@ for gdalfunc in ( :gdaltranslate, :gdalvectortranslate, :gdalwarp, + :getattribute, + :getattributes, :getband, :getcolortable, + :getcomponents, + :getdatatype, + :getdimensions, :getfeature, :getgeom, + :getgridded, + :getindex, + :getindexingvariable, :getlayer, + :getmask, :getmaskband, :getoverview, :getpart, + :getresampled, + :getrootgroup, :getspatialref, + :gettype, + :getunscaled, + :getview, :importCRS, - :intersection, :importEPSG, :importEPSGA, :importESRI, :importPROJ4, + :importURL, + :importUserInput, :importWKT, :importXML, - :importUserInput, - :importURL, + :intersection, :lineargeom, :newspatialref, :nextfeature, + :open, + :opendimensionfromfullname, + :opengroup, + :opengroupfromfullname, + :openmdarray, + :openmdarrayfromfullname, + :openvectorlayer, :pointalongline, :pointonsurface, :polygonfromedges, :polygonize, :read, + :readraster, + :resolvemdarray, :sampleoverview, :simplify, :simplifypreservetopology, + :subsetdimensionfromselection, :symdifference, + :transpose, :union, :update, - :readraster, ) eval(quote function $(gdalfunc)(f::Function, args...; kwargs...) diff --git a/src/dataset.jl b/src/dataset.jl index ba2e0d0d..5a391112 100644 --- a/src/dataset.jl +++ b/src/dataset.jl @@ -1013,9 +1013,23 @@ function buildoverviews!( return dataset end -function destroy(dataset::AbstractDataset)::Nothing - GDAL.gdalclose(dataset) +# TODO: Wrap `GDAL.CPLErr` +function close(dataset::AbstractDataset)::GDAL.CPLErr + dataset.ptr == C_NULL && return GDAL.CE_Failure + if !isnothing(dataset.children) + for child in dataset.children + value = child.value + !isnothing(value) && destroy(value) + end + Base.empty!(dataset.children) + end + err = GDAL.gdalclose(dataset) dataset.ptr = C_NULL + return err +end + +function destroy(dataset::AbstractDataset)::Nothing + close(dataset) return nothing end diff --git a/src/mdarray/attribute.jl b/src/mdarray/attribute.jl new file mode 100644 index 00000000..25daac76 --- /dev/null +++ b/src/mdarray/attribute.jl @@ -0,0 +1,252 @@ +# GDALAttribute + +const NumericAttributeType = Union{ + Int8, + Int16, + Int32, + Int64, + UInt8, + UInt16, + UInt32, + UInt64, + Float32, + Float64, + Complex{Int16}, + Complex{Int32}, + Complex{Float32}, + Complex{Float64}, +} +const ScalarAttributeType = Union{AbstractString,NumericAttributeType} +const AttributeType = + Union{ScalarAttributeType,AbstractVector{<:ScalarAttributeType}} + +function getdimensionssize(attribute::AbstractAttribute)::NTuple{<:Any,Int} + @assert !isnull(attribute) + count = Ref{Csize_t}() + sizeptr = GDAL.gdalattributegetdimensionssize(attribute, count) + size = reverse(ntuple(d -> Int(unsafe_load(sizeptr, d), count[]))) + GDAL.vsifree(sizeptr) + return size +end + +function readasraw(attribute::AbstractAttribute)::AbstractVector{UInt8} + @assert !isnull(attribute) + count = Ref{Csize_t}() + rawptr = GDAL.gdalattributereadasraw(attribute, count) + raw = UInt8[unsafe_load(rawptr, n) for n in 1:count[]] + GDAL.gdalattributefreerawresult(rawptr, count[]) + return raw +end + +function read(attribute::AbstractAttribute)::AttributeType + @assert !isnull(attribute) + rank = getdimensioncount(attribute) + length = gettotalelementscount(attribute) + rank == 0 && @assert length == 1 + datatype = getdatatype(attribute) + class = getclass(datatype) + + if class == GDAL.GEDTC_NUMERIC + # Read a numeric attribute + T = convert(DataType, getnumericdatatype(datatype)) + @assert T <: NumericAttributeType + count = Ref{Csize_t}() + ptr = GDAL.gdalattributereadasraw(attribute, count) + @assert count[] == length * sizeof(T) + if rank == 0 + # Read a scalar + value = unsafe_load(convert(Ptr{T}, ptr)) + GDAL.gdalattributefreerawresult(attribute, ptr, count[]) + return value + else + # Read a vector + values = T[unsafe_load(convert(Ptr{T}, ptr), n) for n in 1:length] + GDAL.gdalattributefreerawresult(attribute, ptr, count[]) + return values + end + + elseif class == GDAL.GEDTC_STRING + # Read a string attribute + if rank == 0 + return GDAL.gdalattributereadasstring(attribute) + else + return GDAL.gdalattributereadasstringarray(attribute) + end + + elseif class == GDAL.GEDTC_COMPOUND + # Read a compound attribute + error("unimplemented") + else + error("internal error") + end +end + +function writerraw( + attribute::AbstractAttribute, + value::AbstractVector{UInt8}, +)::Bool + @assert !isnull(attribute) + return Bool(GDAL.gdalattributewriteraw(attribute, value, length(value))) +end + +function write(attribute::AbstractAttribute, value::AbstractString)::Bool + @assert !isnull(attribute) + return Bool(GDAL.gdalattributewritestring(attribute, value)) +end + +function write(attribute::AbstractAttribute, value::NumericAttributeType)::Bool + @assert !isnull(attribute) + rank = getdimensioncount(attribute) + @assert rank == 0 + length = gettotalelementscount(attribute) + @assert length == 1 + datatype = getdatatype(attribute) + class = getclass(datatype) + @assert class == GDAL.GEDTC_NUMERIC + T = convert(DataType, getnumericdatatype(datatype)) + + valueT = convert(T, value)::T + return Bool( + GDAL.gdalattributewriteraw(attribute, Ref(valueT), sizeof(valueT)), + ) +end + +function write( + attribute::AbstractAttribute, + values::AbstractVector{<:AbstractString}, +)::Bool + @assert !isnull(attribute) + return Bool( + GDAL.gdalattributewritestringarray( + attribute, + CSLConstListWrapper(values), + ), + ) +end + +function write( + attribute::AbstractAttribute, + values::AbstractVector{<:NumericAttributeType}, +)::Bool + @assert !isnull(attribute) + rank = getdimensioncount(attribute) + @assert rank == 1 + length = gettotalelementscount(attribute) + datatype = getdatatype(attribute) + class = getclass(datatype) + @assert class == GDAL.GEDTC_NUMERIC + T = convert(DataType, getnumericdatatype(datatype)) + + valuesT = convert(Vector{T}, values)::Vector{T} + return Bool(GDAL.gdalattributewriteraw(attribute, valuesT, sizeof(valuesT))) +end + +################################################################################ + +function getname(attribute::AbstractAttribute)::AbstractString + @assert !isnull(attribute) + return GDAL.gdalattributegetname(attribute) +end + +function getfullname(attribute::AbstractAttribute)::AbstractString + @assert !isnull(attribute) + return GDAL.gdalattributegetfullname(attribute) +end + +function gettotalelementscount(attribute::AbstractAttribute)::Int64 + @assert !isnull(attribute) + return Int64(GDAL.gdalattributegettotalelementscount(attribute)) +end + +function Base.length(attribute::AbstractAttribute)::Int + @assert !isnull(attribute) + return Int(gettotalelementscount(attribute)) +end + +function getdimensioncount(attribute::AbstractAttribute)::Int + @assert !isnull(attribute) + return Int(GDAL.gdalattributegetdimensioncount(attribute)) +end + +Base.ndims(attribute::AbstractAttribute)::Int = getdimensioncount(attribute) + +function getdimensions( + attribute::AbstractAttribute, +)::AbstractVector{<:AbstractDimension} + @assert !isnull(attribute) + dimensionscountref = Ref{Csize_t}() + dimensionshptr = + GDAL.gdalattributegetdimensions(attribute, dimensionscountref) + dimensions = AbstractDimension[ + IDimension(unsafe_load(dimensionshptr, d), attribute.dataset) for + d in dimensionscountref[]:-1:1 + ] + GDAL.vsifree(dimensionshptr) + return dimensions +end + +function unsafe_getdimensions( + attribute::AbstractAttribute, +)::AbstractVector{<:AbstractDimension} + @assert !isnull(attribute) + dimensionscountref = Ref{Csize_t}() + dimensionshptr = + GDAL.gdalattributegetdimensions(attribute, dimensionscountref) + dimensions = AbstractDimension[ + Dimension(unsafe_load(dimensionshptr, d), attribute.dataset) for + d in dimensionscountref[]:-1:1 + ] + GDAL.vsifree(dimensionshptr) + return dimensions +end + +function unsafe_getdatatype( + attribute::AbstractAttribute, +)::AbstractExtendedDataType + @assert !isnull(attribute) + return ExtendedDataType(GDAL.gdalattributegetdatatype(attribute)) +end + +function getdatatype(attribute::AbstractAttribute)::AbstractExtendedDataType + @assert !isnull(attribute) + return IExtendedDataType(GDAL.gdalattributegetdatatype(attribute)) +end + +function getblocksize( + attribute::AbstractAttribute, + options::OptionList = nothing, +)::NTuple{<:Any,Int} + @assert !isnull(attribute) + count = Ref{Csize_t}() + blocksizeptr = GDAL.gdalattributegetblocksize( + attribute, + count, + CSLConstListWrapper(options), + ) + blocksize = reverse(ntuple(d -> Int(unsafe_load(blocksizeptr, d)), count[])) + GDAL.vsifree(blocksizeptr) + return blocksize +end + +function getprocessingchunksize( + attribute::AbstractAttribute, + maxchunkmemory::Integer, +)::NTuple{<:AnyInt} + @assert !isnull(attribute) + count = Ref{Csize_t}() + chunksizeptr = GDAL.gdalattributegetprocessingchunksize( + attribute, + count, + maxchunkmemory, + ) + chunksize = reverse(ntuple(d -> Int(unsafe_load(chunksizeptr, d)), count[])) + GDAL.vsifree(chunksizeptr) + return chunksize +end + +# processperchunk + +function rename!(attribute::AbstractAttribute, newname::AbstractString)::Bool + @assert !isnull(attribute) + return GDAL.gdalattributerename(attribute, newname) +end diff --git a/src/mdarray/dimension.jl b/src/mdarray/dimension.jl new file mode 100644 index 00000000..5963c8a4 --- /dev/null +++ b/src/mdarray/dimension.jl @@ -0,0 +1,57 @@ +# GDALDimension + +function getname(dimension::AbstractDimension)::AbstractString + @assert !isnull(dimension) + return GDAL.gdaldimensiongetname(dimension) +end + +function getfullname(dimension::AbstractDimension)::AbstractString + @assert !isnull(dimension) + return GDAL.gdaldimensiongetfullname(dimension) +end + +function gettype(dimension::AbstractDimension)::AbstractString + @assert !isnull(dimension) + return GDAL.gdaldimensiongettype(dimension) +end + +function getdirection(dimension::AbstractDimension)::AbstractString + @assert !isnull(dimension) + return GDAL.gdaldimensiongetdirection(dimension) +end + +function getsize(dimension::AbstractDimension)::Int + @assert !isnull(dimension) + return Int(GDAL.gdaldimensiongetsize(dimension)) +end + +function unsafe_getindexingvariable( + dimension::AbstractDimension, +)::AbstractMDArray + @assert !isnull(dimension) + ptr = GDAL.gdaldimensiongetindexingvariable(dimension) + ptr == C_NULL && error("Could not get indexing variable for dimension") + return MDArray(ptr, dimension.dataset) +end + +function getindexingvariable(dimension::AbstractDimension)::AbstractMDArray + @assert !isnull(dimension) + ptr = GDAL.gdaldimensiongetindexingvariable(dimension) + ptr == C_NULL && error("Could not get indexing variable for dimension") + return IMDArray(ptr, dimension.dataset) +end + +function setindexingvariable!( + dimension::AbstractDimension, + indexingvariable::AbstractMDArray, +)::Nothing + @assert !isnull(dimension) + success = GDAL.gdaldimensionsetindexingvariable(dimension, indexingvariable) + success == 0 && error("Could not set indexing variable for dimension") + return nothing +end + +function rename!(dimension::AbstractDimension, newname::AbstractString)::Bool + @assert !isnull(dimension) + return GDAL.gdaldimensionrename(dimension, newname) +end diff --git a/src/mdarray/extendeddatatype.jl b/src/mdarray/extendeddatatype.jl new file mode 100644 index 00000000..4dcbafcc --- /dev/null +++ b/src/mdarray/extendeddatatype.jl @@ -0,0 +1,155 @@ +# GDALExtendedDataType + +function Base.:(==)( + firstedt::AbstractExtendedDataType, + secondedt::AbstractExtendedDataType, +)::Bool + @assert !isnull(firstedt) + @assert !isnull(secondedt) + return Bool(GDAL.gdalextendeddatatypeequals(firstedt, secondedt)) +end + +function getname(edt::AbstractExtendedDataType)::AbstractString + @assert !isnull(edt) + return GDAL.gdalextendeddatatypegetname(edt) +end + +# TODO: Wrap GDAL.GDALExtendedDataTypeClass +function getclass(edt::AbstractExtendedDataType)::GDAL.GDALExtendedDataTypeClass + @assert !isnull(edt) + return GDAL.gdalextendeddatatypegetclass(edt) +end + +function getnumericdatatype(edt::AbstractExtendedDataType)::GDALDataType + @assert !isnull(edt) + return convert( + GDALDataType, + GDAL.gdalextendeddatatypegetnumericdatatype(edt), + ) +end + +# TODO: Wrap GDAL.GDALExtendedDataTypeSubType +function getsubtype( + edt::AbstractExtendedDataType, +)::GDAL.GDALExtendedDataTypeSubType + @assert !isnull(edt) + return GDAL.gdalextendeddatatypegetsubtype(edt) +end + +function unsafe_getcomponents( + edt::AbstractExtendedDataType, +)::AbstractVector{<:AbstractEDTComponent} + @assert !isnull(edt) + count = Ref{Csize_t}() + ptr = GDAL.gdalextendeddatatypegetcomponents(edt, count) + components = AbstractEDTComponent[ + EDTComponent(unsafe_load(ptr, n)) for n in 1:count[] + ] + GDAL.vsifree(ptr) + return components +end + +function getcomponents( + edt::AbstractExtendedDataType, +)::AbstractVector{<:AbstractEDTComponent} + @assert !isnull(edt) + count = Ref{Csize_t}() + ptr = GDAL.gdalextendeddatatypegetcomponents(edt, count) + components = AbstractEDTComponent[ + IEDTComponent(unsafe_load(ptr, n)) for n in 1:count[] + ] + GDAL.vsifree(ptr) + return components +end + +function getsize(edt::AbstractExtendedDataType)::Int + @assert !isnull(edt) + return Int(GDAL.gdalextendeddatatypegetsize(edt)) +end + +function getmaxstringlength(edt::AbstractExtendedDataType)::Int + @assert !isnull(edt) + return Int(GDAL.gdalextendeddatatypegetmaxstringlength(edt)) +end + +function canconvertto( + sourceedt::AbstractExtendedDataType, + targetedt::AbstractExtendedDataType, +)::Bool + @assert !isnull(sourceedt) + @assert !isnull(targetedt) + return Bool(GDAL.gdalextendeddatatypecanconvertto(sourceedt, targetedt)) +end + +# TODO: automate this +function needsfreedynamicmemory(edt::AbstractExtendedDataType)::Bool + return Bool(GDAL.gdalextendeddatatypeneedsfreedynamicmemory(edt)) +end + +function freedynamicmemory( + edt::AbstractExtendedDataType, + buffer::Ptr{Cvoid}, +)::Nothing + GDAL.gdalextendeddatatypefreedynamicmemory(edt, buffer) + return nothing +end + +################################################################################ + +function unsafe_extendeddatatypecreate( + ::Type{T}, +)::AbstractExtendedDataType where {T} + type = convert(GDALDataType, T) + return ExtendedDataType(GDAL.gdalextendeddatatypecreate(type)) +end + +function extendeddatatypecreate(::Type{T})::AbstractExtendedDataType where {T} + type = convert(GDALDataType, T) + return IExtendedDataType(GDAL.gdalextendeddatatypecreate(type)) +end + +# TODO: Wrap GDAL.GDALExtendedDataTypeSubType +function unsafe_extendeddatatypecreatestring( + maxstringlength::Integer = 0, + subtype::GDAL.GDALExtendedDataTypeSubType = GDAL.GEDTST_NONE, +)::AbstractExtendedDataType + return ExtendedDataType( + GDAL.gdalextendeddatatypecreatestringex(maxstringlength, subtype), + ) +end + +function extendeddatatypecreatestring( + maxstringlength::Integer = 0, + subtype::GDAL.GDALExtendedDataTypeSubType = GDAL.GEDTST_NONE, +)::AbstractExtendedDataType + return IExtendedDataType( + GDAL.gdalextendeddatatypecreatestringex(maxstringlength, subtype), + ) +end + +# copyvalue +# copyvalues + +################################################################################ + +# GDLEDTComponent + +function getname(comp::AbstractEDTComponent)::AbstractString + @assert !isnull(comp) + return GDAL.gdaledtcomponenttgetname(comp) +end + +function getoffset(comp::AbstractEDTComponent)::Int + @assert !isnull(comp) + return Int(GDAL.gdaledtcomponenttgetoffset(comp)) +end + +function unsafe_gettype(comp::AbstractEDTComponent)::AbstractExtendedDataType + @assert !isnull(comp) + return ExtendedDatatType(GDAL.gdaledtcomponenttgettype(comp)) +end + +function gettype(comp::AbstractEDTComponent)::AbstractExtendedDataType + @assert !isnull(comp) + return IExtendedDatatType(GDAL.gdaledtcomponenttgettype(comp)) +end diff --git a/src/mdarray/global.jl b/src/mdarray/global.jl new file mode 100644 index 00000000..e950bb7f --- /dev/null +++ b/src/mdarray/global.jl @@ -0,0 +1,109 @@ +# Global functions + +function unsafe_createmultidimensional( + driver::Driver, + name::AbstractString, + rootgroupoptions::OptionList = nothing, + options::OptionList = nothing, + ; + hard_close::Bool = true, +)::AbstractDataset + @assert !isnull(driver) + return Dataset( + GDAL.gdalcreatemultidimensional( + driver, + name, + CSLConstListWrapper(rootgroupoptions), + CSLConstListWrapper(options), + ), + hard_close = hard_close, + ) +end + +function createmultidimensional( + driver::Driver, + name::AbstractString, + rootgroupoptions::OptionList = nothing, + options::OptionList = nothing, + ; + hard_close::Bool = true, +)::AbstractDataset + @assert !isnull(driver) + return IDataset( + GDAL.gdalcreatemultidimensional( + driver, + name, + CSLConstListWrapper(rootgroupoptions), + CSLConstListWrapper(options), + ), + hard_close = hard_close, + ) +end + +function unsafe_open( + filename::AbstractString, + openflags::Integer, + alloweddrivers::OptionList, + openoptions::OptionList, + siblingfiles::OptionList, + hard_close::Union{Nothing,Bool} = nothing, +)::AbstractDataset + if isnothing(hard_close) + # We hard-close the dataset if it is a writable multidim dataset + hard_close = + (openflags & OF_MULTIDIM_RASTER != 0) && + (openflags & OF_UPDATE != 0) + end + return Dataset( + GDAL.gdalopenex( + filename, + openflags, + CSLConstListWrapper(alloweddrivers), + CSLConstListWrapper(openoptions), + CSLConstListWrapper(siblingfiles), + ), + hard_close = hard_close, + ) +end + +function open( + filename::AbstractString, + openflags::Integer, + alloweddrivers::OptionList, + openoptions::OptionList, + siblingfiles::OptionList, + hard_close::Union{Nothing,Bool} = nothing, +)::AbstractDataset + if isnothing(hard_close) + # We hard-close the dataset if it is a writable multidim dataset + hard_close = + (openflags & OF_MULTIDIM_RASTER != 0) && + (openflags & OF_UPDATE != 0) + end + return IDataset( + GDAL.gdalopenex( + filename, + openflags, + CSLConstListWrapper(alloweddrivers), + CSLConstListWrapper(openoptions), + CSLConstListWrapper(siblingfiles), + ), + hard_close = hard_close, + ) +end + +# TODO: Wrap `GDAL.CPLErr` +function flushcache!(dataset::AbstractDataset)::GDAL.CPLErr + @assert !isnull(dataset) + return GDAL.gdalflushcache(dataset) +end + +function unsafe_getrootgroup(dataset::AbstractDataset)::AbstractGroup + @assert !isnull(dataset) + return Group(GDAL.gdaldatasetgetrootgroup(dataset), WeakRef(dataset)) +end + +function getrootgroup(dataset::AbstractDataset)::AbstractGroup + @assert !isnull(dataset) + return Group(GDAL.gdaldatasetgetrootgroup(dataset), WeakRef(dataset)) +end diff --git a/src/mdarray/group.jl b/src/mdarray/group.jl new file mode 100644 index 00000000..3db9ebd0 --- /dev/null +++ b/src/mdarray/group.jl @@ -0,0 +1,550 @@ +# GDALGroup + +function getname(group::AbstractGroup)::AbstractString + @assert !isnull(group) + return GDAL.gdalgroupgetname(group) +end + +function getfullname(group::AbstractGroup)::AbstractString + @assert !isnull(group) + return GDAL.gdalgroupgetfullname(group) +end + +function getmdarraynames( + group::AbstractGroup, + options::OptionList = nothing, +)::AbstractVector{<:AbstractString} + @assert !isnull(group) + return GDAL.gdalgroupgetmdarraynames(group, CSLConstListWrapper(options)) +end + +function unsafe_openmdarray( + group::AbstractGroup, + name::AbstractString, + options::OptionList = nothing, +)::AbstractMDArray + @assert !isnull(group) + ptr = GDAL.gdalgroupopenmdarray(group, name, CSLConstListWrapper(options)) + ptr == C_NULL && error("Could not open mdarray \"$name\"") + return MDArray(ptr, group.dataset) +end + +function openmdarray( + group::AbstractGroup, + name::AbstractString, + options::OptionList = nothing, +)::AbstractMDArray + @assert !isnull(group) + ptr = GDAL.gdalgroupopenmdarray(group, name, CSLConstListWrapper(options)) + ptr == C_NULL && error("Could not open mdarray \"$name\"") + return IMDArray(ptr, group.dataset) +end + +function getgroupnames( + group::AbstractGroup, + options::OptionList = nothing, +)::AbstractVector{<:AbstractString} + @assert !isnull(group) + return GDAL.gdalgroupgetgroupnames(group, CSLConstListWrapper(options)) +end + +function unsafe_opengroup( + group::AbstractGroup, + name::AbstractString, + options::OptionList = nothing, +)::AbstractGroup + @assert !isnull(group) + ptr = GDAL.gdalgroupopengroup(group, name, CSLConstListWrapper(options)) + ptr == C_NULL && error("Could no open group \"$name\"") + return Group(ptr, group.dataset) +end + +function opengroup( + group::AbstractGroup, + name::AbstractString, + options::OptionList = nothing, +)::AbstractGroup + @assert !isnull(group) + ptr = GDAL.gdalgroupopengroup(group, name, CSLConstListWrapper(options)) + ptr == C_NULL && error("Could no open group \"$name\"") + return IGroup(ptr, group.dataset) +end + +function getvectorlayernames( + group::AbstractGroup, + options::OptionList = nothing, +)::AbstractVector{<:AbstractString} + @assert !isnull(group) + return GDAL.gdalgroupgetvectorlayernames( + group, + CSLConstListWrapper(options), + ) +end + +function unsafe_openvectorlayer( + group::AbstractGroup, + options::OptionList = nothing, +)::AbstractFeatureLayer + @assert !isnull(group) + # TODO: Find out how to set `ownedby` and `spatialref`, probably by querying `group` + # TODO: Store dataset + return FeatureLayer( + GDAL.openvectorlayer(group, CSLConstListWrapper(options)), + ownedby, + spatialref, + ) +end + +function openvectorlayer( + group::AbstractGroup, + options::OptionList = nothing, +)::AbstractFeatureLayer + @assert !isnull(group) + # TODO: Find out how to set `ownedby` and `spatialref`, probably by querying `group` + # TODO: Store dataset + return IFeatureLayer( + GDAL.openvectorlayer(group, CSLConstListWrapper(options)), + ownedby, + spatialref, + ) +end + +function unsafe_getdimensions( + group::AbstractGroup, + options::OptionList = nothing, +)::AbstractVector{<:AbstractDimension} + @assert !isnull(group) + dimensionscountref = Ref{Csize_t}() + dimensionshptr = GDAL.gdalgroupgetdimensions( + group, + dimensionscountref, + CSLConstListWrapper(options), + ) + dimensions = AbstractDimension[ + Dimension(unsafe_load(dimensionshptr, n), group.dataset) for + n in 1:dimensionscountref[] + ] + GDAL.vsifree(dimensionshptr) + return dimensions +end + +function getdimensions( + group::AbstractGroup, + options::OptionList = nothing, +)::AbstractVector{<:AbstractDimension} + @assert !isnull(group) + dimensionscountref = Ref{Csize_t}() + dimensionshptr = GDAL.gdalgroupgetdimensions( + group, + dimensionscountref, + CSLConstListWrapper(options), + ) + dimensions = AbstractDimension[ + IDimension(unsafe_load(dimensionshptr, n), group.dataset) for + n in 1:dimensionscountref[] + ] + GDAL.vsifree(dimensionshptr) + return dimensions +end + +function unsafe_creategroup( + group::AbstractGroup, + name::AbstractString, + options::OptionList = nothing, +)::AbstractGroup + @assert !isnull(group) + ptr = GDAL.gdalgroupcreategroup(group, name, CSLConstListWrapper(options)) + ptr == C_NULL && error("Could not create group \"$name\"") + return Group(ptr, group.dataset) +end + +function creategroup( + group::AbstractGroup, + name::AbstractString, + options::OptionList = nothing, +)::AbstractGroup + @assert !isnull(group) + ptr = GDAL.gdalgroupcreategroup(group, name, CSLConstListWrapper(options)) + ptr == C_NULL && error("Could not create group \"$name\"") + return IGroup(ptr, group.dataset) +end + +function deletegroup( + group::AbstractGroup, + name::AbstractString, + options::OptionList = nothing, +)::Bool + @assert !isnull(group) + return GDAL.gdalgroupdeletegroup(group, name, CSLConstListWrapper(options)) +end + +function unsafe_createdimension( + group::AbstractGroup, + name::AbstractString, + type::AbstractString, + direction::AbstractString, + size::Integer, + options::OptionList = nothing, +)::AbstractDimension + @assert !isnull(group) + ptr = GDAL.gdalgroupcreatedimension( + group, + name, + type, + direction, + size, + CSLConstListWrapper(options), + ) + ptr == C_NULL && error("Could not create dimension \"$name\"") + return Dimension(ptr, group.dataset) +end + +function createdimension( + group::AbstractGroup, + name::AbstractString, + type::AbstractString, + direction::AbstractString, + size::Integer, + options::OptionList = nothing, +)::AbstractDimension + @assert !isnull(group) + ptr = GDAL.gdalgroupcreatedimension( + group, + name, + type, + direction, + size, + CSLConstListWrapper(options), + ) + ptr == C_NULL && error("Could not create dimension \"$name\"") + return IDimension(ptr, group.dataset) +end + +function unsafe_createmdarray( + group::AbstractGroup, + name::AbstractString, + dimensions::VectorLike{<:AbstractDimension}, + datatype::AbstractExtendedDataType, + options::OptionList = nothing, +)::AbstractMDArray + @assert !isnull(group) + @assert all(!isnull(dim) for dim in dimensions) + @assert !isnull(datatype) + ptr = GDAL.gdalgroupcreatemdarray( + group, + name, + length(dimensions), + DimensionHList(reverse(dimensions)), + datatype, + CSLConstListWrapper(options), + ) + ptr == C_NULL && error("Could not create mdarray \"$name\"") + return MDArray(ptr, group.dataset) +end + +function createmdarray( + group::AbstractGroup, + name::AbstractString, + dimensions::VectorLike{<:AbstractDimension}, + datatype::AbstractExtendedDataType, + options::OptionList = nothing, +)::AbstractMDArray + @assert !isnull(group) + @assert all(!isnull(dim) for dim in dimensions) + @assert !isnull(datatype) + ptr = GDAL.gdalgroupcreatemdarray( + group, + name, + length(dimensions), + DimensionHList(reverse(dimensions)), + datatype, + CSLConstListWrapper(options), + ) + ptr == C_NULL && error("Could not create mdarray \"$name\"") + return IMDArray(ptr, group.dataset) +end + +function deletemdarray( + group::AbstractGroup, + name::AbstractString, + options::OptionList = nothing, +)::Bool + @assert !isnull(group) + return GDAL.gdalgroupdeletemdarray( + group, + name, + CSLConstListWrapper(options), + ) +end + +# gettotalcopycost +# copyfrom + +function getstructuralinfo( + group::AbstractGroup, +)::AbstractVector{<:AbstractString} + @assert !isnull(group) + return GDAL.gdalgroupgetstructuralinfo(group) +end + +function unsafe_openmdarrayfromfullname( + group::AbstractGroup, + fullname::AbstractString, + options::OptionList = nothing, +)::AbstractMDArray + @assert !isnull(group) + ptr = GDAL.gdalgroupopenmdarrayfromfullname( + group, + fullname, + CSLConstListWrapper(options), + ) + ptr == C_NULL && error("Could not open mdarray \"$fullname\"") + return MDArray(ptr, group.dataset) +end + +function openmdarrayfromfullname( + group::AbstractGroup, + fullname::AbstractString, + options::OptionList = nothing, +)::AbstractMDArray + @assert !isnull(group) + ptr = GDAL.gdalgroupopenmdarrayfromfullname( + group, + fullname, + CSLConstListWrapper(options), + ) + ptr == C_NULL && error("Could not open mdarray \"$fullname\"") + return IMDArray(ptr, group.dataset) +end + +function unsafe_resolvemdarray( + group::AbstractGroup, + name::AbstractString, + startingpath::AbstractString, + options::OptionList = nothing, +)::AbstractMDArray + @assert !isnull(group) + ptr = GDAL.gdalgroupresolvemdarray( + group, + name, + startingpath, + CSLConstListWrapper(options), + ) + ptr == C_NULL && error("Could not resolve mdarray \"$name\"") + return MDArray(ptr, group.dataset) +end + +function resolvemdarray( + group::AbstractGroup, + name::AbstractString, + startingpath::AbstractString, + options::OptionList = nothing, +)::AbstractMDArray + @assert !isnull(group) + ptr = GDAL.gdalgroupresolvemdarray( + group, + name, + startingpath, + CSLConstListWrapper(options), + ) + ptr == C_NULL && error("Could not resolve mdarray \"$name\"") + return IMDArray(ptr, group.dataset) +end + +function unsafe_opengroupfromfullname( + group::AbstractGroup, + fullname::AbstractString, + options::OptionList = nothing, +)::AbstractGroup + @assert !isnull(group) + ptr = GDAL.gdalgroupopengroupfromfullname( + group, + fullname, + CSLConstListWrapper(options), + ) + ptr == C_NULL && error("Could not open group \"$fullname\"") + return Group(ptr, group.dataset) +end + +function opengroupfromfullname( + group::AbstractGroup, + fullname::AbstractString, + options::OptionList = nothing, +)::AbstractGroup + @assert !isnull(group) + ptr = GDAL.gdalgroupopengroupfromfullname( + group, + fullname, + CSLConstListWrapper(options), + ) + ptr == C_NULL && error("Could not open group \"$fullname\"") + return IGroup(ptr, group.dataset) +end + +# function unsafe_opendimensionfromfullname( +# group::AbstractGroup, +# fullname::AbstractString, +# options::OptionList=nothing, +# )::AbstractDimension +# @assert !isnull(group) +# return Dimension( +# GDAL.gdalgroupopendimensionfromfullname(group, fullname, CSLConstListWrapper(options)), group.dataset +# ) +# end +# +# function opendimensionfromfullname( +# group::AbstractGroup, +# fullname::AbstractString, +# options::OptionList=nothing, +# )::AbstractDimension +# @assert !isnull(group) +# return IDimension( +# GDAL.gdalgroupopendimensionfromfullname(group, fullname, CSLConstListWrapper(options)), group.dataset +# ) +# end + +# clearstatistics + +function rename(group::AbstractGroup, newname::AbstractString)::Bool + @assert !isnull(group) + return GDAL.gdalgrouprename(group, newname) +end + +function unsafe_subsetdimensionfromselection( + group::AbstractGroup, + selection::AbstractString, + options::OptionList = nothing, +)::AbstractGroup + @assert !isnull(group) + return Group( + GDAL.gdalgroupsubsetdimensionfromselection( + group, + selection, + CSLConstListWrapper(options), + ), + group.dataset, + ) +end + +function subsetdimensionfromselection( + group::AbstractGroup, + selection::AbstractString, + options::OptionList = nothing, +)::AbstractGroup + @assert !isnull(group) + return IGroup( + GDAL.gdalgroupsubsetdimensionfromselection( + group, + selection, + CSLConstListWrapper(options), + ), + group.dataset, + ) +end + +################################################################################ + +function unsafe_getattribute( + group::AbstractGroup, + name::AbstractString, +)::AbstractAttribute + @assert !isnull(group) + ptr = GDAL.gdalgroupgetattribute(group, name) + ptr == C_NULL && error("Could not open attribute \"$name\"") + return Attribute(ptr, group.dataset) +end + +function getattribute( + group::AbstractGroup, + name::AbstractString, +)::AbstractAttribute + @assert !isnull(group) + ptr = GDAL.gdalgroupgetattribute(group, name) + ptr == C_NULL && error("Could not open attribute \"$name\"") + return IAttribute(ptr, group.dataset) +end + +function unsafe_getattributes( + group::AbstractGroup, + options::OptionList = nothing, +)::AbstractVector{<:AbstractAttribute} + @assert !isnull(group) + count = Ref{Csize_t}() + ptr = + GDAL.gdalgroupgetattributes(group, count, CSLConstListWrapper(options)) + attributes = AbstractAttribute[ + Attribute(unsafe_load(ptr, n), group.dataset) for n in 1:count[] + ] + GDAL.vsifree(ptr) + return attributes +end + +function getattributes( + group::AbstractGroup, + options::OptionList = nothing, +)::AbstractVector{<:AbstractAttribute} + @assert !isnull(group) + count = Ref{Csize_t}() + ptr = + GDAL.gdalgroupgetattributes(group, count, CSLConstListWrapper(options)) + attributes = AbstractAttribute[ + IAttribute(unsafe_load(ptr, n), group.dataset) for n in 1:count[] + ] + GDAL.vsifree(ptr) + return attributes +end + +function unsafe_createattribute( + group::AbstractGroup, + name::AbstractString, + dimensions::VectorLike{<:Integer}, + datatype::AbstractExtendedDataType, + options::OptionList = nothing, +)::AbstractAttribute + @assert !isnull(group) + @assert !isnull(datatype) + ptr = GDAL.gdalgroupcreateattribute( + group, + name, + length(dimensions), + reverse(dimensions), + datatype, + CSLConstListWrapper(options), + ) + ptr == C_NULL && error("Could not create attribute \"$name\"") + return Attribute(ptr, group.dataset) +end + +function createattribute( + group::AbstractGroup, + name::AbstractString, + dimensions::VectorLike{<:Integer}, + datatype::AbstractExtendedDataType, + options::OptionList = nothing, +)::AbstractAttribute + @assert !isnull(group) + @assert !isnull(datatype) + ptr = GDAL.gdalgroupcreateattribute( + group, + name, + length(dimensions), + reverse(dimensions), + datatype, + CSLConstListWrapper(options), + ) + ptr == C_NULL && error("Could not create attribute \"$name\"") + return IAttribute(ptr, group.dataset) +end + +function deleteattribute( + group::AbstractGroup, + name::AbstractString, + options::OptionList = nothing, +)::Bool + @assert !isnull(group) + return GDAL.gdalgroupdeleteattribute( + group, + name, + CSLConstListWrapper(options), + ) +end diff --git a/src/mdarray/highlevel.jl b/src/mdarray/highlevel.jl new file mode 100644 index 00000000..55ae1869 --- /dev/null +++ b/src/mdarray/highlevel.jl @@ -0,0 +1,99 @@ +# High-level functions + +function writemdarray( + group::AbstractGroup, + name::AbstractString, + value::StridedArray{T,D}, + options::OptionList = nothing, +)::Nothing where {T<:NumericAttributeType,D} + dimensions = AbstractDimension[ + createdimension(group, "$name.$d", "", "", size(value, d)) for d in 1:D + ] + extendeddatatypecreate(T) do datatype + createmdarray(group, name, dimensions, datatype, options) do mdarray + write(mdarray, value) + return nothing + end + end +end + +function readmdarray( + group::AbstractGroup, + name::AbstractString, + options::OptionList = nothing, +)::AbstractArray + openmdarray(group, name, options) do mdarray + return read(mdarray) + end +end + +function writeattribute( + group::Union{AbstractGroup,AbstractMDArray}, + name::AbstractString, + value::AbstractString, +)::Nothing + extendeddatatypecreatestring(length(value)) do datatype + createattribute(group, name, UInt64[], datatype) do attribute + write(attribute, value) + return nothing + end + end +end + +function writeattribute( + group::Union{AbstractGroup,AbstractMDArray}, + name::AbstractString, + value::NumericAttributeType, +)::Nothing + extendeddatatypecreate(typeof(value)) do datatype + createattribute(group, name, UInt64[], datatype) do attribute + write(attribute, value) + return nothing + end + end +end + +function writeattribute( + group::Union{AbstractGroup,AbstractMDArray}, + name::AbstractString, + values::AbstractVector{<:AbstractString}, +)::Nothing + extendeddatatypecreatestring(0) do datatype + createattribute( + group, + name, + UInt64[length(values)], + datatype, + ) do attribute + write(attribute, values) + return nothing + end + end +end + +function writeattribute( + group::Union{AbstractGroup,AbstractMDArray}, + name::AbstractString, + values::AbstractVector{<:NumericAttributeType}, +)::Nothing + extendeddatatypecreate(eltype(values)) do datatype + createattribute( + group, + name, + UInt64[length(values)], + datatype, + ) do attribute + write(attribute, values) + return nothing + end + end +end + +function readattribute( + group::Union{AbstractGroup,AbstractMDArray}, + name::AbstractString, +)::AttributeType + getattribute(group, name) do attribute + return read(attribute) + end +end diff --git a/src/mdarray/mdarray.jl b/src/mdarray/mdarray.jl new file mode 100644 index 00000000..ffee5447 --- /dev/null +++ b/src/mdarray/mdarray.jl @@ -0,0 +1,1034 @@ +# GDALMDArray + +function MDArray(ptr::GDAL.GDALMDArrayH, dataset::WeakRef) + @assert ptr != C_NULL + datatype = IExtendedDataType(GDAL.gdalmdarraygetdatatype(ptr)) + class = getclass(datatype) + @assert class == GDAL.GEDTC_NUMERIC + T = convert(DataType, getnumericdatatype(datatype)) + D = Int(GDAL.gdalmdarraygetdimensioncount(ptr)) + return MDArray{T,D}(ptr, dataset) +end + +function IMDArray(ptr::GDAL.GDALMDArrayH, dataset::WeakRef) + @assert ptr != C_NULL + datatype = IExtendedDataType(GDAL.gdalmdarraygetdatatype(ptr)) + class = getclass(datatype) + @assert class == GDAL.GEDTC_NUMERIC + T = convert(DataType, getnumericdatatype(datatype)) + D = Int(GDAL.gdalmdarraygetdimensioncount(ptr)) + return IMDArray{T,D}(ptr, dataset) +end + +# function iswritable(mdarray::AbstractMDArray)::Bool +# return GDAL.gdalmdarrayiswritable(mdarray) +# end +# +# Base.iswritable(mdarray::AbstractMDArray)::Bool = iswritable(mdarray) +# Base.isreadonly(mdarray::AbstractMDArray)::Bool = !iswritable(mdarray) + +function getfilename(mdarray::AbstractMDArray)::AbstractString + @assert !isnull(mdarray) + return GDAL.gdalmdarraygetfilename(mdarray) +end + +function getstructuralinfo( + mdarray::AbstractMDArray, +)::AbstractVector{<:AbstractString} + @assert !isnull(mdarray) + return GDAL.gdalmdarraygetstructuralinfo(mdarray) +end + +function getunit(mdarray::AbstractMDArray)::AbstractString + @assert !isnull(mdarray) + return GDAL.gdalmdarraygetunit(mdarray) +end + +function setunit!(mdarray::AbstractMDArray, unit::AbstractString)::Bool + @assert !isnull(mdarray) + return GDAL.gdalmdarraysetunit(mdarray, unit) +end + +function setspatialref!(mdarray::AbstractMDArray, srs::AbstractSpatialRef)::Bool + @assert !isnull(mdarray) + return GDAL.gdalmdarraysetspatialref(mdarray, srs) +end + +function unsafe_getspatialref(mdarray::AbstractMDArray)::AbstractSpatialRef + @assert !isnull(mdarray) + return SpatialRef(GDAL.gdalmdarraygetspatialref(mdarray)) +end + +function getspatialref(mdarray::AbstractMDArray)::AbstractSpatialRef + @assert !isnull(mdarray) + return ISpatialRef(GDAL.gdalmdarraygetspatialref(mdarray)) +end + +function getrawnodatavalue(mdarray::AbstractMDArray)::Ptr{Cvoid} + @assert !isnull(mdarray) + return GDAL.gdalmdarraygetrawnodatavalue(mdarray) +end + +function getrawnodatavalueasdouble( + mdarray::AbstractMDArray, +)::Union{Nothing,Float64} + @assert !isnull(mdarray) + hasnodata = Ref{Cbool}() + nodatavalue = GDAL.gdalmdarraygetnodatavalueasdouble(mdarray, hasnodata) + return hasnodata[] ? nodatavalue : nothing +end + +function getrawnodatavalueasint64( + mdarray::AbstractMDArray, +)::Union{Nothing,Int64} + @assert !isnull(mdarray) + hasnodata = Ref{Cbool}() + nodatavalue = GDAL.gdalmdarraygetnodatavalueasint64(mdarray, hasnodata) + return hasnodata[] ? nodatavalue : nothing +end + +function getrawnodatavalueasuint64( + mdarray::AbstractMDArray, +)::Union{Nothing,UInt64} + @assert !isnull(mdarray) + hasnodata = Ref{Cbool}() + nodatavalue = GDAL.gdalmdarraygetnodatavalueasuint64(mdarray, hasnodata) + return hasnodata[] ? nodatavalue : nothing +end + +function getnodatavalue(::Type{Float64}, mdarray::AbstractMDArray) + @assert !isnull(mdarray) + return getrawnodatavalueasdouble(mdarray) +end +function getnodatavalue(::Type{Int64}, mdarray::AbstractMDArray) + @assert !isnull(mdarray) + return getrawnodatavalueasint64(mdarray) +end +function getnodatavalue(::Type{UInt64}, mdarray::AbstractMDArray) + @assert !isnull(mdarray) + return getrawnodatavalueasuint64(mdarray) +end + +function setrawnodatavalue!( + mdarray::AbstractMDArray, + rawnodata::Ptr{Cvoid}, +)::Bool + @assert !isnull(mdarray) + return GDAL.gdalmdarraysetrawnodatavalue(mdarray, rawnodata) +end + +function setnodatavalue!(mdarray::AbstractMDArray, nodata::Float64)::Bool + @assert !isnull(mdarray) + return GDAL.gdalmdarraysetnodatavalueasdouble(mdarray, nodata) +end + +function setnodatavalue!(mdarray::AbstractMDArray, nodata::Int64)::Bool + @assert !isnull(mdarray) + return GDAL.gdalmdarraysetnodatavalueasint64(mdarray, nodata) +end + +function setnodatavalue!(mdarray::AbstractMDArray, nodata::UInt64)::Bool + @assert !isnull(mdarray) + return GDAL.gdalmdarraysetnodatavalueasuint64(mdarray, nodata) +end + +function resize!( + mdarray::AbstractMDArray, + newdimsizes::VectorLike{<:Integer}, + options::OptionList = nothing, +)::Bool + @assert !isnull(mdarray) + return GDAL.gdalmdarrayresize( + mdarray, + newdimsizes, + CSLConstListWrapper(options), + ) +end + +function getoffset(mdarray::AbstractMDArray)::Union{Nothing,Float64} + @assert !isnull(mdarray) + hasoffset = Ref{Cbool}() + offset = GDAL.gdalmdarraygetoffset(mdarray, hasoffset) + return hasoffset[] ? offset : nothing +end + +function getoffsetex( + mdarray::AbstractMDArray, +)::Union{Nothing,Tuple{Float64,Type}} + @assert !isnull(mdarray) + hasoffset = Ref{Cbool}() + storagetyperef = Ref{GDAL.GDALDataType}() + offset = GDAL.gdalmdarraygetoffsetex(mdarray, hasoffset, storagetyperef) + !hasoffset[] && return nothing + storagetype = convert(Type, storagetyperef[]) + return offset, storagetype +end + +function getscale(mdarray::AbstractMDArray)::Union{Nothing,Float64} + @assert !isnull(mdarray) + hasscale = Ref{Cbool}() + scale = GDAL.gdalmdarraygetscale(mdarray, hasscale) + return hasscale[] ? scale : nothing +end + +function getscaleex( + mdarray::AbstractMDArray, +)::Union{Nothing,Tuple{Float64,Type}} + @assert !isnull(mdarray) + hasscale = Ref{Cbool}() + storagetyperef = Ref{GDAL.GDALDataType}() + scale = GDAL.gdalmdarraygetscaleex(mdarray, hasscale, storagetyperef) + !hasscale[] && return nothing + storagetype = convert(Type, storagetyperef[]) + return scale, storagetype +end + +function setoffset!( + mdarray::AbstractMDArray, + offset::Float64, + storagetype::Union{Type,Nothing}, +)::Bool + @assert !isnull(mdarray) + return GDAL.gdalmdarraysetoffset( + mdarray, + offset, + isnothing(storagetype) ? GDAL.GDT_Unknown : + convert(GDAL.GDALDataType, storagetype), + ) +end + +function setscale!( + mdarray::AbstractMDArray, + offset::Float64, + storagetype::Union{Type,Nothing}, +)::Bool + @assert !isnull(mdarray) + return GDAL.gdalmdarraysetscale( + mdarray, + offset, + isnothing(storagetype) ? GDAL.GDT_Unknown : + convert(GDAL.GDALDataType, storagetype), + ) +end + +function unsafe_getview( + mdarray::AbstractMDArray, + viewexpr::AbstractString, +)::AbstractMDArray + @assert !isnull(mdarray) + ptr = GDAL.gdalmdarraygetview(mdarray, viewexpr) + ptr == C_NULL && error("Could not get view \"$vierexpr\"") + return MDArray(ptr, mdarray.dataset) +end + +function getview( + mdarray::AbstractMDArray, + viewexpr::AbstractString, +)::AbstractMDArray + @assert !isnull(mdarray) + ptr = GDAL.gdalmdarraygetview(mdarray, viewexpr) + ptr == C_NULL && error("Could not get view \"$vierexpr\"") + return IMDArray(ptr, mdarray.dataset) +end + +function unsafe_getindex( + mdarray::AbstractMDArray, + fieldname::AbstractString, +)::AbstractMDArray + @assert !isnull(mdarray) + viewexpr = "['" * replace(fieldname, '\\' => "\\\\", '\'' => "\\\'") * "']" + return unsafe_getview(mdarray, viewexpr) +end + +function getindex( + mdarray::AbstractMDArray, + fieldname::AbstractString, +)::AbstractMDArray + @assert !isnull(mdarray) + viewexpr = "['" * replace(fieldname, '\\' => "\\\\", '\'' => "\\\'") * "']" + return getview(mdarray, viewexpr) +end + +function unsafe_getindex( + mdarray::AbstractMDArray, + indices::Integer..., +)::AbstractMDArray + @assert !isnull(mdarray) + viewexpr = "[" * join(reverse(indices), ",") * "]" + return unsafe_getview(mdarray, viewexpr) +end + +function getindex( + mdarray::AbstractMDArray, + indices::Integer..., +)::AbstractMDArray + @assert !isnull(mdarray) + viewexpr = "[" * join(reverse(indices), ",") * "]" + return getview(mdarray, viewexpr) +end + +# TODO: Return a `LinearAlgebra.Adjoint` instead? +function unsafe_transpose(mdarray::AbstractMDArray)::AbstractMDArray + @assert !isnull(mdarray) + ptr = GDAL.gdalmdarraytranspose(mdarray) + ptr == C_NULL && error("Could not transpose mdarray") + return MDArray(ptr, mdarray.dataset) +end + +function transpose(mdarray::AbstractMDArray)::AbstractMDArray + @assert !isnull(mdarray) + ptr = GDAL.gdalmdarraytranspose(mdarray) + ptr == C_NULL && error("Could not transpose mdarray") + return IMDArray(ptr, mdarray.dataset) +end + +function unsafe_getunscaled( + mdarray::AbstractMDArray, + overriddenscale = Float64(NaN), + overriddenoffset = Float64(NaN), + overriddendstnodata = Float64(NaN), +)::AbstractMDArray + @assert !isnull(mdarray) + ptr = GDAL.gdalmdarraygetunscaled( + mdarray, + overriddenscale, + overriddenoffset, + overriddendstnodata, + ) + ptr == C_NULL && error("Could not get unscaled mdarray") + return MDArray(ptr, mdarray.dataset) +end + +function getunscaled( + mdarray::AbstractMDArray, + overriddenscale = Float64(NaN), + overriddenoffset = Float64(NaN), + overriddendstnodata = Float64(NaN), +)::AbstractMDArray + @assert !isnull(mdarray) + ptr = GDAL.gdalmdarraygetunscaled( + mdarray, + overriddenscale, + overriddenoffset, + overriddendstnodata, + ) + ptr == C_NULL && error("Could not get unscaled mdarray") + return IMDArray(ptr, mdarray.dataset) +end + +function unsafe_getmask( + mdarray::AbstractMDArray, + options::OptionList = nothing, +)::AbstractMDArray + @assert !isnull(mdarray) + ptr = GDAL.gdalmdarraygetmask(mdarray, CSLConstListWrapper(options)) + ptr == C_NULL && error("Could not get mask for mdarray") + return MDArray(ptr, mdarray.dataset) +end + +function getmask( + mdarray::AbstractMDArray, + options::OptionList = nothing, +)::AbstractMDArray + @assert !isnull(mdarray) + ptr = GDAL.gdalmdarraygetmask(mdarray, CSLConstListWrapper(options)) + ptr == C_NULL && error("Could not get mask for mdarray") + return IMDArray(ptr, mdarray.dataset) +end + +# TODO: Wrap GDAL.GDALRIOResampleAlg +function unsafe_getresampled( + mdarray::AbstractMDArray, + newdims::Union{Nothing,VectorLike{<:AbstractDimension}}, + resamplealg::GDAL.GDALRIOResampleAlg, + targetsrs::Union{Nothing,AbstractSpatialRef}, + options::OptionList = nothing, +)::AbstractMDArray + @assert !isnull(mdarray) + ptr = GDAL.gdalmdarraygetresampled( + mdarray, + isnothing(newdims) ? 0 : length(newdims), + isnothing(newdims) ? C_NULL : DimensionHList(reverse(newdims)), + resamplealg, + isnothing(targetsrs) ? C_NULL : targetsrs, + CSLConstListWrapper(options), + ) + ptr == C_NULL && error("Could not get resampled mdarray") + return MDArray(ptr, mdarray.dataset) +end + +function getresampled( + mdarray::AbstractMDArray, + newdims::Union{Nothing,VectorLike{<:AbstractDimension}}, + resamplealg::GDAL.GDALRIOResampleAlg, + targetsrs::Union{Nothing,AbstractSpatialRef}, + options::OptionList = nothing, +)::AbstractMDArray + @assert !isnull(mdarray) + ptr = GDAL.gdalmdarraygetresampled( + mdarray, + isnothing(newdims) ? 0 : length(newdims), + isnothing(newdims) ? C_NULL : DimensionHList(reverse(newdims)), + resamplealg, + isnothing(targetsrs) ? C_NULL : targetsrs, + CSLConstListWrapper(options), + ) + ptr == C_NULL && error("Could not get resampled mdarray") + return IMDArray(ptr, mdarray.dataset) +end + +function unsafe_getgridded( + mdarray::AbstractMDArray, + gridoptions::AbstractString, + xarray::AbstractMDArray, + yarray::AbstractMDArray, + options::OptionList = nothing, +)::AbstractMDArray + @assert !isnull(mdarray) + @assert !isnull(xarray) + @assert !isnull(yarray) + ptr = GDAL.gdalmdarraygetgridded( + mdarray, + gridoptions, + xarray, + yarray, + CSLConstListWrapper(options), + ) + ptr == C_NULL && error("Could not get gridded mdarray") + return MDArray(ptr, mdarray.dataset) +end + +function getgridded( + mdarray::AbstractMDArray, + gridoptions::AbstractString, + xarray::AbstractMDArray, + yarray::AbstractMDArray, + options::OptionList = nothing, +)::AbstractMDArray + @assert !isnull(mdarray) + @assert !isnull(xarray) + @assert !isnull(yarray) + ptr = GDAL.gdalmdarraygetgridded( + mdarray, + gridoptions, + xarray, + yarray, + CSLConstListWrapper(options), + ) + ptr == C_NULL && error("Could not get gridded mdarray") + return IMDArray(ptr, mdarray.dataset) +end + +function unsafe_asclassicdataset( + mdarray::AbstractMDArray, + xdim::Integer, + ydim::Integer, + rootgroup::Union{Nothing,AbstractGroup} = nothing, + options::OptionList = nothing, +)::AbstractDataset + @assert !isnull(mdarray) + @assert isnothing(rootgroup) || !isnull(rootgroup) + return Dataset( + GDAL.gdalmdarrayasclassicdataset( + mdarray, + xdim, + ydim, + isnothing(rootgroup) ? C_NULL : rootgroup, + CSLConstListWrapper(options), + ), + ) +end + +function asclassicdataset( + mdarray::AbstractMDArray, + xdim::Integer, + ydim::Integer, + rootgroup::Union{Nothing,AbstractGroup} = nothing, + options::OptionList = nothing, +)::AbstractDataset + @assert !isnull(mdarray) + @assert isnothing(rootgroup) || !isnull(rootgroup) + return IDataset( + GDAL.gdalmdarrayasclassicdataset( + mdarray, + xdim, + ydim, + isnothing(rootgroup) ? C_NULL : rootgroup, + CSLConstListWrapper(options), + ), + ) +end + +function unsafe_asmdarray(rasterband::AbstractRasterBand)::AbstractMDArray + @assert !isnull(rasterband) + ptr = GADL.gdalrasterbandasmdarray(rasterband) + ptr == C_NULL && error("Could not get view rasterband view as mdarray") + # TODO: Find dataset + return MDArray(ptr) +end + +function asmdarray(rasterband::AbstractRasterBand)::AbstractMDArray + @assert !isnull(rasterband) + ptr = GADL.gdalrasterbandasmdarray(rasterband) + ptr == C_NULL && error("Could not get view rasterband view as mdarray") + # TODO: Find dataset + return IMDArray(ptr) +end + +# TODO: Wrap GDAL.CPLErr +# TODO: Allow a progress function +function getstatistics( + mdarray::AbstractMDArray, + approxok::Bool, + force::Bool, +)::Tuple{GDAL.CPLErr,Float64,Float64,Float64,Float64,Int64} + @assert !isnull(mdarray) + dataset = C_NULL # apparently unused + min = Ref{Float64}() + max = Ref{Float64}() + mean = Ref{Float64}() + stddev = Ref{Float64}() + validcount = Ref{UInt64}() + err = GDAL.gdalmdarraygetstatistics( + mdarray, + dataset, + approxok, + force, + min, + max, + mean, + stddev, + validcount, + C_NULL, + C_NULL, + ) + return err, min[], max[], mean[], stddev[], Int64(validcount[]) +end + +# TODO: Allow a progress function +function computestatistics( + mdarray::AbstractMDArray, + approxok::Bool, + options::OptionList = nothing, +)::Tuple{Bool,Float64,Float64,Float64,Float64,Int64} + @assert !isnull(mdarray) + dataset = C_NULL # apparently unused + min = Ref{Float64}() + max = Ref{Float64}() + mean = Ref{Float64}() + stddev = Ref{Float64}() + validcount = Ref{UInt64}() + success = GDAL.gdalmdarraycomputestatisticsex( + mdarray, + dataset, + approxok, + min, + max, + mean, + stddev, + validcount, + C_NULL, + C_NULL, + CSLConstListWrapper(options), + ) + return Bool(succeess), min[], max[], mean[], stddev[], Int64(validcount[]) +end + +function clearstatistics(mdarray::AbstractMDArray)::Nothing + @assert !isnull(mdarray) + return GDAL.gdalmdarrayclearstatistics(mdarray) +end + +function getcoordinatevariables( + mdarray::AbstractMDArray{<:Any,D}, +)::NTuple{D,T where T<:AbstractMDArray} where {D} + @assert !isnull(mdarray) + count = Ref{Csize_t}() + coordinatevariablesptr = + GDAL.gdalmdarraygetcoordinatevariables(mdarray, count) + coordinatevariables = reverse( + ntuple( + d -> IMDArray( + unsafe_load(coordinatevariablesptr, d), + mdarray.dataset, + ), + count[], + ), + ) + GDAL.vsifree(coordinatevariablesptr) + return coordinatevariables +end + +function adviseread( + mdarray::AbstractMDArray{<:Any,D}, + arraystartidx::Union{Nothing,IndexLike{D}}, + count::Union{Nothing,IndexLike{D}}, + options::OptionList = nothing, +)::Bool where {D} + @assert !isnull(mdarray) + @assert isnothing(arraystartix) ? true : length(arraystartidx) == D + @assert isnothing(count) ? true : length(count == D) + gdal_arraystartidx = + isnothing(arraystartidx) ? C_NULL : + UInt64[arraystartidx[d] - 1 for d in D:-1:1] + gdal_count = isnothing(count) ? C_NULL : Csize_t[count[d] for d in D:-1:1] + return GDAL.gdalmdarrayadviseread( + mdarray, + gdal_arraystartidx, + gdal_count, + CSLConstListWrapper(options), + ) +end + +function isregularlyspaced( + mdarray::AbstractMDArray, +)::Union{Nothing,Tuple{Float64,Float64}} + @assert !isnull(mdarray) + start = Ref{Float64}() + increment = Ref{Float64}() + res = GDAL.gdalmdarrayisregularlyspaced(mdarray, start, increment) + !res[] && return nothing + return start[], increment[] +end + +function guessgeotransform( + mdarray::AbstractMDArray, + dimx::Integer, + dimy::Integer, + pixelispoint::Bool, +)::Union{Nothing,AbstractVector{Float64}} + @assert !isnull(mdarray) + geotransform = Vector{Float64}(undef, 6) + res = GDAL.gdalmdarrayguessgeotransform( + mdarray, + dimx, + dimy, + pixelispoint, + geotransform, + ) + !res && return nothing + return geotransform +end + +function cache(mdarray::AbstractMDArray, options::OptionList = nothing)::Bool + @assert !isnull(mdarray) + return GDAL.gdalmdarraycache(mdarray, CSLConstListWrapper(options)) +end + +function getrootgroup(mdarray::AbstractMDArray)::AbstractGroup + @assert !isnull(mdarray) + return Group(GDAL.gdalmdarraygetrootgroup(mdarray), mdarray.dataset) +end + +################################################################################ + +function getname(mdarray::AbstractMDArray)::AbstractString + @assert !isnull(mdarray) + return GDAL.gdalmdarraygetname(mdarray) +end + +function getfullname(mdarray::AbstractMDArray)::AbstractString + @assert !isnull(mdarray) + return GDAL.gdalmdarraygetfullname(mdarray) +end + +function gettotalelementscount(mdarray::AbstractMDArray)::Int64 + @assert !isnull(mdarray) + return Int64(GDAL.gdalmdarraygettotalelementscount(mdarray)) +end + +function Base.length(mdarray::AbstractMDArray) + @assert !isnull(mdarray) + return Int(gettotalelementscount(mdarray)) +end + +# function getdimensioncount(mdarray::AbstractMDArray)::Int +# @assert !isnull(mdarray) +# return Int(GDAL.gdalmdarraygetdimensioncount(mdarray)) +# end +getdimensioncount(mdarray::AbstractMDArray{<:Any,D}) where {D} = D + +Base.ndims(mdarray::AbstractMDArray)::Int = getdimensioncount(mdarray) + +function getdimensions( + mdarray::AbstractMDArray{<:Any,D}, +)::NTuple{D,T where T<:AbstractDimension} where {D} + @assert !isnull(mdarray) + dimensionscountref = Ref{Csize_t}() + dimensionshptr = GDAL.gdalmdarraygetdimensions(mdarray, dimensionscountref) + dimensions = reverse( + ntuple( + d -> + IDimension(unsafe_load(dimensionshptr, d), mdarray.dataset), + dimensionscountref[], + ), + ) + GDAL.vsifree(dimensionshptr) + return dimensions +end + +function unsafe_getdimensions( + mdarray::AbstractMDArray{<:Any,D}, +)::NTuple{D,T where T<:AbstractDimension} where {D} + @assert !isnull(mdarray) + dimensionscountref = Ref{Csize_t}() + dimensionshptr = GDAL.gdalmdarraygetdimensions(mdarray, dimensionscountref) + dimensions = reverse( + ntuple( + d -> Dimension(unsafe_load(dimensionshptr, d), mdarray.dataset), + dimensionscountref[], + ), + ) + GDAL.vsifree(dimensionshptr) + return dimensions +end + +function Base.size(mdarray::AbstractMDArray{<:Any,D})::NTuple{D,Int} where {D} + getdimensions(mdarray) do dimensions + return ntuple(d -> getsize(dimensions[d]), D) + end +end + +function unsafe_getdatatype(mdarray::AbstractMDArray)::AbstractExtendedDataType + @assert !isnull(mdarray) + return ExtendedDataType(GDAL.gdalmdarraygetdatatype(mdarray)) +end + +function getdatatype(mdarray::AbstractMDArray)::AbstractExtendedDataType + @assert !isnull(mdarray) + return IExtendedDataType(GDAL.gdalmdarraygetdatatype(mdarray)) +end + +Base.eltype(mdarray::AbstractMDArray{T}) where {T} = T + +function getblocksize( + mdarray::AbstractMDArray{<:Any,D}, +)::NTuple{D,Int} where {D} + @assert !isnull(mdarray) + count = Ref{Csize_t}() + blocksizeptr = GDAL.gdalmdarraygetblocksize(mdarray, count) + blocksize = reverse(ntuple(d -> Int(unsafe_load(blocksizeptr, d)), count[])) + GDAL.vsifree(blocksizeptr) + return blocksize +end + +DiskArrays.haschunks(::AbstractMDArray) = DiskArrays.Chunked() + +function DiskArrays.eachchunk( + mdarray::AbstractMDArray{<:Any,D}, +)::NTuple{D,Int} where {D} + blocksize = getblocksize(mdarray) + return DiskArrays.GridChunks(mdarray, blocksize) +end + +function getprocessingchunksize( + mdarray::AbstractMDArray, + maxchunkmemory::Integer, +)::AbstractVector{Int} + @assert !isnull(mdarray) + count = Ref{Csize_t}() + chunksizeptr = + GDAL.gdalmdarraygetprocessingchunksize(mdarray, count, maxchunkmemory) + chunksize = Int[unsafe_load(chunksizeptr, n) for n in count[]:-1:1] + GDAL.vsifree(chunksizeptr) + return chunksize +end + +# processperchunk + +function read!( + mdarray::AbstractMDArray, + arraystartidx::IndexLike{D}, + count::IndexLike{D}, + arraystep::Union{Nothing,IndexLike{D}}, + buffer::StridedArray{T,D}, +)::Nothing where {T,D} + @assert !isnull(mdarray) + @assert length(arraystartidx) == D + @assert length(count) == D + @assert isnothing(arraystep) ? true : length(arraystep) == D + gdal_arraystartidx = UInt64[arraystartidx[d] - 1 for d in D:-1:1] + gdal_count = Csize_t[count[d] for d in D:-1:1] + gdal_arraystep = + isnothing(arraystep) ? C_NULL : Int64[arraystep[d] for d in D:-1:1] + gdal_bufferstride = Cptrdiff_t[stride(buffer, d) for d in D:-1:1] + return extendeddatatypecreate(T) do bufferdatatype + success = GDAL.gdalmdarrayread( + mdarray, + gdal_arraystartidx, + gdal_count, + gdal_arraystep, + gdal_bufferstride, + bufferdatatype, + buffer, + buffer, + sizeof(buffer), + ) + success == 0 && error("Could not read mdarray") + return nothing + end +end + +function read!( + mdarray::AbstractMDArray, + region::RangeLike{D}, + buffer::StridedArray{T,D}, +)::Nothing where {T,D} + @assert length(region) == D + arraystartidx = first.(region) + count = length.(region) + arraystep = step.(region) + return read!(mdarray, arraystartidx, count, arraystep, buffer) +end + +function read!( + mdarray::AbstractMDArray, + indices::CartesianIndices{D}, + arraystep::Union{Nothing,IndexLike{D}}, + buffer::StridedArray{T,D}, +)::Nothing where {T,D} + @assert length(region) == D + arraystartidx = first.(indices) + count = length.(indices) + return read!(mdarray, arraystartidx, count, arraystep, buffer) +end + +function read!( + mdarray::AbstractMDArray, + indices::CartesianIndices{D}, + buffer::StridedArray{T,D}, +)::Nothing where {T,D} + return read!(mdarray, indices, nothing, buffer) +end + +function read!( + mdarray::AbstractMDArray, + buffer::StridedArray{T,D}, +)::Nothing where {T,D} + return read!(mdarray, axes(buffer), buffer) +end + +function read(mdarray::AbstractMDArray)::AbstractArray + getdimensions(mdarray) do dimensions + D = length(dimensions) + sz = [getsize(dimensions[d]) for d in 1:D] + getdatatype(mdarray) do datatype + class = getclass(datatype) + @assert class == GDAL.GEDTC_NUMERIC + T = convert(DataType, getnumericdatatype(datatype)) + buffer = Array{T}(undef, sz...) + read!(mdarray, buffer) + return buffer + end + end +end + +function DiskArrays.readblock!( + mdarray::AbstractMDArray{<:Any,D}, + aout, + r::Vararg{AbstractUnitRange,D}, +)::Nothing where {D} + success == read!(mdarray, r, aout) + @assert success + return nothing +end + +function write( + mdarray::AbstractMDArray, + arraystartidx::IndexLike{D}, + count::IndexLike{D}, + arraystep::Union{Nothing,IndexLike{D}}, + buffer::StridedArray{T,D}, +)::Nothing where {T,D} + @assert !isnull(mdarray) + @assert length(arraystartidx) == D + @assert length(count) == D + @assert isnothing(arraystep) ? true : length(arraystep) == D + gdal_arraystartidx = UInt64[arraystartidx[d] - 1 for d in D:-1:1] + gdal_count = Csize_t[count[d] for d in D:-1:1] + gdal_arraystep = + isnothing(arraystep) ? C_NULL : Int64[arraystep[d] for d in D:-1:1] + gdal_bufferstride = Cptrdiff_t[stride(buffer, d) for d in D:-1:1] + return extendeddatatypecreate(T) do bufferdatatype + success = GDAL.gdalmdarraywrite( + mdarray, + gdal_arraystartidx, + gdal_count, + gdal_arraystep, + gdal_bufferstride, + bufferdatatype, + buffer, + buffer, + sizeof(buffer), + ) + success == 0 && error("Could not write mdarray") + return nothing + end +end + +function write( + mdarray::AbstractMDArray, + region::RangeLike{D}, + buffer::StridedArray{T,D}, +)::Nothing where {T,D} + @assert length(region) == D + arraystartidx = first.(region) + count = length.(region) + arraystep = step.(region) + return write(mdarray, arraystartidx, count, arraystep, buffer) +end + +function write( + mdarray::AbstractMDArray, + indices::CartesianIndices{D}, + arraystep::Union{Nothing,IndexLike{D}}, + buffer::StridedArray{T,D}, +)::Nothing where {T,D} + @assert length(region) == D + arraystartidx = first.(indices) + count = length.(indices) + return write(mdarray, arraystartidx, count, arraystep, buffer) +end + +function write( + mdarray::AbstractMDArray, + indices::CartesianIndices{D}, + buffer::StridedArray{T,D}, +)::Nothing where {T,D} + return write(mdarray, indices, nothing, buffer) +end + +function write( + mdarray::AbstractMDArray, + buffer::StridedArray{T,D}, +)::Nothing where {T,D} + return write(mdarray, axes(buffer), buffer) +end + +function DiskArrays.writeblock!( + mdarray::AbstractMDArray{<:Any,D}, + ain, + r::Vararg{AbstractUnitRange,D}, +)::Nothing where {D} + success == write(mdarray, r, ain) + @assert success + return nothing +end + +function rename!(mdarray::AbstractMDArray, newname::AbstractString)::Bool + @assert !isnull(mdarray) + return GDAL.gdalmdarrayrename(mdarray, newname) +end + +################################################################################ + +function unsafe_getattribute( + mdarray::AbstractMDArray, + name::AbstractString, +)::AbstractAttribute + @assert !isnull(mdarray) + ptr = GDAL.gdalmdarraygetattribute(mdarray, name) + ptr == C_NULL && error("Could not get attribute \"$name\"") + return Attribute(ptr, mdarray.dataset) +end + +function getattribute( + mdarray::AbstractMDArray, + name::AbstractString, +)::AbstractAttribute + @assert !isnull(mdarray) + ptr = GDAL.gdalmdarraygetattribute(mdarray, name) + ptr == C_NULL && error("Could not get attribute \"$name\"") + return IAttribute(ptr, mdarray.dataset) +end + +function unsafe_getattributes( + mdarray::AbstractMDArray, + options::OptionList = nothing, +)::AbstractVector{<:AbstractAttribute} + @assert !isnull(mdarray) + count = Ref{Csize_t}() + ptr = GDAL.gdalmdarraygetattributes( + mdarray, + count, + CSLConstListWrapper(options), + ) + attributes = AbstractAttribute[ + Attribute(unsafe_load(ptr, n), mdarray.dataset) for n in 1:count[] + ] + GDAL.vsifree(ptr) + return attributes +end + +function getattributes( + mdarray::AbstractMDArray, + options::OptionList = nothing, +)::AbstractVector{<:AbstractAttribute} + @assert !isnull(mdarray) + count = Ref{Csize_t}() + ptr = GDAL.gdalmdarraygetattributes( + mdarray, + count, + CSLConstListWrapper(options), + ) + attributes = AbstractAttribute[ + IAttribute(unsafe_load(ptr, n), mdarray.dataset) for n in 1:count[] + ] + GDAL.vsifree(ptr) + return attributes +end + +function unsafe_createattribute( + mdarray::AbstractMDArray, + name::AbstractString, + dimensions::AbstractVector{<:Integer}, + datatype::AbstractExtendedDataType, + options::OptionList = nothing, +)::AbstractAttribute + @assert !isnull(mdarray) + @assert !isnull(datatype) + ptr = GDAL.gdalmdarraycreateattribute( + mdarray, + name, + length(dimensions), + dimensions, + datatype, + CSLConstListWrapper(options), + ) + ptr == C_NULL && error("Could not create attribute \"$name\"") + return Attribute(ptr, mdarray.dataset) +end + +function createattribute( + mdarray::AbstractMDArray, + name::AbstractString, + dimensions::AbstractVector{<:Integer}, + datatype::AbstractExtendedDataType, + options::OptionList = nothing, +)::AbstractAttribute + @assert !isnull(mdarray) + @assert !isnull(datatype) + ptr = GDAL.gdalmdarraycreateattribute( + mdarray, + name, + length(dimensions), + dimensions, + datatype, + CSLConstListWrapper(options), + ) + ptr == C_NULL && error("Could not create attribute \"$name\"") + return IAttribute(ptr, mdarray.dataset) +end + +function deleteattribute( + mdarray::AbstractMDArray, + name::AbstractString, + options::OptionList = nothing, +)::Bool + @assert !isnull(mdarray) + return GDAL.gdalmdarraydeleteattribute( + mdarray, + name, + CSLConstListWrapper(options), + ) +end diff --git a/src/mdarray/types.jl b/src/mdarray/types.jl new file mode 100644 index 00000000..ce26c6a9 --- /dev/null +++ b/src/mdarray/types.jl @@ -0,0 +1,306 @@ +abstract type AbstractExtendedDataType end +# needs to have a `ptr::GDALExtendedDataTypeH` attribute + +abstract type AbstractEDTComponent end +# needs to have a `ptr::GDALEDTComponentH` attribute + +# TODO: <: AbstractDict +abstract type AbstractGroup end +# needs to have a `ptr::GDAL.GDALGroupH` attribute + +abstract type AbstractMDArray{T,D} <: AbstractDiskArray{T,D} end +# needs to have a `ptr::GDAL.GDALMDArrayH` attribute + +# TODO: <: DenseArray{T,D} +abstract type AbstractAttribute end +# needs to have a `ptr::GDAL.GDALAttributeH` attribute + +abstract type AbstractDimension end +# needs to have a `ptr::GDAL.GDALDimensionH` attribute + +################################################################################ + +mutable struct ExtendedDataType <: AbstractExtendedDataType + ptr::GDAL.GDALExtendedDataTypeH + + ExtendedDataType(ptr::GDAL.GDALExtendedDataTypeH) = new(ptr) +end + +mutable struct IExtendedDataType <: AbstractExtendedDataType + ptr::GDAL.GDALExtendedDataTypeH + + function IExtendedDataType(ptr::GDAL.GDALExtendedDataTypeH) + extendeddatatype = new(ptr) + ptr != C_NULL && finalizer(destroy, extendeddatatype) + return extendeddatatype + end +end + +mutable struct EDTComponent <: AbstractEDTComponent + ptr::GDAL.GDALEDTComponentH + + EDTComponent(ptr::GDAL.GDALEDTComponentH) = new(ptr) +end + +mutable struct IEDTComponent <: AbstractEDTComponent + ptr::GDAL.GDALEDTComponentH + + function IEDTComponent(ptr::GDAL.GDALEDTComponentH) + edtcomponent = new(ptr) + ptr != C_NULL && finalizer(destroy, edtcomponent) + return edtcomponent + end +end + +mutable struct Group <: AbstractGroup + ptr::GDAL.GDALGroupH + dataset::WeakRef # AbstractDataset + + function Group(ptr::GDAL.GDALGroupH, dataset::WeakRef) + group = new(ptr, dataset) + add_child!(dataset, group) + return group + end +end + +mutable struct IGroup <: AbstractGroup + ptr::GDAL.GDALGroupH + dataset::WeakRef # AbstractDataset + + function IGroup(ptr::GDAL.GDALGroupH, dataset::WeakRef) + group = new(ptr, dataset) + add_child!(dataset, group) + ptr != C_NULL && finalizer(destroy, group) + return group + end +end + +mutable struct MDArray{T,D} <: AbstractMDArray{T,D} + ptr::GDAL.GDALMDArrayH + dataset::WeakRef # AbstractDataset + + function MDArray{T,D}(ptr::GDAL.GDALMDArrayH, dataset::WeakRef) where {T,D} + T::Type + D::Int + mdarray = new{T,D}(ptr, dataset) + add_child!(dataset, mdarray) + return mdarray + end +end + +mutable struct IMDArray{T,D} <: AbstractMDArray{T,D} + ptr::GDAL.GDALMDArrayH + dataset::WeakRef # AbstractDataset + + function IMDArray{T,D}(ptr::GDAL.GDALMDArrayH, dataset::WeakRef) where {T,D} + T::Type + D::Int + mdarray = new{T,D}(ptr, dataset) + add_child!(dataset, mdarray) + ptr != C_NULL && finalizer(destroy, mdarray) + return mdarray + end +end + +mutable struct Attribute <: AbstractAttribute + ptr::GDAL.GDALAttributeH + dataset::WeakRef # AbstractDataset + + function Attribute(ptr::GDAL.GDALAttributeH, dataset::WeakRef) + attribute = new(ptr, dataset) + add_child!(dataset, attribute) + return attribute + end +end + +mutable struct IAttribute <: AbstractAttribute + ptr::GDAL.GDALAttributeH + dataset::WeakRef # AbstractDataset + + function IAttribute(ptr::GDAL.GDALAttributeH, dataset::WeakRef) + attribute = new(ptr, dataset) + add_child!(dataset, attribute) + ptr != C_NULL && finalizer(destroy, attribute) + return attribute + end +end + +mutable struct Dimension <: AbstractDimension + ptr::GDAL.GDALDimensionH + dataset::WeakRef # AbstractDataset + + function Dimension(ptr::GDAL.GDALDimensionH, dataset::WeakRef) + dimension = new(ptr, dataset) + add_child!(dataset, dimension) + return dimension + end +end + +mutable struct IDimension <: AbstractDimension + ptr::GDAL.GDALDimensionH + dataset::WeakRef # AbstractDataset + + function IDimension(ptr::GDAL.GDALDimensionH, dataset::WeakRef) + dimension = new(ptr, dataset) + add_child!(dataset, dimension) + ptr != C_NULL && finalizer(destroy, dimension) + return dimension + end +end + +################################################################################ + +isnull(x::AbstractAttribute) = x.ptr == C_NULL +isnull(x::AbstractDataset) = x.ptr == C_NULL +isnull(x::AbstractDimension) = x.ptr == C_NULL +isnull(x::AbstractEDTComponent) = x.ptr == C_NULL +isnull(x::AbstractExtendedDataType) = x.ptr == C_NULL +isnull(x::AbstractFeature) = x.ptr == C_NULL +isnull(x::AbstractFeatureDefn) = x.ptr == C_NULL +isnull(x::AbstractFeatureLayer) = x.ptr == C_NULL +isnull(x::AbstractFieldDefn) = x.ptr == C_NULL +isnull(x::AbstractGeomFieldDefn) = x.ptr == C_NULL +isnull(x::AbstractGeometry) = x.ptr == C_NULL +isnull(x::AbstractGroup) = x.ptr == C_NULL +isnull(x::AbstractMDArray) = x.ptr == C_NULL +isnull(x::AbstractRasterBand) = x.ptr == C_NULL +isnull(x::AbstractSpatialRef) = x.ptr == C_NULL +isnull(x::ColorTable) = x.ptr == C_NULL +isnull(x::CoordTransform) = x.ptr == C_NULL +isnull(x::Driver) = x.ptr == C_NULL +isnull(x::Field) = x.ptr == C_NULL +isnull(x::RasterAttrTable) = x.ptr == C_NULL +isnull(x::StyleManager) = x.ptr == C_NULL +isnull(x::StyleTable) = x.ptr == C_NULL +isnull(x::StyleTool) = x.ptr == C_NULL + +################################################################################ + +const VectorLike{T} = Union{AbstractVector{<:T},NTuple{<:Any,X where X<:T}} + +const IndexLike{D} = Union{ + AbstractVector{<:Integer}, + CartesianIndex{D}, + NTuple{D,I where I<:Integer}, +} +const RangeLike{D} = Union{ + AbstractVector{<:AbstractRange{<:Integer}}, + NTuple{D,R where R<:AbstractRange{<:Integer}}, +} + +################################################################################ + +Base.unsafe_convert(::Type{Ptr{Cvoid}}, x::AbstractExtendedDataType) = x.ptr +Base.unsafe_convert(::Type{Ptr{Cvoid}}, x::AbstractEDTComponent) = x.ptr +Base.unsafe_convert(::Type{Ptr{Cvoid}}, x::AbstractGroup) = x.ptr +Base.unsafe_convert(::Type{Ptr{Cvoid}}, x::AbstractMDArray) = x.ptr +Base.unsafe_convert(::Type{Ptr{Cvoid}}, x::AbstractAttribute) = x.ptr +Base.unsafe_convert(::Type{Ptr{Cvoid}}, x::AbstractDimension) = x.ptr + +################################################################################ + +function destroy(datatype::AbstractExtendedDataType)::Nothing + datatype.ptr == C_NULL && return nothing + GDAL.gdalextendeddatatyperelease(datatype) + datatype.ptr = C_NULL + return nothing +end + +function destroy(edtcomponent::AbstractEDTComponent)::Nothing + edtcomponent.ptr == C_NULL && return nothing + GDAL.gdaledtcomponentrelease(edtcomponent) + edtcomponent.ptr = C_NULL + return nothing +end + +function destroy(group::AbstractGroup)::Nothing + group.ptr == C_NULL && return nothing + GDAL.gdalgrouprelease(group) + group.ptr = C_NULL + return nothing +end + +function destroy(mdarray::AbstractMDArray)::Nothing + mdarray.ptr == C_NULL && return nothing + GDAL.gdalmdarrayrelease(mdarray) + mdarray.ptr = C_NULL + return nothing +end + +function destroy(attribute::AbstractAttribute)::Nothing + attribute.ptr == C_NULL && return nothing + GDAL.gdalattributerelease(attribute) + attribute.ptr = C_NULL + return nothing +end + +function destroy(dimension::AbstractDimension)::Nothing + dimension.ptr == C_NULL && return nothing + GDAL.gdaldimensionrelease(dimension) + dimension.ptr = C_NULL + return nothing +end + +function destroy(edtcomponents::VectorLike{<:AbstractEDTComponent}) + return destroy.(edtcomponents) +end +destroy(attributes::VectorLike{<:AbstractAttribute}) = destroy.(attributes) +destroy(dimensions::VectorLike{<:AbstractDimension}) = destroy.(dimensions) + +################################################################################ + +# Helpers + +const OptionList = Union{Nothing,AbstractVector{<:AbstractString}} + +# TODO: Move to GDAL.jl, redefining `CSLConstList` +struct CSLConstListWrapper + # Hold on to the original arguments to prevent GC from freeing + # them while they are being used in a ccall + cstrings::Union{Nothing,Vector{Cstring}} + strings::Any + + function CSLConstListWrapper(strings::Nothing) + cstrings = nothing + return new(cstrings, strings) + end + function CSLConstListWrapper( + strings::AbstractVector{<:Union{String,SubString{String}}}, + ) + cstrings = Cstring[[pointer(str) for str in strings]; C_NULL] + return new(cstrings, strings) + end +end +function CSLConstListWrapper(strings::AbstractVector{<:AbstractString}) + return CSLConstListWrapper(String.(strings)) +end + +function Base.cconvert(::Type{GDAL.CSLConstList}, wrapper::CSLConstListWrapper) + isnothing(wrapper.cstrings) && + return Base.cconvert(GDAL.CSLConstList, C_NULL) + return Base.cconvert(GDAL.CSLConstList, wrapper.cstrings) +end + +struct DimensionHList + # Hold on to the original arguments to prevent GC from freeing + # them while they are being used in a ccall + dimensionhs::Vector{GDAL.GDALDimensionH} + dimensions::AbstractVector{<:AbstractDimension} + + function DimensionHList(dimensions::AbstractVector{<:AbstractDimension}) + dimensionhs = GDAL.GDALDimensionH[ + Base.unsafe_convert(Ptr{Cvoid}, dim) for dim in dimensions + ] + return new(dimensionhs, dimensions) + end +end +function DimensionHList(dimensions::NTuple{<:Any,T where T<:AbstractDimension}) + return DimensionHList([dimensions...]) +end + +function Base.cconvert( + ::Type{Ptr{GDAL.GDALDimensionH}}, + dimensionhlist::DimensionHList, +) + return Base.cconvert(Ptr{Cvoid}, dimensionhlist.dimensionhs) +end diff --git a/src/types.jl b/src/types.jl index 38e85909..1bc768cf 100644 --- a/src/types.jl +++ b/src/types.jl @@ -35,22 +35,49 @@ mutable struct CoordTransform ptr::GDAL.OGRCoordinateTransformationH end +# In the multidim API, underlying files are closed only when all +# objects potentially pointing to them (groups, arrays, attributes, +# dimensions) have been released. Their lifetime is not connected with +# the one of the GDALDataset. +# +# To handle this in Julia, each multidim dataset can hold a list of +# its children. This allows us to "hard close" a dataset when using +# the interactive API. Being able to close a dataset at a particular +# time is important when writing. +# +# Each child must have a `destroy` function. mutable struct Dataset <: AbstractDataset ptr::GDAL.GDALDatasetH + children::Union{Nothing,Vector{WeakRef}} - Dataset(ptr::GDAL.GDALDatasetH = C_NULL) = new(ptr) + function Dataset(ptr::GDAL.GDALDatasetH = C_NULL; hard_close::Bool = false) + return new(ptr, hard_close ? WeakRef[] : nothing) + end end mutable struct IDataset <: AbstractDataset ptr::GDAL.GDALDatasetH + children::Union{Nothing,Vector{WeakRef}} - function IDataset(ptr::GDAL.GDALDatasetH = C_NULL) - dataset = new(ptr) + function IDataset(ptr::GDAL.GDALDatasetH = C_NULL; hard_close::Bool = false) + dataset = new(ptr, hard_close ? WeakRef[] : nothing) finalizer(destroy, dataset) return dataset end end +function add_child!(dataset::WeakRef, obj::Any)::Nothing + isnull(obj) && return nothing + dataset = dataset.value + # It is fine if the dataset does not exist any more + isnothing(dataset) && return nothing + dataset::AbstractDataset + @assert !isnull(dataset) + isnothing(dataset.children) && return nothing + push!(dataset.children, WeakRef(obj)) + return nothing +end + mutable struct Driver ptr::GDAL.GDALDriverH end @@ -615,13 +642,14 @@ end wkbNDR::GDAL.wkbNDR, ) -import Base.| - for T in (GDALOpenFlag, FieldValidation) eval(quote - |(x::$T, y::UInt8)::UInt8 = UInt8(x) | y - |(x::UInt8, y::$T)::UInt8 = x | UInt8(y) - |(x::$T, y::$T)::UInt8 = UInt8(x) | UInt8(y) + Base.:&(x::$T, y::Unsigned)::UInt32 = UInt32(x) & UInt32(y) + Base.:&(x::Unsigned, y::$T)::UInt32 = UInt32(x) & UInt32(y) + Base.:&(x::$T, y::$T)::UInt32 = UInt32(x) & UInt32(y) + Base.:|(x::$T, y::Unsigned)::UInt32 = UInt32(x) | UInt32(y) + Base.:|(x::Unsigned, y::$T)::UInt32 = UInt32(x) | UInt32(y) + Base.:|(x::$T, y::$T)::UInt32 = UInt32(x) | UInt32(y) end) end diff --git a/test/runtests.jl b/test/runtests.jl index b3aeb608..addfd68f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,8 +1,9 @@ using Test using Dates using GDAL -import ArchGDAL -import Aqua +#TODO import ArchGDAL +import ArchGDAL as AG +using Aqua # ensure all testing files are present include("remotefiles.jl") @@ -31,6 +32,7 @@ include("remotefiles.jl") include("test_gdalutilities.jl") include("test_gdalutilities_errors.jl") include("test_rasterattrtable.jl") + include("test_mdarray.jl") include("test_ospy_examples.jl") include("test_geos_operations.jl") include("test_cookbook_geometry.jl") diff --git a/test/test_mdarray.jl b/test/test_mdarray.jl new file mode 100644 index 00000000..3f7a895b --- /dev/null +++ b/test/test_mdarray.jl @@ -0,0 +1,608 @@ +using Test +import ArchGDAL as AG +using GDAL + +# TODO: Test vsizip driver + +# There should be more drivers... Anyone willing to update GDAL? +# Possible drivers: at least HDF4, HDF5, TileDB +const mdarray_drivers = [ + ( + drivername = "MEM", + drivercreateoptions = nothing, + mdarraycreateoptions = nothing, + ), + ( + drivername = "netCDF", + drivercreateoptions = ["FORMAT=NC4"], + mdarraycreateoptions = ["COMPRESS=DEFLATE", "ZLEVEL=9"], + ), + ( + drivername = "Zarr", + drivercreateoptions = ["FORMAT=ZARR_V3"], + mdarraycreatoptions = [ + "COMPRESS=BLOSC", + "BLOSC_CLEVEL=9", + "BLOSC_SHUFFLE=BIT", + ], + ), +] + +# Attributes with complex values are not supported by Zarr (?) +const scalar_attribute_types = [ + String, + Int8, + Int16, + Int32, + Int64, + UInt8, + UInt16, + UInt32, + UInt64, + Float32, + Float64, + # Complex{Int16}, + # Complex{Int32}, + # Complex{Float32}, + # Complex{Float64}, +] +const attribute_types = + [scalar_attribute_types..., [Vector{T} for T in scalar_attribute_types]...] + +# Can't have `\0` or `/` in attribute names +# For netCDF: +# - first character must be alphanumeric or underscore or >= 128, +# - next characters cannot be iscontrol or DEL, +# - last character cannot be isspace +const attribute_names = [ + "attribute", + "αβγ", + # [string(ch) for ch in Char(1):Char(256) if ch != '/']..., + [ + string(ch) for ch in [ + ('A':'Z')..., + ('a':'z')..., + ('0':'9')..., + '_', + (Char(128):Char(256))..., + ] + ]..., +] + +get_attribute_value(::Type{String}) = "string" +get_attribute_value(::Type{T}) where {T<:Real} = T(32) +get_attribute_value(::Type{T}) where {T<:Complex} = T(32, 33) +get_attribute_value(::Type{Vector{String}}) = String["string", "", "αβγ"] +function get_attribute_value(::Type{Vector{T}}) where {T<:Integer} + # Can't store large Int64 values (JSON...) + tmin = + T == Int64 ? 1000 * T(typemin(Int32)) : + T == UInt64 ? 1000 * T(typemin(UInt32)) : typemin(T) + tmax = + T == Int64 ? 1000 * T(typemax(Int32)) : + T == UInt64 ? 1000 * T(typemax(UInt32)) : typemax(T) + return T[32, tmin, tmax, 0] +end +function get_attribute_value(::Type{Vector{T}}) where {T<:Real} + return T[ + 32, + typemin(T), + typemax(T), + T(+0.0), + T(-0.0), + eps(T), + prevfloat(T(1)), + nextfloat(T(1)), + T(Inf), + T(-Inf), + T(NaN), + ] +end +function get_attribute_value(::Type{Vector{T}}) where {T<:Complex{<:Integer}} + return T[T(32, 33), T(typemin(real(T)), typemax(real(T))), T(0)] +end +function get_attribute_value(::Type{Vector{T}}) where {T<:Complex{<:Real}} + return T[ + T(32, 33), + T(typemin(real(T)), typemax(real(T))), + T(0), + T(+0.0, -0.0), + T(eps(real(T))), + T(prevfloat(real(T)(1)), nextfloat(real(T)(1))), + T(Inf, -Inf), + T(NaN), + ] +end + +function write_attributes(loc::Union{AG.AbstractGroup,AG.AbstractMDArray}) + for name in attribute_names + AG.writeattribute(loc, name, name) + end + for T in attribute_types + AG.writeattribute(loc, "$T", get_attribute_value(T)) + end + return nothing +end +function test_attributes(loc::Union{AG.AbstractGroup,AG.AbstractMDArray}) + for name in attribute_names + @test isequal(AG.readattribute(loc, name), name) + end + for T in attribute_types + @test isequal(AG.readattribute(loc, "$T"), get_attribute_value(T)) + end + return nothing +end + +@testset "test_mdarray.jl" begin + @testset "$drivername" for ( + drivername, + drivercreateoptions, + mdarraycreateoptions, + ) in mdarray_drivers + driver = AG.getdriver(drivername) + + @testset "interactive" begin + filename = tempname(; cleanup = false) + memory_dataset = nothing + + @testset "writing" begin + dataset = AG.createmultidimensional( + driver, + filename, + nothing, + drivercreateoptions, + ) + @test !AG.isnull(dataset) + @test match(r"^GDAL Dataset", string(dataset)) !== nothing + + files = AG.filelist(dataset) + if drivername in ["MEM"] + @test length(files) == 0 + elseif drivername in ["netCDF", "Zarr"] + @test length(files) == 1 + else + @assert false + end + + root = AG.getrootgroup(dataset) + @test !AG.isnull(root) + @test match(r"^ArchGDAL.Group", string(root)) !== nothing + rootname = AG.getname(root) + @test rootname == "/" + rootfullname = AG.getfullname(root) + @test rootfullname == "/" + + group = AG.creategroup(root, "group") + @test !AG.isnull(group) + @test match(r"^ArchGDAL.IGroup", string(group)) !== nothing + @test AG.getname(group) == "group" + @test AG.getfullname(group) == "/group" + + @test AG.getgroupnames(root) == ["group"] + @test AG.getgroupnames(group) == [] + + write_attributes(group) + + nx, ny = 3, 4 + dimx = AG.createdimension(group, "x", "", "", nx) + @test !AG.isnull(dimx) + @test match(r"^ArchGDAL.IDimension", string(dimx)) !== nothing + dimy = AG.createdimension(group, "y", "", "", ny) + @test !AG.isnull(dimy) + + datatype = AG.extendeddatatypecreate(Float32) + @test !AG.isnull(datatype) + @test match( + r"^ArchGDAL.IExtendedDataType", + string(datatype), + ) !== nothing + + if drivername != "netCDF" + # netCDF does not support deleting MDArrays + mdarray0 = AG.createmdarray( + group, + "mdarray0", + [dimx, dimy], + datatype, + mdarraycreateoptions, + ) + @test !AG.isnull(mdarray0) + success = AG.deletemdarray(group, "mdarray0") + @test success + end + + mdarray = AG.createmdarray( + group, + "mdarray", + (dimx, dimy), + datatype, + mdarraycreateoptions, + ) + @test !AG.isnull(mdarray) + @test match(r"^3×4 ArchGDAL.IMDArray", string(mdarray)) !== + nothing + + @test AG.getmdarraynames(root) == [] + @test AG.getmdarraynames(group) == ["mdarray"] + + @test AG.getvectorlayernames(root) == [] + @test AG.getvectorlayernames(group) == [] + + @test AG.getstructuralinfo(group) == [] + + # @test AG.iswritable(mdarray) + + data = Float32[x + 100 * y for x in 1:nx, y in 1:ny] + AG.write(mdarray, data) + + write_attributes(mdarray) + + AG.writemdarray(group, "primes", UInt8[2, 3, 5, 7, 251]) + + if drivername != "MEM" + err = AG.close(dataset) + @test err == GDAL.CE_None + else + memory_dataset = dataset + end + + # Trigger all finalizers + for i in 1:10 + GC.gc() + end + end + + @testset "reading" begin + if drivername != "MEM" + dataset = AG.open( + filename, + AG.OF_MULTIDIM_RASTER | + AG.OF_READONLY | + AG.OF_SHARED | + AG.OF_VERBOSE_ERROR, + nothing, + nothing, + nothing, + ) + else + dataset = memory_dataset + end + @test !AG.isnull(dataset) + + root = AG.getrootgroup(dataset) + @test !AG.isnull(root) + + group = AG.opengroup(root, "group") + @test !AG.isnull(group) + + test_attributes(group) + + mdarray = AG.openmdarray(group, "mdarray") + @test !AG.isnull(mdarray) + + # @test !AG.iswritable(mdarray) + + dimensions = AG.getdimensions(mdarray) + @test length(dimensions) == 2 + dimx, dimy = dimensions + @test all(!AG.isnull(dim) for dim in dimensions) + @test AG.getname(dimx) == "x" + @test AG.getname(dimy) == "y" + nx, ny = AG.getsize(dimx), AG.getsize(dimy) + @test (nx, ny) == (3, 4) + @test AG.getfullname(dimx) == "/group/x" + @test AG.gettype(dimx) == "" + @test AG.getdirection(dimx) == "" + @test_throws ErrorException AG.getindexingvariable(dimx) + # TODO: setindexingvariable! + # TODO: rename! + + mdarray1 = AG.openmdarrayfromfullname(root, "/group/mdarray") + @test !AG.isnull(mdarray1) + @test AG.getfullname(mdarray1) == "/group/mdarray" + @test_throws ErrorException AG.openmdarrayfromfullname( + root, + "/group/doesnotexist", + ) + mdarray2 = AG.resolvemdarray(group, "mdarray", "") + @test !AG.isnull(mdarray2) + @test AG.getfullname(mdarray2) == "/group/mdarray" + @test_throws ErrorException AG.resolvemdarray( + group, + "doesnotexist", + "", + ) + + group1 = AG.opengroupfromfullname(root, "/group") + @test !AG.isnull(group1) + @test AG.getfullname(group1) == "/group" + @test_throws ErrorException AG.opengroupfromfullname( + group, + "/doesnotexist", + ) + datatype = AG.getdatatype(mdarray) + @test !AG.isnull(datatype) + @test AG.getclass(datatype) == GDAL.GEDTC_NUMERIC + @test AG.getnumericdatatype(datatype) == AG.GDT_Float32 + + data = Array{Float32}(undef, nx, ny) + AG.read!(mdarray, data) + @test data == Float32[x + 100 * y for x in 1:nx, y in 1:ny] + + data = AG.read(mdarray) + @test data == Float32[x + 100 * y for x in 1:nx, y in 1:ny] + + test_attributes(mdarray) + + primes = AG.readmdarray(group, "primes") + @test primes == UInt8[2, 3, 5, 7, 251] + + err = AG.close(dataset) + @test err == GDAL.CE_None + + # Trigger all finalizers + for i in 1:10 + GC.gc() + end + end + end + + @testset "context handlers" begin + filename = tempname(; cleanup = false) + memory_dataset = nothing + + @testset "writing" begin + AG.createmultidimensional( + driver, + filename, + nothing, + drivercreateoptions, + ) do dataset + @test !AG.isnull(dataset) + + AG.getrootgroup(dataset) do root + @test !AG.isnull(root) + + rootname = AG.getname(root) + @test rootname == "/" + rootfullname = AG.getfullname(root) + @test rootfullname == "/" + + AG.creategroup(root, "group") do group + @test !AG.isnull(group) + @test AG.getname(group) == "group" + @test AG.getfullname(group) == "/group" + + @test AG.getgroupnames(root) == ["group"] + @test AG.getgroupnames(group) == [] + + write_attributes(group) + + nx, ny = 3, 4 + AG.createdimension(group, "x", "", "", nx) do dimx + @test !AG.isnull(dimx) + AG.createdimension( + group, + "y", + "", + "", + ny, + ) do dimy + @test !AG.isnull(dimy) + + AG.getdimensions(root) do dims + @test dims == [] + end + AG.getdimensions(group) do dimensions + @test length(dimensions) == 2 + end + + AG.extendeddatatypecreate( + Float32, + ) do datatype + @test !AG.isnull(datatype) + + if drivername != "netCDF" + # netCDF does not support deleting MDArrays + AG.createmdarray( + group, + "mdarray0", + [dimx, dimy], + datatype, + mdarraycreateoptions, + ) do mdarray0 + @test !AG.isnull(mdarray0) + end + success = AG.deletemdarray( + group, + "mdarray0", + ) + @test success + end + + AG.createmdarray( + group, + "mdarray", + (dimx, dimy), + datatype, + mdarraycreateoptions, + ) do mdarray + @test !AG.isnull(mdarray) + + @test AG.getmdarraynames(root) == [] + @test AG.getmdarraynames(group) == + ["mdarray"] + + @test AG.getvectorlayernames( + root, + ) == [] + @test AG.getvectorlayernames( + group, + ) == [] + + @test AG.getstructuralinfo(group) == + [] + + # @test AG.iswritable(mdarray) + + data = Float32[ + x + 100 * y for x in 1:nx, + y in 1:ny + ] + AG.write(mdarray, data) + + write_attributes(mdarray) + + AG.writemdarray( + group, + "primes", + UInt8[2, 3, 5, 7, 251], + ) + + return + end + end + end + end + end + end + end + + # Trigger all finalizers + for i in 1:10 + GC.gc() + end + end + + if drivername != "MEM" + @testset "reading" begin + AG.open( + filename, + AG.OF_MULTIDIM_RASTER | + AG.OF_READONLY | + AG.OF_SHARED | + AG.OF_VERBOSE_ERROR, + nothing, + nothing, + nothing, + ) do dataset + @test !AG.isnull(dataset) + + AG.getrootgroup(dataset) do root + @test !AG.isnull(root) + + AG.opengroup(root, "group") do group + @test !AG.isnull(group) + + test_attributes(group) + + AG.openmdarray(group, "mdarray") do mdarray + @test !AG.isnull(mdarray) + + # @test !AG.iswritable(mdarray) + + AG.getdimensions(mdarray) do dimensions + @test length(dimensions) == 2 + dimx, dimy = dimensions + @test all( + !AG.isnull(dim) for + dim in dimensions + ) + @test AG.getname(dimx) == "x" + @test AG.getname(dimy) == "y" + nx, ny = + AG.getsize(dimx), AG.getsize(dimy) + @test (nx, ny) == (3, 4) + @test AG.getfullname(dimx) == "/group/x" + @test AG.gettype(dimx) == "" + @test AG.getdirection(dimx) == "" + @test_throws ErrorException AG.getindexingvariable( + dimx, + ) do xvar + end + # TODO: setindexingvariable! + # TODO: rename! + + datatype = AG.getdatatype(mdarray) + @test !AG.isnull(datatype) + @test AG.getclass(datatype) == + GDAL.GEDTC_NUMERIC + @test AG.getnumericdatatype(datatype) == + AG.GDT_Float32 + + AG.openmdarrayfromfullname( + root, + "/group/mdarray", + ) do mdarray1 + @test !AG.isnull(mdarray1) + @test AG.getfullname(mdarray1) == + "/group/mdarray" + end + @test_throws ErrorException AG.openmdarrayfromfullname( + root, + "/group/doesnotexist", + ) do doesnotexist + end + + AG.resolvemdarray( + root, + "mdarray", + "", + ) do mdarray2 + @test !AG.isnull(mdarray2) + @test AG.getfullname(mdarray2) == + "/group/mdarray" + end + @test_throws ErrorException AG.resolvemdarray( + root, + "doesnotexist", + "", + ) do doesnotexist + end + + AG.opengroupfromfullname( + root, + "/group", + ) do group1 + @test !AG.isnull(group1) + @test AG.getfullname(group1) == + "/group" + end + @test_throws ErrorException AG.opengroupfromfullname( + root, + "/doesnotexist", + ) do doesnotexist + end + + data = Array{Float32}(undef, nx, ny) + AG.read!(mdarray, data) + @test data == Float32[ + x + 100 * y for x in 1:nx, y in 1:ny + ] + + data = AG.read(mdarray) + @test data == Float32[ + x + 100 * y for x in 1:nx, y in 1:ny + ] + + test_attributes(mdarray) + + primes = AG.readmdarray(group, "primes") + @test primes == UInt8[2, 3, 5, 7, 251] + + return + end + end + end + end + end + + # Trigger all finalizers + for i in 1:10 + GC.gc() + end + end + end + end + end +end