---
title: "Theory"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Theory}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  dev = "svglite",
  fig.ext = "svg",
  fig.width = 7,
  fig.height = 5
)
# Save par settings and restore on exit (CRAN policy)
oldpar <- par(no.readonly = TRUE)
knitr::knit_hooks$set(document = function(x) { par(oldpar); x })
```

## What AoE is Not

Before explaining what AoE does, it's important to clarify what it is *not*:

- **Not a buffer**: Buffers add a fixed distance. AoE computes the buffer distance from an *area* target—you specify how much area, not how many meters.

- **Not a distance decay**: There is no continuous weight function. Points are categorically classified as core, halo, or pruned.

- **Not a magic number**: The default scale (√2 − 1) is derived from the constraint of equal core/halo areas, not chosen arbitrarily. But you *can* override it with domain knowledge.

## The Problem: Border Truncation

When analyzing spatial data within political or administrative boundaries, a fundamental assumption is often violated: that the sampling region represents the ecological extent of the processes being studied.

Consider sampling species occurrences within a country. Observations near the border are influenced by conditions *outside* that country. A forest that spans the border, a river that crosses it, or simply the continuous nature of climate and habitat means that truncating at the border introduces systematic bias.

This is **border truncation**: the artificial constraint of ecological processes to administrative boundaries.

## The Area of Effect

The **area of effect** (AoE) is the spatial extent over which observations within a support are influenced by external conditions. It is computed by expanding the support boundary outward to create a halo region.

The key insight: **halos are defined as a proportion of region area**, not as arbitrary buffer distances. This enables consistent cross-region comparisons without units or scale dependencies.

## Core and Halo Classification

Points within the AoE are classified into two categories:

- **Core**: Points inside the original support. These are fully contained within the sampling region and represent "pure" observations unaffected by border effects.

- **Halo**: Points outside the original support but inside the expanded AoE. These observations are influenced by conditions in the border zone and may require different treatment in analysis.

Points outside the AoE are **pruned** (removed). They are too distant to be meaningfully related to the support region.

```{r classification, echo=FALSE, fig.cap="Point classification by AoE. Core points (green) are inside the original support. Halo points (orange) are in the expanded region. Points outside the AoE are pruned."}
library(sf)
library(areaOfEffect)

# Create support
support <- st_as_sf(
  data.frame(id = 1),
  geometry = st_sfc(st_polygon(list(
    cbind(c(2, 8, 8, 2, 2), c(2, 2, 8, 8, 2))
  ))),
  crs = 32631
)

# Use aoe() to get proper AoE geometry
dummy_pt <- st_as_sf(
  data.frame(id = 1),
  geometry = st_sfc(st_point(c(5, 5))),
  crs = 32631
)
aoe_result <- aoe(dummy_pt, support)
aoe_geom <- aoe_geometry(aoe_result, "aoe")

# Create random points
set.seed(42)
n_pts <- 50
pts_coords <- cbind(
  runif(n_pts, -2, 12),
  runif(n_pts, -2, 12)
)
pts <- st_as_sf(
  data.frame(id = 1:n_pts),
  geometry = st_sfc(lapply(1:n_pts, function(i) st_point(pts_coords[i, ]))),
  crs = 32631
)

# Classify
in_support <- st_intersects(pts, support, sparse = FALSE)[, 1]
in_aoe <- st_intersects(pts, aoe_geom, sparse = FALSE)[, 1]

pts$class <- ifelse(in_support, "core",
                    ifelse(in_aoe, "halo", "pruned"))

# Plot
par(mar = c(2, 2, 2, 2), bty = "n")
plot(st_geometry(aoe_geom), border = "steelblue", lty = 2,
     xlim = c(-3, 13), ylim = c(-3, 13), asp = 1)
plot(st_geometry(support), border = "black", lwd = 2, add = TRUE)
points(pts_coords[pts$class == "pruned", ], col = "gray60", pch = 4, cex = 0.8)
points(pts_coords[pts$class == "halo", ], col = "darkorange", pch = 16)
points(pts_coords[pts$class == "core", ], col = "forestgreen", pch = 16)
legend("topright",
       legend = c("Core", "Halo", "Pruned", "Original", "AoE"),
       col = c("forestgreen", "darkorange", "gray60", "black", "steelblue"),
       pch = c(16, 16, 4, NA, NA),
       lty = c(NA, NA, NA, 1, 2),
       lwd = c(NA, NA, NA, 2, 1))
