diff --git a/Project.toml b/Project.toml index d8148c9d..fd6d623a 100644 --- a/Project.toml +++ b/Project.toml @@ -23,7 +23,7 @@ CEnum = "0.4" ColorTypes = "0.10, 0.11" DiskArrays = "0.3" Extents = "0.1" -GDAL = "1.5" +GDAL = "1.5.1" GeoFormatTypes = "0.3, 0.4" GeoInterface = "1" GeoInterfaceRecipes = "1.0" diff --git a/src/dataset.jl b/src/dataset.jl index 05a50b85..741c7a68 100644 --- a/src/dataset.jl +++ b/src/dataset.jl @@ -1,6 +1,6 @@ """ copywholeraster(source::AbstractDataset, dest::AbstractDataset; - ) + options::StringList, progressfunc::Function) Copy all dataset raster data. @@ -14,6 +14,9 @@ pixel interleaved operation and `\"COMPRESSED=YES\"` to force alignment on target dataset block sizes to achieve best compression. More options may be supported in the future. +For progress reporting one can pass `progressfunc` function(::Float64, ::String)::Bool +to call to report progress. + ### Additional Remarks This function is primarily intended to support implementation of driver specific `createcopy()` functions. It implements efficient copying, in @@ -24,15 +27,14 @@ function copywholeraster!( source::AbstractDataset, dest::D; options = StringList(C_NULL), - progressfunc::Function = GDAL.gdaldummyprogress, - progressdata::Any = C_NULL, + progressfunc::Function = _dummyprogress, )::D where {D<:AbstractDataset} result = GDAL.gdaldatasetcopywholeraster( source, dest, options, - @cplprogress(progressfunc), - progressdata, + @cfunction(_progresscallback, Cint, (Cdouble, Cstring, Ptr{Cvoid})), + progressfunc, ) @cplerr result "Failed to copy whole raster" return dest @@ -56,10 +58,11 @@ provided template dataset. * `filename` the filename for the new dataset. UTF-8 encoded. * `driver` the driver to use for creating the new dataset * `strict` ``true`` if the copy must be strictly equivalent, or more -normally ``false`` if the copy may adapt as needed for the output format. + normally ``false`` if the copy may adapt as needed for the output format. * `options` additional format dependent options controlling creation -of the output file. `The APPEND_SUBDATASET=YES` option can be specified to -avoid prior destruction of existing dataset. + of the output file. `The APPEND_SUBDATASET=YES` option can be specified to + avoid prior destruction of existing dataset. +* `progressfunc` a function(::Float64, ::String)::Bool to call to report progress ### Returns a pointer to the newly created dataset (may be read-only access). @@ -93,8 +96,7 @@ function unsafe_copy( driver::Driver = getdriver(dataset), strict::Bool = false, options = StringList(C_NULL), - progressfunc::Function = GDAL.gdaldummyprogress, - progressdata = C_NULL, + progressfunc::Function = _dummyprogress, )::Dataset return Dataset( GDAL.gdalcreatecopy( @@ -103,8 +105,8 @@ function unsafe_copy( dataset, strict, options, - @cplprogress(progressfunc), - progressdata, + @cfunction(_progresscallback, Cint, (Cdouble, Cstring, Ptr{Cvoid})), + progressfunc, ), ) end @@ -126,10 +128,11 @@ provided template dataset. * `filename` the filename for the new dataset. UTF-8 encoded. * `driver` the driver to use for creating the new dataset * `strict` ``true`` if the copy must be strictly equivalent, or more - normally ``false`` if the copy may adapt as needed for the output format. + normally ``false`` if the copy may adapt as needed for the output format. * `options` additional format dependent options controlling creation -of the output file. `The APPEND_SUBDATASET=YES` option can be specified to -avoid prior destruction of existing dataset. + of the output file. `The APPEND_SUBDATASET=YES` option can be specified to + avoid prior destruction of existing dataset. +* `progressfunc` a function(::Float64, ::String)::Bool to call to report progress ### Example ``` @@ -152,8 +155,7 @@ function copy( driver::Driver = getdriver(dataset), strict::Bool = false, options = StringList(C_NULL), - progressfunc::Function = GDAL.gdaldummyprogress, - progressdata = C_NULL, + progressfunc::Function = _dummyprogress, )::IDataset return IDataset( GDAL.gdalcreatecopy( @@ -162,8 +164,8 @@ function copy( dataset, strict, options, - @cplprogress(progressfunc), - progressdata, + @cfunction(_progresscallback, Cint, (Cdouble, Cstring, Ptr{Cvoid})), + progressfunc, ), ) end @@ -931,7 +933,7 @@ end """ buildoverviews!(dataset::AbstractDataset, overviewlist::Vector{Cint}; - bandlist, resampling="NEAREST", progressfunc, progressdata) + bandlist, resampling="NEAREST", progressfunc) Build raster overview(s). @@ -946,16 +948,14 @@ returned, and CPLGetLastErrorNo() will return CPLE_NotSupported. * `sampling` one of "NEAREST" (default), "GAUSS","CUBIC","AVERAGE","MODE", "AVERAGE_MAGPHASE" or "NONE" controlling the downsampling method applied. -* `progressfunc` a function to call to report progress, or `NULL`. -* `progressdata` application data to pass to the progress function. +* `progressfunc` a function(::Float64, ::String)::Bool to call to report progress """ function buildoverviews!( dataset::T, overviewlist::Vector{Cint}; bandlist::Vector{Cint} = Cint[], resampling::AbstractString = "NEAREST", - progressfunc::Function = GDAL.gdaldummyprogress, - progressdata = C_NULL, + progressfunc::Function = _dummyprogress, )::T where {T<:AbstractDataset} result = GDAL.gdalbuildoverviews( dataset, @@ -964,8 +964,8 @@ function buildoverviews!( overviewlist, length(bandlist), bandlist, - @cplprogress(progressfunc), - progressdata, + @cfunction(_progresscallback, Cint, (Cdouble, Cstring, Ptr{Cvoid})), + progressfunc, ) @cplerr result "Failed to build overviews" return dataset diff --git a/src/raster/rasterband.jl b/src/raster/rasterband.jl index acb3c876..e8bdd31e 100644 --- a/src/raster/rasterband.jl +++ b/src/raster/rasterband.jl @@ -257,7 +257,7 @@ end """ copywholeraster!( source::AbstractRasterBand, dest::AbstractRasterBand; - [options, [progressdata, [progressfunc]]]) + [options, [progressfunc]]) Copy all raster band raster data. @@ -276,22 +276,20 @@ More options may be supported in the future. * `source` the source band * `dest` the destination band * `options` transfer hints in "StringList" Name=Value format. -* `progressfunc` progress reporting function. -* `progressdata` callback data for progress function. +* `progressfunc` a function(::Float64, ::String)::Bool to call to report progress """ function copywholeraster!( source::T, dest::AbstractRasterBand; options = StringList(C_NULL), - progressdata = C_NULL, - progressfunc::Function = GDAL.gdaldummyprogress, + progressfunc::Function = _dummyprogress, )::T where {T<:AbstractRasterBand} result = GDAL.gdalrasterbandcopywholeraster( source, dest, options, - @cplprogress(progressfunc), - progressdata, + @cfunction(_progresscallback, Cint, (Cdouble, Cstring, Ptr{Cvoid})), + progressfunc, ) @cplerr result "Failed to copy whole raster" return source @@ -414,8 +412,7 @@ images in one file from another outside the overview architecture. ### Keyword Arguments * `resampling` (optional) Resampling algorithm (eg. "AVERAGE"). default to "NEAREST". -* `progressfunc` (optional) progress report function. -* `progressdata` (optional) progress function callback data. +* `progressfunc` (optional) a function(::Float64, ::String)::Bool to call to report progress ### Additional Remarks The output bands need to exist in advance. @@ -429,18 +426,15 @@ function regenerateoverviews!( band::T, overviewbands::Vector{<:AbstractRasterBand}, resampling::AbstractString = "NEAREST", - # progressfunc::Function = GDAL.gdaldummyprogress, - progressdata = C_NULL, + progressfunc::Function = _dummyprogress, )::T where {T<:AbstractRasterBand} - cfunc = - @cfunction(GDAL.gdaldummyprogress, Cint, (Cdouble, Cstring, Ptr{Cvoid})) result = GDAL.gdalregenerateoverviews( band, length(overviewbands), overviewbands, resampling, - cfunc, - progressdata, + @cfunction(_progresscallback, Cint, (Cdouble, Cstring, Ptr{Cvoid})), + progressfunc, ) @cplerr result "Failed to regenerate overviews" return band diff --git a/src/utils.jl b/src/utils.jl index 09961e88..9e59f7ce 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -228,25 +228,41 @@ macro cplwarn(code, message) end end -macro cplprogress(progressfunc) - @static if Sys.ARCH == :aarch64 - @warn "User provided progress functions are unsupported on this architecture." - quote - @cfunction( - $(Expr(:$, esc(GDAL.gdaldummyprogress))), - Cint, - (Cdouble, Cstring, Ptr{Cvoid}) - ) - end - else - quote - @cfunction( - $(Expr(:$, esc(progressfunc))), - Cint, - (Cdouble, Cstring, Ptr{Cvoid}) - ) - end - end +""" +Default progress function, which logs no progress. +A progress function should return true to continue, or false to abort. +""" +function _dummyprogress(progress::Cdouble, message = "")::Bool + return true +end + +""" + _progresscallback(dfComplete::Cdouble, pszMessage::Cstring, pProgressArg::Ptr{Cvoid})::Cint + +The progress callback function to be passed to GDAL. Users can provide their own progress function by +passing a function of the form `f(dfComplete::Cdouble, pszMessage::Cstring)::Bool` to the +`progressfunc` keyword argument of the functions that implement it, which should be wrapped in a `Ref`. + +In essence, we pass a pointer to the (user-provided) progress function to GDAL, and GDAL passes it back to us, +including the progress ratio and message. We then call the user-provided function with these arguments. + +### Parameters +* `dfComplete` – completion ratio from 0.0 to 1.0. +* `pszMessage` – optional message (made available as an argument to the user-defined progress function). +* `pProgressArg` – callback data argument (i.e. user-defined progress function, see `_dummyprogress`). +""" +function _progresscallback( + dfComplete::Cdouble, + pszMessage::Cstring, + pProgressArg::Ptr{Cvoid}, # A pointer to Ref(_dummyprogress) by default +)::Cint + pProgressArg == C_NULL && return true + # User provided functions are wrapped in a Ref so the conversion to Ptr{Cvoid} works + # Here we do the reverse, load the pointer and unwrap the ref, so we get the function. + f = unsafe_pointer_to_objref(pProgressArg) + isa(f, Function) || return true + pszMessage == C_NULL && return f(dfComplete)::Bool + return f(dfComplete, unsafe_string(pszMessage))::Bool end # """ diff --git a/test/test_dataset.jl b/test/test_dataset.jl index c86cdcfd..4b98fab2 100644 --- a/test/test_dataset.jl +++ b/test/test_dataset.jl @@ -29,6 +29,12 @@ end @testset "Test methods for raster dataset" begin AG.read("data/utmsmall.tif") do dataset @testset "Method 1" begin + io = IOBuffer() + function showprogress(progress, message = "") + print(io, round(Int, progress*100)) + return true + end + AG.copy( dataset, filename = "/vsimem/utmcopy.tif", @@ -38,7 +44,13 @@ end @test AG.noverview(band) == 0 AG.buildoverviews!(copydataset, Cint[2, 4, 8]) @test AG.noverview(band) == 3 - AG.copywholeraster!(dataset, copydataset) + AG.copywholeraster!( + dataset, + copydataset, + progressfunc = showprogress, + ) + seek(io, 0) + @test occursin("100", String(readavailable(io))) return nothing end end