Interactive versus Scoped Objects
ArchGDAL provides two approaches for working with GDAL objects.
The first approach is through Scoped Objects, which uses do
-blocks as context managers. The problem with using do-blocks to manage context is that they are difficult to work with in an interactive way:
ArchGDAL.read(filename) do dataset
# dataset exists within this scope
end
# we do not have access to dataset from here on
In the example above, we do not get to see information about dataset
unless we write code to display information within the scope of the do-block. This makes it difficult to work with it in an exploratory "depth-first" manner.
The second approach is through Interactive Objects, which are designed for use at the REPL.
dataset = ArchGDAL.read(filename)
# work with dataset
A potential drawback of the second approach is that the objects are managed by Julia's garbage collector. This requires ArchGDAL to keep track of objects that interactive objects have a relationship with so that the interactive objects are not prematurely destroyed. For safety, ArchGDAL might make clones/copies of the underlying data, and only allow a subset of GDAL's objects to be created in this way.
Memory Management (Advanced)
Unlike the design of fiona, ArchGDAL
does not immediately make copies from data sources. This introduces concerns about memory management (whether objects should be managed by Julia's garbage collector, or by other means of destroying GDAL object when they are out of scope).
Scoped Objects
For scoped objects, they are often created within the context of a do-block. As an example, the following code block
ArchGDAL.getband(dataset, i) do rasterband
# do something with rasterband
end
corresponds to the following sequence of function calls:
rasterband = ArchGDAL.unsafe_getband(dataset, i)
try
# do something with rasterband
finally
ArchGDAL.destroy(rasterband)
end
under the hood (see src/context.jl
). Therefore, the objects themselves do not have a finalizer registered:
mutable struct RasterBand <: AbstractRasterBand
ptr::GDAL.GDALRasterBandH
end
unsafe_getband(dataset::AbstractDataset, i::Integer) =
RasterBand(GDAL.getrasterband(dataset, i))
function destroy(rb::AbstractRasterBand)
rb.ptr = C_NULL
end
We use the unsafe_
prefix to indicate those methods that return scoped objects. These methods should not be used by users directly.
Interactive Objects
By contrast, the following code
rasterband = ArchGDAL.getband(dataset, i)
# do something with rasterband
returns an interactive rasterband that has destroy()
registered with its finalizer.
mutable struct IRasterBand <: AbstractRasterBand
ptr::GDAL.GDALRasterBandH
ownedby::AbstractDataset
function IRasterBand(
ptr::GDAL.GDALRasterBandH = C_NULL;
ownedby::AbstractDataset = Dataset()
)
rasterband = new(ptr, ownedby)
finalizer(destroy, rasterband)
return rasterband
end
end
getband(dataset::AbstractDataset, i::Integer) =
IRasterBand(GDAL.getrasterband(dataset, i), ownedby = dataset)
function destroy(rasterband::IRasterBand)
rasterband.ptr = C_NULL
rasterband.ownedby = Dataset()
return rasterband
end
The I
in IRasterBand
indicates that it is an [i]nteractive type. Other interactive types include IDataset
, IFeatureLayer
, ISpatialRef
and IGeometry
.
ArchGDAL requires all interactive types to have a finalizer that calls destroy()
on them. All objects that have a relationship with an interactive object are required to hold a reference to the interactive object. For example, objects of type IRasterBand
might have a relationship with an IDataset
, therefore they have an ownedby
attribute which might refer to such a dataset.
Views
Sometimes, it is helpful to work with objects that are "internal references" that have restrictions on the types of methods that they support. As an example layerdefn(featurelayer)
returns a feature definition that is internal to the feature layer, and does not support methods such as write!(featuredefn, fielddefn)
and deletegeomdefn!(featuredefn, i)
. To indicate that they might have restrictions, some types have View
as a postfix. Such types include IFeatureDefnView
, IFieldDefnView
, and IGeomFieldDefnView
.
Summary
To summarize,
ArchGDAL.unsafe_<method>(args...)
will return a scoped object. The proper way of using them is within the setting of a do-block:
ArchGDAL.<method>(args...) do result
# result is a scoped object
end
ArchGDAL.<method>(args...)
will return an interactive object.
result = ArchGDAL.<method>(args...)
# result is an interactive object
Users are allowed to mix both "interactive" and "scoped" objects. As long as they do not manually call ArchGDAL.destroy()
on any object, ArchGDAL is designed to avoid the pitfalls of GDAL memory management (e.g. in Python Gotchas).
References
Here's a collection of references for developers who are interested.
- https://docs.julialang.org/en/v1/manual/calling-c-and-fortran-code/
- https://github.com/JuliaLang/julia/issues/7721
- https://github.com/JuliaLang/julia/issues/11207
- https://gdal.org/api/python_gotchas.html
- https://lists.osgeo.org/pipermail/gdal-dev/2010-September/026027.html
- https://sgillies.net/2013/12/17/teaching-python-gis-users-to-be-more-rational.html
- https://pcjericks.github.io/py-gdalogr-cookbook/gotchas.html#features-and-geometries-have-a-relationship-you-don-t-want-to-break