--- 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