The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
library(sf)
#> Linking to GEOS 3.13.0, GDAL 3.10.1, PROJ 9.5.1; sf_use_s2() is TRUE
library(terra)
#> terra 1.8.42
library(weightedVoronoi)Spatial allocation around point locations is a recurring requirement in ecology and social–ecological research, including the delineation of settlement influence zones, species territories, and management areas. Voronoi tessellations provide a simple and transparent framework for dividing space among generators, but standard implementations assume uniform influence and unconstrained Euclidean distance.
The weightedVoronoi package extends this framework by allowing (i) generator-specific weights, (ii) tessellations constrained to arbitrary polygonal domains using either Euclidean or geodesic distance, (iii) optional resistance- and terrain-informed geodesic allocation, and (iv) uncertainty-aware and temporal tessellation workflows.
weightedVoronoi provides multiple tessellation methods that differ in how distance is defined, how movement is constrained, and how analyses are structured. The appropriate choice depends on both your data and your research question.
The first decision is how distance should be measured.
distance = "euclidean") Uses
straight-line distance Fast and simple Appropriate when space is open
and unconstraineddistance = "geodesic") Uses
shortest-path distance within the domain Respects boundaries,
concavities, and barriers Recommended when movement cannot occur across
gaps (e.g. coastlines, habitat patches)Geodesic distance can be modified by environmental resistance:
resistance_rast) →
incorporate land cover, risk, infrastructure, etc.dem_rast, use_tobler = TRUE) → movement cost increases
with slopeUse resistance when:
By default, resistance is isotropic: - cost depends on slope magnitude only
For directional effects (e.g. uphill vs downhill): - set
anisotropy = "terrain"
This enables: - asymmetric movement costs - more realistic modelling in steep terrain
Single tessellation - Use weighted_voronoi_domain()
for:
For geodesic tessellations:
weight_model = "additive"
anisotropy = "none"Recommended when:
Use:
to: - propagate uncertainty in generator weights - obtain probability and entropy maps
We first define a concave, irregular spatial domain and a set of generator points with heterogeneous weights.
crs_use <- 32636 # projected CRS (metres)
make_irregular_boundary <- function(crs = crs_use) {
# A simple concave polygon (fast, deterministic, vignette-safe)
coords <- rbind(
c(0, 0),
c(1200, 0),
c(1200, 900),
c(800, 900),
c(800, 500),
c(400, 500),
c(400, 900),
c(0, 900),
c(0, 0)
)
st_sf(geometry = st_sfc(st_polygon(list(coords))), crs = crs)
}
boundary_sf <- make_irregular_boundary()
set.seed(42)
pts <- st_sample(boundary_sf, size = 8, type = "random")
points_sf <- st_sf(
village = paste0("V", seq_along(pts)),
population = round(exp(rnorm(length(pts), log(300), 0.8))),
geometry = pts,
crs = crs_use
)
plot(st_geometry(boundary_sf), border = "black", lwd = 2)
plot(st_geometry(points_sf), add = TRUE, pch = 21, bg = "red")We first generate a weighted tessellation using Euclidean distance. Generator influence is scaled using a logarithmic transformation of the population weights.
out_euc <- weighted_voronoi_domain(
points_sf = points_sf,
weight_col = "population",
boundary_sf = boundary_sf,
res = 20,
weight_transform = log10,
distance = "euclidean",
# Optional alternative weight semantics:
weight_model = "multiplicative",
clip_to_boundary = TRUE,
verbose = FALSE
)
plot(st_geometry(boundary_sf), border = "black", lwd = 2)
plot(out_euc$polygons["generator_id"], add = TRUE)
plot(st_geometry(points_sf), add = TRUE, pch = 21, bg = "red")Geodesic tessellations compute shortest-path distances constrained to the domain.
out_geo <- weighted_voronoi_domain(
points_sf = points_sf,
weight_col = "population",
boundary_sf = boundary_sf,
res = 20,
weight_transform = log10,
distance = "geodesic",
close_mask = TRUE,
close_iters = 1,
verbose = FALSE
)
plot(st_geometry(boundary_sf), border = "black", lwd = 2)
plot(out_geo$polygons["generator_id"], add = TRUE)
plot(st_geometry(points_sf), add = TRUE, pch = 21, bg = "red")By default, geodesic allocation uses the "classic"
engine, which computes one accumulated-cost surface per generator. For
additive isotropic geodesic tessellations, a faster
"multisource" engine is also available:
out_geo_fast <- weighted_voronoi_domain(
points_sf = points_sf,
weight_col = "population",
boundary_sf = boundary_sf,
res = 20,
distance = "geodesic",
weight_model = "additive",
geodesic_engine = "multisource",
verbose = FALSE
)The multisource engine is currently available for additive isotropic
geodesic tessellations (weight_model = "additive" and
anisotropy = "none").
In many ecological and social–ecological applications, effective distance is shaped not only by domain geometry but also by environmental resistance. Topography, land cover, and infrastructure can increase or block movement.
The recommended workflow is to build a resistance surface (optionally composed from multiple rasters), apply barriers, and then pass the result to weighted_voronoi_domain() via resistance_rast.
# Build a template grid (aligned to the analysis)
template <- out_euc$allocation
# Two simple resistance layers (for demonstration)
R_land <- template; terra::values(R_land) <- 1
R_land[template == 1] <- 2 # small spatial pattern
R_risk <- template; terra::values(R_risk) <- 1
xy <- terra::xyFromCell(R_risk, 1:terra::ncell(R_risk))
band <- xy[,1] > 450 & xy[,1] < 550
vals <- terra::values(R_risk); vals[band] <- 10; terra::values(R_risk) <- vals
# Compose layers (multiplicative by default)
R <- compose_resistance(R_land, R_risk, template = template, method = "multiply")
# Add a river barrier (semi-permeable)
river <- st_sf(
geometry = st_sfc(st_linestring(rbind(c(500, 0), c(500, 900)))),
crs = st_crs(boundary_sf)
)
R2 <- add_barriers(R, river, permeability = "semi", cost_multiplier = 20, width = 20)
out_geo_res <- weighted_voronoi_domain(
points_sf = points_sf,
weight_col = "population",
boundary_sf = boundary_sf,
res = 20,
weight_transform = log10,
distance = "geodesic",
resistance_rast = R2,
verbose = FALSE
)
plot(st_geometry(boundary_sf), border = "black", lwd = 2)
plot(out_geo_res$polygons["generator_id"], add = TRUE)
plot(st_geometry(points_sf), add = TRUE, pch = 21, bg = "red")In many ecological and social–ecological applications, effective
distance is shaped not only by domain geometry but also by environmental
resistance. Topography, land cover, or infrastructure may increase the
cost of movement between locations. To accommodate this,
weightedVoronoi allows geodesic distance calculations to be
modified by an isotropic resistance surface.
When a digital elevation model (DEM) is supplied and
use_tobler = TRUE, movement cost is adjusted using Tobler’s
hiking function, which increases effective distance across steep
slopes.
# Create a synthetic DEM on the same grid for a lightweight vignette example
bnd_v <- terra::vect(boundary_sf)
dem <- terra::rast(ext = terra::ext(bnd_v), resolution = 20, crs = terra::crs(bnd_v))
xy <- terra::crds(dem, df = TRUE)
y0 <- (min(xy$y) + max(xy$y)) / 2
sigma <- (max(xy$y) - min(xy$y)) / 10
height <- 800
terra::values(dem) <- height * exp(-((xy$y - y0)^2) / (2 * sigma^2))
out_geo_dem <- weighted_voronoi_domain(
points_sf = points_sf,
weight_col = "population",
boundary_sf = boundary_sf,
res = 20,
weight_transform = log10,
distance = "geodesic",
dem_rast = dem,
use_tobler = TRUE,
verbose = FALSE
)
plot(st_geometry(boundary_sf), border = "black", lwd = 2)
plot(out_geo_dem$polygons["generator_id"], main = "Geodesic + Tobler resistance", add = TRUE)
plot(st_geometry(points_sf), add = TRUE, pch = 21, bg = "red")The DEM-based geodesic workflow above uses isotropic resistance: movement cost depends on slope magnitude, but not on movement direction. In some applications, however, uphill and downhill movement should differ. For example, steep uphill travel may be more costly than downhill travel, even across the same terrain.
weightedVoronoi therefore also supports a
terrain-anisotropic geodesic mode, in which movement between
neighbouring raster cells becomes direction-dependent. This requires a
DEM and uses a directed transition graph internally.
Below, we use a simple synthetic eastward-rising DEM and compare isotropic and terrain-anisotropic geodesic allocation.
# Terrain-anisotropy example (self-contained)
crs_use2 <- 32636
boundary_sf2 <- st_sf(
id = 1,
geometry = st_sfc(
st_polygon(list(rbind(
c(0, 0),
c(1200, 0),
c(1200, 900),
c(800, 900),
c(800, 500),
c(400, 500),
c(400, 900),
c(0, 900),
c(0, 0)
))),
crs = crs_use2
)
)
dem_aniso <- terra::rast(
ext = terra::ext(terra::vect(boundary_sf2)),
resolution = 20,
crs = terra::crs(terra::vect(boundary_sf2))
)
xy2 <- terra::crds(dem_aniso, df = TRUE)
terra::values(dem_aniso) <- xy2$x * 10
pts2 <- st_sf(
village = c("A", "B"),
population = c(1, 1),
geometry = st_sfc(
st_point(c(200, 450)),
st_point(c(950, 450))
),
crs = crs_use2
)
out_geo_iso2 <- weighted_voronoi_domain(
points_sf = pts2,
weight_col = "population",
boundary_sf = boundary_sf2,
res = 20,
distance = "geodesic",
dem_rast = dem_aniso,
use_tobler = TRUE,
anisotropy = "none",
verbose = FALSE
)
#> Warning: attribute variables are assumed to be spatially constant throughout
#> all geometries
out_geo_aniso2 <- weighted_voronoi_domain(
points_sf = pts2,
weight_col = "population",
boundary_sf = boundary_sf2,
res = 20,
distance = "geodesic",
dem_rast = dem_aniso,
use_tobler = TRUE,
anisotropy = "terrain",
uphill_factor = 3,
downhill_factor = 1.2,
verbose = FALSE
)
#> Warning: attribute variables are assumed to be spatially constant throughout
#> all geometries
oldpar <- par(no.readonly = TRUE)
par(mfrow = c(1, 2))
plot(st_geometry(boundary_sf2), border = "black", lwd = 2, main = "Isotropic DEM geodesic")
plot(out_geo_iso2$polygons["generator_id"], add = TRUE)
plot(st_geometry(pts2), add = TRUE, pch = 21, bg = "red")
plot(st_geometry(boundary_sf2), border = "black", lwd = 2, main = "Terrain-anisotropic geodesic")
plot(out_geo_aniso2$polygons["generator_id"], add = TRUE)
plot(st_geometry(pts2), add = TRUE, pch = 21, bg = "red")In this example, uphill and downhill movement are treated
differently, so the terrain-anisotropic tessellation can diverge from
the isotropic DEM-based result. The parameters
uphill_factor and downhill_factor control the
strength of this directional asymmetry.
In some applications, generator weights are not known exactly. To
explore how this uncertainty affects allocation,
weightedVoronoi can repeat the tessellation under
stochastic perturbation of generator weights and summarise the results
as per-cell membership probabilities and entropy.
In the current implementation, uncertainty is applied to generator weights only. For each simulation, weights are perturbed using a lognormal multiplicative model, the tessellation is recomputed, and the allocation rasters are combined into probability surfaces. Entropy is then used to summarise spatial uncertainty: low entropy indicates stable assignment, whereas high entropy indicates cells that switch more often among generators.
crs_u <- "EPSG:32636"
boundary_u <- st_sf(
geometry = st_sfc(st_polygon(list(rbind(
c(0, 0),
c(200, 0),
c(200, 200),
c(0, 200),
c(0, 0)
)))),
crs = crs_u
)
points_u <- st_sf(
population = c(0.01, 0.02),
geometry = st_sfc(
st_point(c(60, 100)),
st_point(c(140, 100))
),
crs = crs_u
)
out_u <- weighted_voronoi_uncertainty(
points_sf = points_u,
weight_col = "population",
boundary_sf = boundary_u,
n_sim = 30,
weight_sd = 0.8,
distance = "geodesic",
geodesic_engine = "multisource",
weight_model = "additive",
verbose = FALSE,
seed = 1
)
plot(out_u$entropy, main = "Entropy")
plot(terra::vect(points_u), add = TRUE, pch = 21, col = "black", bg = "red")weightedVoronoi can also run tessellations across a
sequence of time-specific point datasets. This makes it possible to
compare allocation through time while allowing generator weights,
locations, and resistance inputs to vary by time step.
pts_t1 <- points_u
pts_t2 <- points_u
pts_t2$population <- c(0.02, 0.01)
out_time <- weighted_voronoi_time(
points_list = list(time1 = pts_t1, time2 = pts_t2),
weight_col = "population",
boundary_sf = boundary_u,
distance = "geodesic",
geodesic_engine = "multisource",
weight_model = "additive",
verbose = FALSE
)
#> Warning: attribute variables are assumed to be spatially constant throughout
#> all geometries
#> Warning: attribute variables are assumed to be spatially constant throughout
#> all geometries
oldpar <- par(no.readonly = TRUE)
par(mfrow = c(1, 2))
plot(out_time$change_map_first_last, main = "Change map")
plot(out_time$persistence, main = "Persistence")In this example, change_map_first_last identifies cells
whose allocation differs between the first and last time step, while
persistence highlights cells that retain the same
allocation across the full temporal sequence.
Although both tessellations fully partition the domain, they differ in how influence propagates through concave boundaries and narrow corridors. The Euclidean tessellation allocates regions based on straight-line distance within the rasterised domain, whereas the geodesic tessellation respects the domain geometry and prevents influence across inaccessible areas.
These differences are reflected both visually and in the generator-level summary tables returned by the software.
Resolution: Smaller raster cell sizes improve boundary fidelity but increase computation time.
Weights: Alternative weight transformations (e.g. square-root or power-law) can be supplied to reflect different assumptions about generator influence.
Distance metric: Geodesic distance is recommended when domain geometry strongly constrains access or interaction.
Terrain anisotropy: Use anisotropy = "terrain" with
dem_rast when uphill and downhill movement should
differ.
Uncertainty: If entropy is zero everywhere, the tessellation may simply be robust to the perturbation level used; stronger perturbations or a smaller spatial domain may be needed to reveal uncertainty.
Temporal analysis: weighted_voronoi_time() supports
time-specific point datasets and can return change and persistence
maps.
This vignette illustrates how weightedVoronoi can be used to generate reproducible, weighted spatial tessellations that respect complex boundaries and heterogeneous generator influence. The approach provides a flexible alternative to standard Voronoi methods in ecological and social–ecological applications.
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.