---
title: "Quick Start"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Quick Start}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
dev = "svglite",
fig.ext = "svg",
fig.width = 8,
fig.height = 6
)
# Save par settings and restore on exit (CRAN policy)
oldpar <- par(no.readonly = TRUE)
knitr::knit_hooks$set(document = function(x) { par(oldpar); x })
# ============================================================================
# Consistent ggplot2 theme and color palette for all figures
# ============================================================================
# Muted professional color palette
aoe_colors <- list(
core = "#4A5568", # Slate gray
halo = "#718096", # Lighter slate
support = "#2D3748", # Dark slate (borders)
aoe_border = "#5A7BA6", # Muted blue
aoe_fill = "#5A7BA610",
mask = "#A0AEC0", # Light gray
land = "#E2E8F0", # Very light gray
sea = "#BEE3F8", # Light blue
point_core = "#2B6CB0", # Blue
point_halo = "#C05621", # Orange
point_pruned = "#A0AEC0", # Gray
side_a = "#2B6CB0", # Blue
side_b = "#C05621", # Orange
text = "#2D3748", # Dark slate
grid = "#E2E8F0" # Light gray
)
# ggplot2 theme for maps
theme_aoe <- function(base_size = 12) {
ggplot2::theme_void(base_size = base_size) +
ggplot2::theme(
plot.background = ggplot2::element_rect(fill = "transparent", color = NA),
panel.background = ggplot2::element_rect(fill = "transparent", color = NA),
legend.background = ggplot2::element_rect(fill = "transparent", color = NA),
legend.key = ggplot2::element_rect(fill = "transparent", color = NA),
legend.position = "right",
legend.title = ggplot2::element_text(size = base_size, face = "bold", color = aoe_colors$text),
legend.text = ggplot2::element_text(size = base_size - 1, color = aoe_colors$text),
plot.title = ggplot2::element_text(size = base_size + 2, face = "bold", color = aoe_colors$text, hjust = 0.5),
plot.margin = ggplot2::margin(10, 10, 10, 10)
)
}
```
## Overview
The `areaOfEffect` package classifies spatial points by their position relative to a region's boundary—without requiring sf expertise.
**Dataframe in → dataframe out.**
Points are classified as:
- **Core**: inside the original support
- **Halo**: outside the original but inside the area of effect
- **Pruned**: outside the AoE entirely (removed)
By default, halos are defined as **equal area to the core**—a proportion-based definition that enables consistent cross-region comparisons.
## Getting Started
```{r setup, message=FALSE}
library(areaOfEffect)
library(sf)
library(ggplot2)
```
### From a Dataframe
The simplest usage: pass a dataframe with coordinates and a country name.
```{r dataframe-example, eval=FALSE}
# Your occurrence data
observations <- data.frame(
species = c("Oak", "Beech", "Pine", "Spruce"),
lon = c(14.5, 15.2, 16.8, 20.0),
lat = c(47.5, 48.1, 47.2, 48.5)
)
# Classify relative to Austria
result <- aoe(observations, "Austria")
result$aoe_class
#> [1] "core" "core" "halo"
```
The package auto-detects coordinate columns (lon/lat, x/y, longitude/latitude, etc.).
### From sf Objects
sf objects work directly:
```{r sf-example, eval=FALSE}
result <- aoe(pts_sf, "AT")
```
## Austria Example
```{r austria-visual, fig.cap="Austria (dark) with its area of effect (blue dashed). The halo has equal area to the core."}
# Get Austria and transform to equal-area projection
austria <- get_country("AT")
austria_ea <- st_transform(austria, "ESRI:54009")
# Create a point inside Austria
dummy_pt <- st_centroid(austria_ea)
# Run aoe() to get geometries (uses buffer method by default)
result <- aoe(dummy_pt, austria_ea)
geoms <- aoe_geometry(result, "both")
# Extract geometries
austria_geom <- geoms[geoms$type == "original", ]
aoe_geom <- geoms[geoms$type == "aoe", ]
# Plot
par(mar = c(1, 1, 1, 1), bty = "n")
plot(st_geometry(aoe_geom), border = "steelblue", lty = 2, lwd = 1.5)
plot(st_geometry(austria_geom), border = "black", lwd = 2, add = TRUE)
legend("topright",
legend = c("Austria (core)", "Area of Effect"),
col = c("black", "steelblue"),
lty = c(1, 2),
lwd = c(2, 1.5),
inset = 0.02)
```
## Basic Usage with Custom Polygons
```{r support}
support <- st_as_sf(
data.frame(id = 1),
geometry = st_sfc(st_polygon(list(
cbind(c(0, 100, 100, 0, 0), c(0, 0, 100, 100, 0))
))),
crs = 32631
)
```
Create observation points:
```{r points}
pts <- st_as_sf(
data.frame(
id = 1:5,
value = c(10, 20, 15, 25, 30)
),
geometry = st_sfc(
st_point(c(50, 50)), # center
st_point(c(10, 10)), # near corner
st_point(c(95, 50)), # near edge
st_point(c(120, 50)), # outside, in halo
st_point(c(250, 250)) # far outside
),
crs = 32631
)
```
Apply the area of effect:
```{r aoe}
result <- aoe(pts, support)
print(result)
```
The result contains only points inside the AoE, with their classification:
```{r class}
result$aoe_class
```
## Multiple Supports
Process multiple regions at once:
```{r multiple}
# Two adjacent regions
supports <- st_as_sf(
data.frame(region = c("A", "B")),
geometry = st_sfc(
st_polygon(list(cbind(c(0, 50, 50, 0, 0), c(0, 0, 100, 100, 0)))),
st_polygon(list(cbind(c(50, 100, 100, 50, 50), c(0, 0, 100, 100, 0))))
),
crs = 32631
)
# Points that may fall in overlapping AoEs
pts_multi <- st_as_sf(
data.frame(id = 1:3),
geometry = st_sfc(
st_point(c(25, 50)), # inside A
st_point(c(50, 50)), # on boundary
st_point(c(75, 50)) # inside B
),
crs = 32631
)
result_multi <- aoe(pts_multi, supports)
print(result_multi)
```
Points can appear multiple times (once per support whose AoE contains them).
## Using a Mask (Coastlines)
For coastal regions, sea is a hard boundary. Provide a mask to constrain the AoE:
```{r mask-example, fig.cap="AoE with land mask. The AoE is clipped to the land boundary."}
# Create a coastal support
support_coast <- st_as_sf(
data.frame(id = 1),
geometry = st_sfc(st_polygon(list(
cbind(c(40, 80, 80, 40, 40), c(20, 20, 60, 60, 20))
))),
crs = 32631
)
# Create land mask (irregular coastline)
land_mask <- st_as_sf(
data.frame(id = 1),
geometry = st_sfc(st_polygon(list(cbind(
c(0, 100, 100, 70, 50, 30, 0, 0),
c(0, 0, 50, 60, 55, 70, 60, 0)
)))),
crs = 32631
)
# Create sea area (inverse of land for visualization)
sea_area <- st_as_sf(
data.frame(id = 1),
geometry = st_sfc(st_polygon(list(cbind(
c(-20, 120, 120, -20, -20),
c(-20, -20, 100, 100, -20)
)))),
crs = 32631
)
sea_area <- st_difference(sea_area, land_mask)
# Create some points
pts_coast <- st_as_sf(
data.frame(id = 1:4, class = c("core", "core", "halo", "pruned")),
geometry = st_sfc(
st_point(c(60, 40)), # core
st_point(c(50, 30)), # core
st_point(c(30, 40)), # halo (on land)
st_point(c(90, 70)) # would be halo but in sea
),
crs = 32631
)
# Apply with mask
result_coast <- aoe(pts_coast[1:3, ], support_coast, mask = land_mask)
# Get geometries for visualization
aoe_masked <- aoe_geometry(result_coast, "aoe")
support_geom <- aoe_geometry(result_coast, "original")
# Prepare point data for plotting
result_coast$class <- result_coast$aoe_class
pruned_pt <- pts_coast[4, ]
# Plot with ggplot2
ggplot() +
geom_sf(data = sea_area, fill = aoe_colors$sea, color = NA) +
geom_sf(data = land_mask, fill = aoe_colors$land, color = aoe_colors$mask, linewidth = 0.8) +
geom_sf(data = aoe_masked, fill = aoe_colors$aoe_fill, color = aoe_colors$aoe_border,
linetype = "dashed", linewidth = 1) +
geom_sf(data = support_geom, fill = NA, color = aoe_colors$support, linewidth = 1.2) +
geom_sf(data = result_coast, aes(color = class), size = 4) +
geom_sf(data = pruned_pt, color = aoe_colors$point_pruned, shape = 4, size = 4, stroke = 1.5) +
scale_color_manual(
values = c("core" = aoe_colors$point_core, "halo" = aoe_colors$point_halo),
labels = c("Core", "Halo")
) +
annotate("text", x = 90, y = 80, label = "SEA", color = "black",
fontface = "bold", size = 5) +
coord_sf(xlim = c(-10, 110), ylim = c(-10, 90)) +
labs(color = "Class") +
theme_aoe()
```
### Real-World Example: Portugal
The package includes bundled country boundaries and a global land mask. Use `mask = "land"`
to clip AoE to coastlines:
```{r portugal-mask, fig.cap="Portugal with land-masked AoE. The halo extends into Spain but not into the Atlantic."}
# Create a point inside Portugal (approximate center of mainland)
dummy <- st_as_sf(
data.frame(id = 1),
geometry = st_sfc(st_point(c(-8, 39.5))),
crs = 4326
)
# Without mask
result_no_mask <- aoe(dummy, "PT")
aoe_no_mask <- aoe_geometry(result_no_mask, "aoe")
# With mask + area=1 for equal land area
result_masked <- aoe(dummy, "PT", mask = "land", area = 1)
aoe_masked <- aoe_geometry(result_masked, "aoe")
# Get support geometry
support_geom <- aoe_geometry(result_masked, "original")
# Transform to equal area for plotting
crs_ea <- st_crs("+proj=laea +lat_0=39.5 +lon_0=-8 +datum=WGS84")
aoe_no_mask_ea <- st_transform(aoe_no_mask, crs_ea)
aoe_masked_ea <- st_transform(aoe_masked, crs_ea)
support_ea <- st_transform(support_geom, crs_ea)
# Plot with padding to avoid clipping
bbox <- st_bbox(aoe_no_mask_ea)
pad <- (bbox[4] - bbox[2]) * 0.05
par(mar = c(1, 1, 1, 1), bty = "n")
plot(st_geometry(aoe_no_mask_ea), border = "gray50", lty = 2, lwd = 1.5,
xlim = c(bbox[1] - pad, bbox[3] + pad),
ylim = c(bbox[2] - pad, bbox[4] + pad))
plot(st_geometry(aoe_masked_ea), col = rgb(0.3, 0.5, 0.7, 0.3),
border = "steelblue", lty = 2, lwd = 1.5, add = TRUE)
plot(st_geometry(support_ea), border = "black", lwd = 2, add = TRUE)
legend("topright",
legend = c("Portugal", "AoE (unmasked)", "AoE (land only)"),
col = c("black", "gray50", "steelblue"),
lty = c(1, 2, 2),
lwd = c(2, 1.5, 1.5),
bty = "n",
inset = 0.05)
```
The `area = 1` parameter ensures the halo has equal land area to the core, even after
the ocean is masked out. Without this, coastline clipping would reduce the effective halo area.
## Scale Parameter
The `scale` parameter controls halo size as a proportion of core area.
```{r scale}
# Default: equal core/halo areas (scale = sqrt(2) - 1)
result_default <- aoe(pts, support)
# Scale = 1: larger halo (3:1 area ratio)
result_large <- aoe(pts, support, scale = 1)
```
| Scale | Halo:Core Area |
|-------|----------------|
| `sqrt(2) - 1` (default) | 1:1 |
| `1` | 3:1 |
| `0.5` | 1.25:1 |
## Area Parameter (Target Halo Area)
Sometimes you need a specific halo area regardless of masking. The `area` parameter
specifies the target halo area as a proportion of the original support:
```{r area-param, eval=FALSE}
# Halo area = original area (same as scale = sqrt(2) - 1 without mask)
result <- aoe(pts, support, area = 1)
# Halo area = half of original
result <- aoe(pts, support, area = 0.5)
```
Unlike `scale`, `area` accounts for masking: the function finds the scale that
produces the target halo area *after* mask intersection. This is useful for
coastal regions where scale alone would produce inconsistent effective areas.
```{r area-masked, eval=FALSE}
# Target area = 1 means halo = original, even after coastline clipping
result <- aoe(pts, support, area = 1, mask = "land")
```
## Adaptive Expansion with `aoe_expand()`
When some supports have too few points at baseline AoE, `aoe_expand()` finds the
minimum scale needed to capture a target number of points:
```{r expand-example}
# Create sparse data
set.seed(42)
pts_sparse <- st_as_sf(
data.frame(id = 1:15),
geometry = st_sfc(c(
lapply(1:5, function(i) st_point(c(runif(1, 20, 80), runif(1, 20, 80)))),
lapply(1:10, function(i) st_point(c(runif(1, -50, 150), runif(1, -50, 150))))
)),
crs = 32631
)
# Expand until at least 10 points are captured
result_expand <- aoe_expand(pts_sparse, support, min_points = 10)
```
Two safety caps prevent unreasonable expansion:
- `max_area = 2` (default): halo area cannot exceed 2× the original
- `max_dist`: maximum expansion distance in CRS units
```{r expand-caps, eval=FALSE}
# Strict caps
result <- aoe_expand(pts, support,
min_points = 50,
max_area = 1.5, # halo ≤ 1.5× original
max_dist = 500) # max 500m expansion
```
Check expansion details:
```{r expand-info}
info <- attr(result_expand, "expansion_info")
info
```
## Balanced Sampling with `aoe_sample()`
Core regions often dominate due to point density. `aoe_sample()` provides
stratified sampling to balance core/halo representation:
```{r sample-example}
# Create imbalanced data (many core, few halo)
set.seed(42)
pts_imbal <- st_as_sf(
data.frame(id = 1:60),
geometry = st_sfc(c(
lapply(1:50, function(i) st_point(c(runif(1, 10, 90), runif(1, 10, 90)))),
lapply(1:10, function(i) st_point(c(runif(1, 110, 140), runif(1, 10, 90))))
)),
crs = 32631
)
result_imbal <- aoe(pts_imbal, support, scale = 1)
# Default: balance core/halo (downsamples core to match halo)
set.seed(123)
balanced <- aoe_sample(result_imbal)
table(balanced$aoe_class)
```
Custom ratios and fixed sample sizes:
```{r sample-custom}
# Fixed n with 70/30 split
set.seed(123)
sampled <- aoe_sample(result_imbal, n = 20, ratio = c(core = 0.7, halo = 0.3))
table(sampled$aoe_class)
```
For multiple supports, use `by = "support"` to sample within each:
```{r sample-support, eval=FALSE}
sampled <- aoe_sample(result_multi, by = "support")
```
## Border Classification with `aoe_border()`
When your study involves a boundary *line* rather than a polygon (e.g., a river, mountain
range, or political border), use `aoe_border()` to classify points by their distance from
and side of the border.
```{r border-example, fig.cap="Border classification. Points are classified by side (blue vs orange) and distance (core vs halo) from the border line."}
# Create a diagonal border line
border_line <- st_as_sf(
data.frame(id = 1),
geometry = st_sfc(st_linestring(matrix(
c(0, 0,
100, 100), ncol = 2, byrow = TRUE
))),
crs = 32631
)
# Create points on both sides
set.seed(42)
pts_border <- st_as_sf(
data.frame(id = 1:30),
geometry = st_sfc(c(
# Points on side 1 (above the line)
lapply(1:15, function(i) st_point(c(runif(1, 10, 90), runif(1, 10, 90) + 20))),
# Points on side 2 (below the line)
lapply(1:15, function(i) st_point(c(runif(1, 10, 90), runif(1, 10, 90) - 20)))
)),
crs = 32631
)
# Classify by distance from border
result_border <- aoe_border(
pts_border, border_line,
width = 30,
side_names = c("north", "south")
)
# Extract geometries for ggplot2
geoms <- attr(result_border, "border_geometries")
# Plot with ggplot2
ggplot() +
# Halo zones (background)
geom_sf(data = geoms$side1_halo, fill = paste0(aoe_colors$side_a, "20"), color = NA) +
geom_sf(data = geoms$side2_halo, fill = paste0(aoe_colors$side_b, "20"), color = NA) +
# Core zones
geom_sf(data = geoms$side1_core, fill = paste0(aoe_colors$side_a, "40"), color = NA) +
geom_sf(data = geoms$side2_core, fill = paste0(aoe_colors$side_b, "40"), color = NA) +
# Border line
geom_sf(data = geoms$border, color = aoe_colors$support, linewidth = 1.5) +
# Points
geom_sf(data = result_border,
aes(color = side, shape = aoe_class), size = 3) +
scale_color_manual(values = c("north" = aoe_colors$side_a, "south" = aoe_colors$side_b)) +
scale_shape_manual(values = c("core" = 16, "halo" = 1),
labels = c("Core", "Halo")) +
labs(color = "Side", shape = "Class") +
theme_aoe()
```
The `aoe_border()` function:
- Creates symmetric buffer zones on both sides of the border
- Classifies points as "core" (near border) or "halo" (farther away)
- Assigns each point to a side based on position relative to the line
### Area-Based Border Zones
Use the `area` parameter to specify target zone areas instead of fixed widths:
```{r border-area, eval=FALSE}
# Each side's core zone has area 5000 (in CRS units²)
result <- aoe_border(pts, border, area = 5000)
```
### Sampling from Border Results
`aoe_sample()` also works with border results, allowing stratification by side or class:
```{r border-sample}
# Balance by side (equal north/south)
set.seed(123)
balanced_side <- aoe_sample(result_border, ratio = c(north = 0.5, south = 0.5))
table(balanced_side$side)
# Balance by distance class
set.seed(123)
balanced_class <- aoe_sample(result_border, by = "class")
table(balanced_class$aoe_class)
```
## Diagnostics
```{r summary}
aoe_summary(result)
```
## Summary
- `aoe()` classifies points as "core" or "halo"
- Works with dataframes or sf objects
- Pass country codes directly: `aoe(df, "AT")`
- Area-based halos enable consistent cross-country comparisons
- Use `mask` for coastlines and hard boundaries
- Use `area` parameter for target halo area (accounts for masking)
- Use `aoe_expand()` for adaptive expansion to capture minimum points
- Use `aoe_sample()` for balanced core/halo sampling
- Use `aoe_border()` for border/line-based classification
- Use `aoe_summary()` for diagnostics