Skip to content

Commit

Permalink
divide
Browse files Browse the repository at this point in the history
  • Loading branch information
rhijmans committed Dec 21, 2024
1 parent d97e8dd commit 684eba0
Show file tree
Hide file tree
Showing 8 changed files with 308 additions and 3 deletions.
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ Authors@R: c(
person("Emanuele", "Cordano", role="ctb",comment=c(ORCID="0000-0002-3508-5898")),
person("Krzysztof", "Dyba", role="ctb", comment=c(ORCID="0000-0002-8614-3816")),
person("Edzer", "Pebesma", role="ctb", comment=c(ORCID="0000-0001-8049-7069")),
person("Barry", "Rowlingson", role="ctb"),
person("Michael D.", "Sumner", role="ctb"))

2 changes: 1 addition & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
useDynLib(terra, .registration=TRUE)
import(methods, Rcpp)
exportClasses(SpatExtent, SpatRaster, SpatRasterDataset, SpatRasterCollection, SpatVector, SpatVectorProxy, SpatVectorCollection)
exportMethods("[", "[[", "!", "%in%", activeCat, "activeCat<-", "add<-", addCats, adjacent, all.equal, aggregate, allNA, align, animate, anyNA, app, Arith, approximate, as.bool, as.int, as.contour, as.lines, as.points, as.polygons, as.raster, as.array, as.data.frame, as.factor, as.list, as.logical, as.matrix, as.numeric, atan2, atan_2, autocor, barplot, blocks, boundaries, boxplot, buffer, cartogram, categories, cats, catalyze, clamp, clamp_ts, classify, clearance, cellSize, cells, cellFromXY, cellFromRowCol, cellFromRowColCombine, centroids, click, bestMatch, colFromX, colFromCell, colorize, coltab, "coltab<-", combineGeoms, compare, concats, Compare, compareGeom, contour, convHull, countNA, costDist, crds, cover, crop, crosstab, crs, "crs<-", datatype, deepcopy, delaunay, densify, density, depth, "depth<-", describe, diff, disagg, direction, distance, dots, draw, droplevels, edges, elongate, emptyGeoms, erase, extend, ext, "ext<-", extract, extractRange, expanse, fillHoles, fillTime, flip, focal, focal3D, focalPairs, focalReg, focalCpp, focalValues, forceCCW, freq, gaps, geom, geomtype, getTileExtents, global, gridDist, has.colors, has.RGB, has.time, hasMinMax, hasValues, hist, head, identical, ifel, impose, init, image, inext, interpIDW, interpNear, inMemory, inset, interpolate, intersect, is.bool, is.int, is.lonlat, is.rotated, isTRUE, isFALSE, is.empty, is.factor, is.lines, is.points, is.polygons, is.related, is.valid, k_means, lapp, layerCor, levels, "levels<-", linearUnits, lines, Logic, varnames, "varnames<-", logic, longnames, "longnames<-", makeValid, mask, match, math, Math, Math2, mean, median, meta, merge, mergeLines, mergeTime, minCircle, minmax, minRect, modal, mosaic, na.omit, not.na, NAflag, "NAflag<-", nearby, nearest, ncell, ncol, "ncol<-", nlyr, "nlyr<-", noNA, normalize.longitude, nrow, "nrow<-", nsrc, origin, "origin<-", pairs, panel, patches, perim, persp, plot, plotRGB, plet, prcomp, princomp, RGB, "RGB<-", polys, points, predict, project, quantile, query, rangeFill, rapp, rast, rasterize, rasterizeGeom, rasterizeWin, readStart, readStop, readValues, rectify, regress, relate, removeDupNodes, res, "res<-", resample, rescale, rev, rcl, roll, rotate, rowFromY, rowColCombine, rowColFromCell, rowFromCell, sapp, scale, scale_linear, scoff, "scoff<-", sds, sort, sprc, sel, selectRange, setMinMax, setValues, segregate, selectHighest, set.cats, set.crs, set.ext, set.names, set.RGB, set.values, size, sharedPaths, shift, sieve, simplifyGeom, snap, sources, spatSample, split, spin, stdev, stretch, subset, subst, summary, Summary, surfArea, svc, symdif, t, metags, "metags<-", tail, tapp, terrain, tighten, makeNodes, makeTiles, time, timeInfo, "time<-", text, toMemory, trans, trim, units, union, "units<-", unique, unwrap, update, vect, values, "values<-", viewshed, voronoi, vrt, weighted.mean, where.min, where.max, which.lyr, which.min, which.max, which.lyr, width, window, "window<-", writeCDF, writeRaster, wrap, wrapCache, writeStart, writeStop, writeVector, writeValues, xmin, xmax, "xmin<-", "xmax<-", xres, xFromCol, xyFromCell, xFromCell, ymin, ymax, "ymin<-", "ymax<-", yres, yFromCell, yFromRow, zonal, zoom, cbind2, readRDS, saveRDS, unserialize, serialize, xapp, area, gridDistance, colSums, rowSums, colMeans, rowMeans)
exportMethods("[", "[[", "!", "%in%", activeCat, "activeCat<-", "add<-", addCats, adjacent, all.equal, aggregate, allNA, align, animate, anyNA, app, Arith, approximate, as.bool, as.int, as.contour, as.lines, as.points, as.polygons, as.raster, as.array, as.data.frame, as.factor, as.list, as.logical, as.matrix, as.numeric, atan2, atan_2, autocor, barplot, blocks, boundaries, boxplot, buffer, cartogram, categories, cats, catalyze, clamp, clamp_ts, classify, clearance, cellSize, cells, cellFromXY, cellFromRowCol, cellFromRowColCombine, centroids, click, bestMatch, colFromX, colFromCell, colorize, coltab, "coltab<-", combineGeoms, compare, concats, Compare, compareGeom, contour, convHull, countNA, costDist, crds, cover, crop, crosstab, crs, "crs<-", datatype, deepcopy, delaunay, densify, density, depth, "depth<-", describe, diff, disagg, direction, distance, divide, dots, draw, droplevels, edges, elongate, emptyGeoms, erase, extend, ext, "ext<-", extract, extractRange, expanse, fillHoles, fillTime, flip, focal, focal3D, focalPairs, focalReg, focalCpp, focalValues, forceCCW, freq, gaps, geom, geomtype, getTileExtents, global, gridDist, has.colors, has.RGB, has.time, hasMinMax, hasValues, hist, head, identical, ifel, impose, init, image, inext, interpIDW, interpNear, inMemory, inset, interpolate, intersect, is.bool, is.int, is.lonlat, is.rotated, isTRUE, isFALSE, is.empty, is.factor, is.lines, is.points, is.polygons, is.related, is.valid, k_means, lapp, layerCor, levels, "levels<-", linearUnits, lines, Logic, varnames, "varnames<-", logic, longnames, "longnames<-", makeValid, mask, match, math, Math, Math2, mean, median, meta, merge, mergeLines, mergeTime, minCircle, minmax, minRect, modal, mosaic, na.omit, not.na, NAflag, "NAflag<-", nearby, nearest, ncell, ncol, "ncol<-", nlyr, "nlyr<-", noNA, normalize.longitude, nrow, "nrow<-", nsrc, origin, "origin<-", pairs, panel, patches, perim, persp, plot, plotRGB, plet, prcomp, princomp, RGB, "RGB<-", polys, points, predict, project, quantile, query, rangeFill, rapp, rast, rasterize, rasterizeGeom, rasterizeWin, readStart, readStop, readValues, rectify, regress, relate, removeDupNodes, res, "res<-", resample, rescale, rev, rcl, roll, rotate, rowFromY, rowColCombine, rowColFromCell, rowFromCell, sapp, scale, scale_linear, scoff, "scoff<-", sds, sort, sprc, sel, selectRange, setMinMax, setValues, segregate, selectHighest, set.cats, set.crs, set.ext, set.names, set.RGB, set.values, size, sharedPaths, shift, sieve, simplifyGeom, snap, sources, spatSample, split, spin, stdev, stretch, subset, subst, summary, Summary, surfArea, svc, symdif, t, metags, "metags<-", tail, tapp, terrain, tighten, makeNodes, makeTiles, time, timeInfo, "time<-", text, toMemory, trans, trim, units, union, "units<-", unique, unwrap, update, vect, values, "values<-", viewshed, voronoi, vrt, weighted.mean, where.min, where.max, which.lyr, which.min, which.max, which.lyr, width, window, "window<-", writeCDF, writeRaster, wrap, wrapCache, writeStart, writeStop, writeVector, writeValues, xmin, xmax, "xmin<-", "xmax<-", xres, xFromCol, xyFromCell, xFromCell, ymin, ymax, "ymin<-", "ymax<-", yres, yFromCell, yFromRow, zonal, zoom, cbind2, readRDS, saveRDS, unserialize, serialize, xapp, area, gridDistance, colSums, rowSums, colMeans, rowMeans)

