Changelog
Source:NEWS.md
guerrilla 0.4.0
Four articles instead of one long one
irreg2.Rmd was the whole package’s documentation, and it had grown to cover what a grid is, how barycentric coordinates work, eight interpolation methods, and what happens when you project the coordinates. Those are four things.
vignette("interpolating")is the tour: the eight methods on one dataset with one grid, so the pictures can be compared, ending with all of them on one page. It is the renamedirreg2, which is a name that told a reader nothing.vignette("grids")is what a grid is: four elements, where the cells are, why the cell size is not stored, and the converters to , and .vignette("triangulation")is the Delaunay and Voronoi tessellations drawn side by side,bary_weights()worked through, the readable R engine checking the C one,find_triangle()on its own, thefacets()story, and themesh3dcase.vignette("projection")is from the previous release.
New _pkgdown.yml groups the reference index by what things are for, with the superseded functions in their own section rather than mixed in with the rest.
README
Rewritten around a worked example and two figures: the same points interpolated two ways, and then a prediction next to its standard error. It says what the package is for in the first two lines, which the old one did not.
Deprecations, finished
defaultgrid() and tri_fun() warn and forward to grid_spec() and grid_barycentric(). facets() is superseded without a warning, since it still does what it always did and its help page now explains what that was. All three are grouped as superseded on the reference index.
as_gdalraster() documents that it returns the open GDALRaster, not a file name, and that the caller closes it. It always did; the help page did not say so, and the new grids article found that out the hard way.
Coordinates of any magnitude, and a bug that had been waiting
geometry::tsearch() builds a quadtree over the input points, and on some coordinate ranges the insertion fails outright with “Failed to insert point into QuadTree”. This package’s own transect, projected to metres on a local equal-area, is one of those ranges. So was the same data scaled by 1000. Scaled by 10000 it is fine, so it is not a threshold anyone can steer around.
Barycentric weights do not change when a triangle and a point inside it are translated and scaled together, so grid_barycentric() (and mesh_raster(), which shares the path) now centres and scales the coordinates before triangulating and searching. The answer is unaffected and the failure cannot happen. Qhull is happier for the same reason.
This only shows up once you leave degrees, which is a reason to try work in a projection even when nothing requires it.
GDAL’s own gridder
-
New
grid_gdal()runsgdal_gridand returns the result as a grid, so GDAL’s interpolators sit on the same footing as the R ones and can be compared directly. On the package’s own data:-
"linear:radius=0.0"andgrid_barycentric()agree to 7e-15, on the same cells; -
"nearest"andgrid_voronoi()are bitwise identical across 3000 cells, having arrived by completely different routes – a nearest point search in GDAL, a GEOS Voronoi tessellation here; -
"invdist:power=2.0"andgrid_idw()agree to 1e-4.
-
Two
gdal_griddefaults are worth knowing and are now documented and handled.lineardoes not stop at the convex hull: the defaultradiusof -1 is an infinite search, so a cell in no triangle silently takes its nearest point’s value. And cells it cannot estimate are filled with 0 and not tagged as no data, so nothing downstream can tell that 0 from a measurement.grid_gdal()setsnodata=nanunless the algorithm string already sets one.gdal_gridis reachable from R only throughsf::gdal_utils(). wrapswarp,translateandrasterizebut notGDALGrid, and the unifiedgdalcommand line added in GDAL 3.11 has no grid subcommand to wrap. That is the only reasonsfis in Suggests.
New article: coordinates, projections, and GDAL
Interpolating the transect in degrees and in a local equal-area gives surfaces that differ by up to a fifth of the range of the data, and cover different areas, because a straight line in one projection is not a straight line in another and the convex hull is made of straight lines. Neither is more correct and the interpolation cannot choose; the article is about making the question visible rather than settling it.
It also closes the loop on thin plate splines. grid_tps() fits a spline to (x, y) -> value; GDAL’s -tps warping fits one to (pixel, line) -> (x, y). Fitting fields::Tps() to the same sixteen ground control points GDAL was given, and comparing against which input pixel each warped cell actually came from, gives a mean offset of 0.001 pixels over 8631 cells. Georeferencing an image and interpolating a temperature field are the same operation.
The methods are functions now, not closures in a vignette
Six of the methods the vignette demonstrated existed only as closures inside it, each one wrapping its engine in raster::interpolate() and a bit of coercion. They are exported functions with the same signature as everything else: coordinates, values, a grid.
New
grid_bin(),grid_tps(),grid_idw(),grid_kriging(),grid_gam()andgrid_smooth(). Each takes(x, value, grid)and returns a grid, and each one is guarded on the Suggests it needs with an error that names the package.grid_tps(),grid_kriging()andgrid_gam()takestatistic = "se"and return the standard error surface on the same grid. This is the thing most of these methods can say and none of them were saying. The error surface next to the prediction is the whole argument for preferring a method that fits a model over a method that applies a rule, and it looks strikingly likegrid_bin(fun = length).grid_idw()has nostatistic, on purpose. Inverse distance weighting is a rule rather than a model, so there is nothing to be uncertain with, andidpis chosen rather than estimated. That is the point of having it next togrid_kriging().grid_kriging()stops when the variogram fit comes back with a negative range, which means the values have no spatial structure at these distances. It is the only method here that can refuse; every other one will interpolate pure noise and hand you a picture of it.grid_gam()takes aformula, defaulting tovalue ~ s(x, y)– one isotropic 2-D smooth, which is a thin plate regression spline, which isgrid_tps()with fewer basis functions. The vignette showsvalue ~ s(x) + s(y)beside it, because additive in x and y cannot put a feature in one place.grid_bin()is the honest baseline and answers the question the others answer silently:fundecides what happens when two points land in one cell.fun = lengthgives the count grid, which is the most useful picture in the vignette. It isvaster::cell_from_xy()andtapply(), and no more.
No sp, and no raster in the middle of anything
gstat takes plain data frames with the coordinates given as a formula (locations = ~ x + y), and fields and mgcv predict at a matrix of coordinates. So none of these methods needs a spatial class: the statistical engines never wanted one, they wanted coordinates and a predict method. The conversion functions are still there for handing a result to another package, but nothing in this package’s own path goes through them any more.
sp leaves Suggests, along with stars, dplyr and viridis, which nothing had used for some time.
Vignette
Rewritten again, onto the exported functions. The closures are gone, each section is now a call and an explanation of what the call assumed, and it ends with all eight surfaces on one page – which mostly shows that they agree where there is data and disagree where there is not.
Removed about 130 lines of commented-out code at the end, which used sp, maptools and spatstat interfaces that no longer exist.
Interpolation has a name, and the weights are visible
tri_fun() said what it was implemented with. The methods are now named for what they compute, and the arithmetic each one rests on is written out in R next to the fast path, so the vignette can show it rather than assert it.
New
grid_barycentric()replacestri_fun(), which is deprecated and still works. Same numbers, plus aduplicatesargument and anengineargument.New
bary_weights()computes barycentric weights for a triangle in six lines of arithmetic: the same three numbers are the point-in-triangle test and the interpolation rule, which is the whole idea. Newfind_triangle()locates points in a triangulation with geos, using an STRtree for candidates andbary_weights()to decide.grid_barycentric(engine = "R")runs the whole interpolation that way and agrees withgeometry::tsearch()to 4e-16 on the package’s own example data.New
grid_voronoi()fills a grid from the Voronoi tessellation, which is nearest neighbour – a tile is everywhere closer to one data point than to any other. Built ongeos::geos_voronoi_polygons().-
New
grid_facet_lm()fitsvalue ~ x + yinside each Delaunay triangle. Three points determine a plane, so this reproducesgrid_barycentric(); it exists because seeing that happen is the point.facets(method = "dirichlet")was not doing what it looked like. A Voronoi tile holds exactly one point, so the per-tile model had one observation and three parameters, fitted an intercept, and predicted that value across the tile. It was nearest neighbour computed the expensive way, and it is bitwise identical togrid_voronoi().facets()is kept, superseded and documented as such. mesh_raster()on amesh3dwas calling a helper that did not exist, so that path was broken. It now runs the second half ofgrid_barycentric()directly: the triangles are already there, so there is nothing to triangulate. There is a test for it that builds amesh3dby hand, rather than needing Rvcg installed to find out.
Input is coordinates, not a class
New
xyz_input()reads points from anything wk understands –wk::xy(),wk::xyz(), sf and sfc columns, a matrix, a data frame – and returns coordinates, values and a coordinate reference system. Where azis present it is the value being interpolated, sowk::xyz(x, y, z)is a complete argument togrid_barycentric()on its own.A coordinate reference system carried in on the input is carried out on the grid. Nothing in the interpolation looks at it: the arithmetic is planar, and it is the caller’s business whether that is reasonable for their coordinates.
wk and geos join Imports.
The grid is a list now
Everything that returned a RasterLayer returns a guerrilla_grid, which is a list of four things and nothing else: dimension (ncol, nrow), extent (xmin, xmax, ymin, ymax), crs, and values. str() on one is the complete explanation of what a raster is, which is the point.
New
grid_spec()builds one, with optionalpadand no coordinate system unless you ask for one. It supersedesdefaultgrid(), which is deprecated.New
grid_xy(),grid_ncell(),grid_res(),is_grid(), andprint(),plot(),as.matrix(),as.data.frame()anddim()methods.plot()draws the grid withgraphics::image(), no raster class involved.New
as_raster(),as_terra(),as_gdalraster()to hand a grid to another package, andas_grid()to read one back.tri_fun()andmesh_raster()still accept aRasterLayerorSpatRasteras the target.Cell arithmetic now goes through vaster, which does it in base R.
rasterandspleave Imports; the only hard dependencies left aregeometryandvaster.library(guerrilla)no longer loads raster at all.data(bathy)was a serializedRasterLayer, so the dataset alone made raster a runtime requirement whatever the code did. It is a grid now, and loads with no other package present.
The numbers did not change. tri_fun() and mesh_raster() produce values identical to 0.2.0 on volcano, on the zooplankton transect, and on quakes.
Repeated coordinates
A triangulation cannot hold two values at one place, and Qhull’s answer was to drop points and warn about it. tri_fun() and mesh_raster() now combine them first, with duplicates = mean by default, and say how many they combined. duplicates = NULL restores the old behaviour exactly. New collapse_duplicates() does it on its own.
Coordinates that are all on one line make Qhull return zero triangles and only warn; that now raises an error that says what happened.
Vignette
Rewritten onto the new grid, converting to a raster only where a third-party function needs one – which makes the boundary between this package and the statistical engines visible, rather than incidental.
Getting the package to build again
-
The vignette builds again. Two things were stopping it:
as(tess, "SpatialPolygons")was a coercion registered by maptools, so both tessellation plots died once maptools went away. They now use spatstat’s ownplot.tess(do.col = TRUE), which needs no sp at all and gets a colour ribbon for free.interp::interp()returns a grid of allNA, without complaint, whenyois descending – andraster::yFromRow()is top-down. akima tolerated it. The vignette’sakifun()now sorts the axis. The fixed version agrees withtri_fun()to 9e-16, which it should: both are linear interpolation over a Delaunay triangulation.
defaultgrid()no longer asserts a longitude/latitude coordinate reference system by default. Nothing in this package requires geographic input, so the default was wrong for most uses – including this package’s owntri_fun()example, which interpolatedvolcanoand returned a grid labelled WGS84 degrees. Passprjexplicitly when the coordinates really are in a known system.Removed
tri_pip(), which was superseded bygeometry::tsearch()and had been unused since 2019. With it goes the dependency on sp.geometrymoved from Suggests to Imports:tri_fun()andmesh_raster()cannot work without it.akimareplaced byinterpin Suggests and in the vignette. akima’s licence is non-commercial; interp is GPL and covers the same cases.The
bathydocumentation described a polygon layer. It is a raster.Both GitHub Actions workflows rebuilt from the current r-lib/actions templates; the old ones pinned actions and runner images that no longer exist.
inst/extdata/datamess.matremoved (referenced nowhere), and the SAZ-Sense transect compressed, taking installed data from 1.2 MB to 103 kB.Remove maptools
guerrilla 0.2.0
Tiny release to bump the universe.
New function
mesh_raster()a generalization oftri_fun()now also works with ‘mesh3d’ input.
guerrilla 0.1.0
Removed Suggests for rgdal in favour of reproj package.
Yuge speed up to
tri_funby usinggeometry::tsearch.