```

## The Scale Parameter

The `scale` parameter controls how large the halo is relative to the core. The relationship between scale and area is:

$$\text{Total AoE area} = \text{Core area} \times (1 + s)^2$$

where $s$ is the scale parameter.

Two values have special meaning:

- **`sqrt(2) - 1` ≈ 0.414** (default): Equal core and halo areas

- **`1`**: Halo area is 3× the core area

## Why Equal Area is the Default

The default scale produces equal core and halo areas. This is not arbitrary—it reflects a principled position about spatial influence.

### The Symmetry Argument

When we say a point in the halo is "influenced by" the support region, we're making a claim about spatial relevance. The question is: how much relevance should we grant to the outside?

Equal area says: **the outside matters as much as the inside**.

This is the maximally symmetric choice. Any other ratio implies that either:

- The core is more important than the halo (halo smaller), or

- External conditions dominate internal ones (halo larger)

Without domain-specific knowledge to justify asymmetry, equal weighting is the principled default.

### The Information-Theoretic View

Consider the AoE as defining a probability distribution over space: "where might conditions relevant to this support come from?"

Equal areas means equal prior probability mass inside and outside the original boundary. This is the maximum-entropy choice—it encodes no bias toward internal or external dominance.

### The Geometric Inevitability

The formula $s = \sqrt{2} - 1$ is not a tuned parameter. It's the *unique* solution to the constraint "core equals halo":

$$
(1 + s)^2 - 1 = 1 \implies s = \sqrt{2} - 1
$$

There's something satisfying about a default that isn't chosen but *derived*. It removes a degree of freedom from the analyst and replaces it with a principled constraint.

### When to Override

Use `scale = 1` when:

- Your domain knowledge suggests external conditions strongly dominate

- You're comparing with previous work that used this convention

Use custom scales when:

- You have empirical data on influence decay

- Sensitivity analysis requires exploring the parameter space

- Domain expertise justifies a specific ratio

## Method: Buffer vs Stamp

The package offers two methods for computing the AoE:

### Buffer Method (Default)

The buffer method expands the boundary uniformly in all directions. The buffer distance is computed to achieve the target halo area.

**Advantages:**

- Robust for any polygon shape

- Always guarantees the AoE contains the original support

- Consistent behavior for concave shapes

**How it works:**

The buffer distance $d$ is found by solving:

$$\pi d^2 + P \cdot d = A_{\text{halo}}$$

where $P$ is the perimeter and $A_{\text{halo}}$ is the target halo area.

### Stamp Method (Alternative)

The stamp method scales vertices outward from the centroid, preserving shape proportions.

**Advantages:**

- Preserves the shape's proportions

- Exact area calculation

**Limitation:**

Only guarantees containment for *star-shaped* polygons (where the centroid can "see" all boundary points). For highly concave shapes like country boundaries, small gaps may occur where the original is not fully contained.

Use `method = "stamp"` when working with convex or nearly convex regions where shape preservation is important.

## Hard vs Soft Boundaries

AoE distinguishes between two types of boundaries:

**Political borders (soft)**: Administrative lines have no ecological meaning. The AoE freely crosses them. A country border does not stop species from dispersing or climate from varying.

**Sea boundaries (hard)**: Physical barriers like coastlines are true boundaries. The optional `mask` argument enforces these constraints by intersecting the AoE with a land polygon.

```{r mask-concept, echo=FALSE, fig.cap="Hard boundaries constrain the AoE. The dashed line shows the theoretical AoE; the gray area shows the AoE after applying a land mask."}
# Create a coastal support
support_coastal <- st_as_sf(
  data.frame(id = 1),
  geometry = st_sfc(st_polygon(list(
    cbind(c(4, 8, 8, 4, 4), c(2, 2, 6, 6, 2))
  ))),
  crs = 32631
)

# Land mask (irregular coastline)
land <- st_as_sf(
  data.frame(id = 1),
  geometry = st_sfc(st_polygon(list(cbind(
    c(0, 10, 10, 7, 5, 3, 0, 0),
    c(0, 0, 5, 6, 5.5, 7, 6, 0)
  )))),
  crs = 32631
)

# Create dummy point and run aoe WITHOUT mask to get theoretical AoE
dummy_coastal <- st_as_sf(
  data.frame(id = 1),
  geometry = st_sfc(st_point(c(6, 4))),
  crs = 32631
)
result_unmasked <- aoe(dummy_coastal, support_coastal)
aoe_theoretical <- aoe_geometry(result_unmasked, "aoe")

# Run aoe WITH mask to get constrained AoE
result_coastal <- aoe(dummy_coastal, support_coastal, mask = land)
aoe_masked <- aoe_geometry(result_coastal, "aoe")

par(mar = c(2, 2, 2, 2), bty = "n")
plot(st_geometry(land), col = NA, border = "steelblue", lwd = 2,
     xlim = c(-1, 11), ylim = c(-1, 9), asp = 1)
# Show theoretical AoE as dashed outline
plot(st_geometry(aoe_theoretical), border = "gray50", lty = 2, lwd = 1.5, add = TRUE)
# Show masked AoE as gray fill
plot(st_geometry(aoe_masked), col = rgb(0.5, 0.5, 0.5, 0.3), border = NA, add = TRUE)
plot(st_geometry(support_coastal), border = "black", lwd = 2, add = TRUE)

# Add "SEA" label in the water area
text(9, 7.5, "SEA", col = "steelblue", font = 2, cex = 1.2)

legend("topleft",
       legend = c("Support", "Theoretical AoE", "AoE (masked)", "Coastline"),
       col = c("black", "gray50", "gray50", "steelblue"),
       lty = c(1, 2, NA, 1),
       lwd = c(2, 1.5, NA, 2),
       pch = c(NA, NA, 15, NA),
       pt.cex = c(NA, NA, 2, NA))
```

## Multiple Supports

Real-world analyses often involve multiple administrative regions (countries, provinces, protected areas). AoE handles these naturally:

- Each support is processed independently

- Points can fall within multiple AoEs (when regions are adjacent)

- Output is in long format: one row per point-support combination

This enables cross-border analyses and studies of nested administrative structures without repeated preprocessing.

## Summary

The area of effect provides a principled correction for border truncation in spatial analysis:

- **Area-based definition**: Halos defined by proportion of region area, not arbitrary distances

- **Principled default**: Scale = √2 − 1, giving equal core and halo areas

- **Geometric derivation**: The default emerges from symmetry, not tuning

- **Robust method**: Buffer-based expansion works for any polygon shape

- **Categorical output**: Core, halo, or pruned

- **Soft/hard boundaries**: Political borders ignored, physical barriers respected

- **Multiple supports**: Process many regions at once

The result is a reproducible, interpretable method that can be consistently applied across studies.