exportMethods(watershed, pitfinder, NIDP, flowAccumulation)

Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
- `$<SpatRaster>` can now be used to get a categorical SpatRaster with a different active category
- `scale_linear<SpatRaster>` method for linear scaling of cell values between a minumum and maximum value such as 0 and 1
- `distance` and related methods get argument "method" to choose the distance algorithm for lon/lat data [#1677](https://github.com/rspatial/terra/issues/1677) by Márcia Barbosa
- `divide<SpatRaster>` and `divide<SpatVector>` methods


# version 1.8-5
Expand Down
2 changes: 2 additions & 0 deletions R/Agenerics.R
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ if (!isGeneric("NIDP")) {setGeneric("NIDP", function(x, ...) standardGeneric("NI
if (!isGeneric("flowAccumulation")) {setGeneric("flowAccumulation", function(x, ...) standardGeneric("flowAccumulation"))}


if (!isGeneric("divide")) {setGeneric("divide", function(x, ...) standardGeneric("divide"))}

if (!isGeneric("bestMatch")) {setGeneric("bestMatch", function(x, y, ...) standardGeneric("bestMatch"))}
if (!isGeneric("k_means")) {setGeneric("k_means", function(x, ...) standardGeneric("k_means"))}
if (!isGeneric("princomp")) {setGeneric("princomp", function(x, ...) standardGeneric("princomp"))}
Expand Down
239 changes: 239 additions & 0 deletions R/divide.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
# Author: Robert Hijmans
# December 2024
# License GPL3


getv <- function(x, a) {
cumsum(values(aggregate(x, a, sum, na.rm=TRUE)) / 1000)
}

splitNS3 <- function(x) {
v <- getv(x, c(1, ncol(x)))
m1 <- which.min(abs(v - max(v)/3))
m2 <- which.min(abs(v - max(v) * 2/3))
list(n=x[1:m1, ,drop=FALSE], m=x[(m1+1):m2, ,drop=FALSE], s=x[(m2+1):nrow(x), ,drop=FALSE])
}


splitNS <- function(x) {
v <- getv(x, c(1, ncol(x)))
m <- which.min(abs(v - max(v)/2))
list(n=x[1:m, ,drop=FALSE], s=x[(m+1):nrow(x), ,drop=FALSE])
}

splitWE3 <- function(x) {
v <- getv(x, c(nrow(x), 1))
m1 <- which.min(abs(v - max(v)/3))
m2 <- which.min(abs(v - max(v) * 2/3))
list(w=x[, 1:m1, drop=FALSE], m=x[, (m1+1):m2, drop=FALSE], e=x[, (m2+1):ncol(x), drop=FALSE])
}


splitWE <- function(x) {
v <- getv(x, c(nrow(x), 1))
m <- which.min(abs(v - max(v)/2))
list(w=x[, 1:m, drop=FALSE], e=x[, (m+1):ncol(x), drop=FALSE])
}


setMethod("divide", signature(x="SpatRaster"),
function(x, n=2, start="ns", as.raster=FALSE, na.rm=TRUE) {

# if (!is.null(border)) stopifnot(inherits(border, "SpatVector"))

if (nlyr(x) > 1) {
warn("divide", "only the first layer is used")
x <- x[[1]]
}

n <- round(n)
if (length(n) > 1) {
if (!all(n %in% c(-2,-1,1,2))) {
error("divide", "if (length(n) > 1), values can only be -3, -2 for WE and 2, 3 for NS")
}
} else {
if (!isTRUE(n > 0)) {
error("divide", "n must be > 0")
}
start <- match.arg(tolower(start), c("ns", "ew"))
north <- start == "ns"
}

if (!hasValues(x)) {
out <- x
if (length(n) > 1) {
nrow(out) <- sum(abs(n[n<0]) + 1)
ncol(out) <- sum(abs(n[n<0]) + 1)
} else {
if (north) {
rows <- ceiling(n / 2)
cols <- n - rows
} else {
cols <- ceiling(n / 2)
rows <- n - cols
}
nrow(out) <- rows * 2
ncol(out) <- cols * 2
}
out <- as.polygons(out)
} else {

x <- classify(trim(x), cbind(NA, 0))
out <- list(x)
if (length(n) > 1) {
for (i in 1:length(n)) {
if (n[i] == 1) {
out <- unlist(lapply(out, \(i) splitNS(i)))
} else if (n[i] == -1) {
out <- unlist(lapply(out, \(i) splitWE(i)))
} else if (n[i] == 2) {
out <- unlist(lapply(out, \(i) splitNS3(i)))
} else { # if (n[i] == -2) {
out <- unlist(lapply(out, \(i) splitWE3(i)))
}
}
} else {
for (i in 1:n) {
if (north) {
out <- unlist(lapply(out, \(s) splitNS(s)))
} else {
out <- unlist(lapply(out, \(s) splitWE(s)))
}
north <- !north
}
}
out <- lapply(out, \(i) as.polygons(ext(i))) |> vect()
crs(out) <- crs(out)
}
out$zones <- 1:nrow(out)

if (na.rm) {
border <- as.polygons(not.na(x), TRUE)
out <- crop(out, border)
}
if (isTRUE(as.raster) || is.na(as.raster)) {
r <- rasterize(out, x, "zones")
if (is.na(as.raster)) {
return(list(r=r, v=out))
} else {
return(r)
}
} else {
out
}
}
)



check_frac <- function(f) {
if (is.null(f)) return(f)
if ((any(f <= 0)) || (any(f >= 1))) {
stop("f values must be > 0 and < 1")
}
if (length(f) == 1) {
f <- seq(f, 1, f)
f[length(f)] <- 1
} else {
if (length(unique(f)) < length(f)) {
stop("f values must be unique")
}
ord <- order(f)
if (!all(ord == 1:length(ord))) {
stop("f values must be in ascending order")
}
if (!isTRUE(all.equal(sum(f), 1, tolerance=0.0001))) {
stop("f values must sum to 1")
}
}
f
}



strip_polygon <- function(x, vertical, horizontal) {

totalArea <- expanse(x, transform=FALSE, unit="km")
e <- ext(x)
ex <- data.frame(t(as.vector(e + 1)))
e <- data.frame(t(as.vector(e)))

if (!is.null(vertical)) {
edges <- sapply(vertical, function(fraction){
target <- totalArea * fraction
target_fun <- function(xm){
expanse(crop(x, ext(ex$xmin, xm, ex$ymin, ex$ymax)), transform=FALSE, unit="km") - target
}
stats::uniroot(target_fun, lower=e$xmin+0.0000001, upper=e$xmax)$root
})
xbnds <- matrix(c(ex$xmin, rep(edges,rep(2,length(edges))), ex$xmax), ncol=2, byrow=TRUE)
xbnds <- cbind(xbnds, ex$ymin, ex$ymax)
xbnds <- do.call(rbind, apply(xbnds, 1, \(i) as.polygons(ext(i))))
xbnds$vid <- 1:nrow(xbnds)
}
if (!is.null(horizontal)) {
edges <- sapply(horizontal, function(fraction){
target <- totalArea * fraction
target_fun <- function(ym){
expanse(crop(x, ext(ex$xmin, ex$xmax, ex$ymin, ym)), transform=FALSE, unit="km") - target
}
stats::uniroot(target_fun, lower=e$ymin+0.0000001, upper=e$ymax)$root
})
ybnds <- matrix(c(ex$ymin, rep(edges,rep(2,length(edges))), ex$ymax), ncol=2, byrow=TRUE)
ybnds <- cbind(ex$xmin, ex$xmax, ybnds)
ybnds <- do.call(rbind, apply(ybnds, 1, \(i) as.polygons(ext(i))))
ybnds$hid <- 1:nrow(ybnds)

if (!is.null(vertical)) {
bnds <- union(xbnds, ybnds)
} else {
bnds <- ybnds
}
}
intersect(x, bnds)
}


divide_polygon <- function(x, n, w, alpha, ...) {
s <- terra::spatSample(x, max(n*4, 1000, log(n) * 100), "regular")
xy <- terra::crds(s)
if (!is.null(w)) {
e <- extract(w, s, ID=FALSE)
alpha <- rep_len(alpha, 2)
xy[,1] <- xy[,1] * alpha[1]
xy[,2] <- xy[,2] * alpha[2]
xy <- na.omit(cbind(xy, e))
}
k <- stats::kmeans(xy, centers = n, ...)
ctrs <- k$centers[, 1:2]
if (!is.null(w)) {
ctrs[,1] <- ctrs[,1] / alpha[1]
ctrs[,2] <- ctrs[,2] / alpha[2]
}
v <- terra::voronoi(vect(ctrs, crs=xcrs), bnd=x)
terra::crop(v, x)
}


setMethod("divide", signature(x="SpatVector"),
function(x, n, w=NULL, alpha=1, ...) {
if (geomtype(x) == "polygons") {
error("divide", "the geometry type must be polgyons")
}
if (is.list(n)) {
vertical <- check_frac(n$v)
horizontal <- check_frac(n$h)
if (is.null(vertical) && is.null(horizontal)) return(x)
out <- lapply(1:nrow(x), function(i) strip_polygon(x[i], vertical, horizontal))
} else {
n <- round(n)
stopifnot(n > 0)
if (n == 1) return(deepcopy(x))
xcrs <- crs(x)
crs(x) <- "+proj=utm +zone=1"
out <- lapply(1:nrow(x), function(i) divide_polygon(x[i], n, w, alpha, ...))
}
return(do.call(rbind, out))
}
)

62 changes: 62 additions & 0 deletions man/divide.Rd
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
\name{divide}

\alias{divide}

\title{
Subdivide a raster or polygons
}

\description{
Divide a \code{SpatRaster} into \code{n} parts with approximately the same sum of weights (cell values).

Divides a \code{SpatVector} of polygons into \code{n} compact and approximately equal area parts. The results are not deterministic so you should use set.seed to be able to reproduce your results. If you get a warning about non-convergence, you can increase the number of iterations used with additional argument iter.max
}

\usage{
\S4method{divide}{SpatRaster}(x, n=2, start="ns", as.raster=FALSE, na.rm=TRUE)

\S4method{divide}{SpatVector}(x, n=5, w=NULL, alpha=1)
}

\arguments{
\item{x}{SpatRaster or SpatVector of polygons}
\item{n}{numeric. Can be a single positive integer to indicate the number of parts (SpatVector) or the number of splits (SpatRaster).

If \code{x} is a SpatRaster, it can also be a vector with values -2, -1, 1, or 2. Where 1 means one split and 2 means two splits, and the negative sign indicates an East-West (vertical) split as opposed to a North-South split.

If \code{x} is a SpatVector it can be a list with at least one of these elements: \code{horizontal} and \code{vertical} that specify the proportions of the area that splits should cover. This can either be a single fraction such as 1/3, or a sequence of increasing fractions such as \code{c(1/4, 1/2, 1)}}

\item{start}{character. To indicate the initial direction of splitting the raster. "ns" for North-South (horizontal) or "ew" for East-West (vertical)}
\item{as.raster}{logical. If \code{FALSE} a SpatVector is returned. If \code{FALSE}, a SpatRaster is returned. If \code{NA} a list with a SpatRaster and a SpatVector is returned}
\item{na.rm}{logical. If \code{TRUE} cells in \code{x} that are \code{NA} are not included in the output}

\item{w}{SpatRaster with, for example, environmental data}
\item{alpha}{numeric. One or two numbers that act as weights for the x and y coordinates}
\item{...}{additional arguments such as \code{iter.max} passed on to \code{\link{kmeans}}}

\item{f}{numeric vector of fractions. These must be > 0 and < 1, and in ascending order}
\item{vertical}{logical. If \code{TRUE} the strips are vertical}

}


\value{
SpatVector or SpatRaster, or a list with bith
}


\examples{
f <- system.file("ex/elev.tif", package="terra")
r <- rast(f)
x <- divide(r, 3)
# plot(r); lines(x)


f <- system.file("ex/lux.shp", package="terra")
v <- vect(f)
d <- divide(v, 3)
dv <- divide(v, list(h=.5))
}

\keyword{spatial}

2 changes: 1 addition & 1 deletion man/surfArea.Rd
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Jenness, Jeff S., 2004. Calculating Landscape Surface Area from Digital Elevatio
}

\author{
Barry Rowlingson <b.rowlingson@lancaster.ac.uk>
Barry Rowlingson
}

\examples{
Expand Down
2 changes: 1 addition & 1 deletion src/write_ogr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ GDALDataset* SpatVector::write_ogr(std::string filename, std::string lyrname, st
otype = OFTReal;
} else if (tps[i] == "long") {
std::vector<long> rge = vrange(df.getI(i), true);
if ((rge[0] > -32768) & (rge[1] < 32767)) {
if ((rge[0] >= -32768) && (rge[1] <= 32767)) {
otype = OFTInteger;
} else {
otype = OFTInteger64;
Expand Down

0 comments on commit 684eba0

Please sign in to comment.