---
title: "fastfocal: Fast Multi-scale Raster Extraction and Moving Window Analysis with FFT"
subtitle: "Version 0.1.3 (2025)"
author: "Ho Yi Wan"
output: rmarkdown::html_vignette
bibliography: references.bib
vignette: >
  %\VignetteIndexEntry{fastfocal: Fast Multi-scale Raster Extraction and Moving Window Analysis with FFT}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  eval = TRUE,
  fig.width = 6,
  fig.height = 4,
  out.width = "100%"
)
library(terra)
library(fastfocal)
```

## What is fastfocal?

I created the `fastfocal` R package (Wan 2025) for multi-scale ecological modeling that requires focal statistics at various spatial scales. As rasters and kernels grow larger, traditional methods can become slow. `fastfocal` is designed for high-performance focal and buffer-based raster processing. It provides fast moving-window operations and point-based extraction with a variety of window (kernel) shapes. The package can automatically switch to a Fast Fourier Transform (FFT) backend for large-kernel operations, and otherwise use the C++ backend via `terra` for smaller cases. It is built on `terra` and works with `SpatRaster` and `SpatVector` objects.

This vignette walks through the core functionality using simulated raster layers and step-by-step examples. We highlight how `fastfocal()` and `fastextract()` support multi-scale analysis with flexible kernel definitions.

---

## Key features

- Multi-layer raster support: apply focal operations across stacked rasters.
- Flexible kernels: choose from many window types (e.g., circle, gaussian, logistic, quartic).
- Map-unit radius: specify smoothing or extraction radius directly in meters (map units).
- Point-based extraction: extract raster summaries at points with optional buffer windows.
- FFT backend: automatically selected for large kernels; C++ backend otherwise.
- Identical geometry output: results maintain resolution, extent, and CRS.

---

## Installation

```{r, eval=FALSE}
# install.packages("devtools")
# devtools::install_github("hoyiwan/fastfocal")
```

```{r}
library(fastfocal)
library(terra)
```

---

## Simulating rasters for this vignette

We generate three example rasters with values resembling ecological variables. Users can replace these with their own data.

```{r simulate-coarse-fine}
set.seed(888)
ndvi_coarse  <- rast(matrix(runif(100, 0.1, 0.9), 10, 10))            # NDVI (0.1 - 0.9)
slope_coarse <- rast(matrix(rbeta(100, 2, 5) * 40, 10, 10))            # slope (degrees)
temp_coarse  <- rast(matrix(seq(12, 24, length.out = 100) + rnorm(100, 0, 1), 10, 10))  # temperature with gradient

# Disaggregate to finer resolution
ndvi  <- disagg(ndvi_coarse, fact = 10, method = "bilinear")
slope <- disagg(slope_coarse, fact = 10, method = "bilinear")
temp  <- disagg(temp_coarse, fact = 10, method = "bilinear")

# Set a 3 km by 3 km extent for all layers (projected units, e.g., meters)
ext(ndvi) <- ext(0, 3000, 0, 3000)
ext(slope) <- ext(ndvi)
ext(temp) <- ext(ndvi)
crs(ndvi) <- "EPSG:32611"  # UTM Zone 11N
crs(slope) <- crs(ndvi)
crs(temp) <- crs(ndvi)

# Combine into a multi-layer SpatRaster
r <- c(ndvi, slope, temp)
names(r) <- c("ndvi", "slope", "temp")

plot(r)
```

---

## Point and buffer-based extraction

Use `fastextract()` to extract values at points and, optionally, within buffers of different sizes.

```{r simulate-points}
# Simulate some points
pts <- vect(matrix(c(500,500, 1500,1500, 2500,2500), ncol=2, byrow=TRUE), type="points", crs=crs(r))

oldpar <- par(no.readonly = TRUE)
par(mfrow = c(2, 2))
plot(r[[1]], main = "NDVI");  plot(pts, add = TRUE, pch = 20, cex = 2)
plot(r[[2]], main = "Slope"); plot(pts, add = TRUE, pch = 20, cex = 2)
plot(r[[3]], main = "Temp");  plot(pts, add = TRUE, pch = 20, cex = 2)
par(oldpar)
```

```{r extract}
fastextract(r, pts)                               # default d = 0 (extract at the point)
fastextract(r, pts, d = c(0, 100, 500))           # multiple distances
fastextract(r, pts, d = c(0, 100, 500, 1000), w = "gaussian", fun = "sd")
```

---

## Moving-window analysis

Apply `fastfocal()` to compute moving-window statistics over the stack using different kernels and summary functions.

```{r focal}
# Focal means using a circular window (300 m radius)
f_circ <- fastfocal(r, d = 300, w = "circle",   fun = "mean")

# Focal means using a gaussian window (300 m radius)
f_gaus <- fastfocal(r, d = 300, w = "gaussian", fun = "mean")

oldpar <- par(no.readonly = TRUE)
par(mfrow = c(3, 3), mar = c(2, 2, 2, 2))
plot(r[[1]], main = "NDVI");  plot(f_circ[[1]], main = "NDVI - Circle");  plot(f_gaus[[1]], main = "NDVI - Gaussian")
plot(r[[2]], main = "Slope"); plot(f_circ[[2]], main = "Slope - Circle"); plot(f_gaus[[2]], main = "Slope - Gaussian")
plot(r[[3]], main = "Temp");  plot(f_circ[[3]], main = "Temp - Circle");  plot(f_gaus[[3]], main = "Temp - Gaussian")
par(oldpar)
```

---

## Comparing statistics on one layer

```{r multi-stat}
f_mean   <- fastfocal(r, d = 300, fun = "mean",   na.rm = TRUE)
f_sd     <- fastfocal(r, d = 300, fun = "sd",     na.rm = TRUE)
f_median <- fastfocal(r, d = 300, fun = "median", na.rm = TRUE)

oldpar <- par(no.readonly = TRUE)
par(mfrow = c(2, 2))
plot(r[[1]],       main = "NDVI")
plot(f_mean[[1]],  main = "NDVI - Mean (300 m)")
plot(f_sd[[1]],    main = "NDVI - SD (300 m)")
plot(f_median[[1]],main = "NDVI - Median (300 m)")
par(oldpar)
```

---

## Visualizing supported kernels

This section visualizes the built-in window types supported by `fastfocal_weights()`. We keep the radius modest so the code runs quickly in the vignette.

```{r visualize-kernels}
center_r <- rast(ext(0, 90, 0, 90), resolution = 30, crs = "EPSG:32611")
kernel_types <- c("circle", "rectangle", "gaussian", "pareto", "idw", "exponential",
                  "triangular", "cosine", "logistic", "cauchy", "quartic", "epanechnikov")

pad_kernel <- function(k, pad = 2) {
  nr <- nrow(k); nc <- ncol(k)
  out <- matrix(0, nrow = nr + 2 * pad, ncol = nc + 2 * pad)
  out[(pad + 1):(pad + nr), (pad + 1):(pad + nc)] <- k
  out
}

oldpar <- par(no.readonly = TRUE)
par(mfrow = c(3, 4), mar = c(2, 2, 2, 2))
for (w in kernel_types) {
  k_raw <- fastfocal_weights(center_r, d = 150, w = w, normalize = FALSE)  # ~11x11 at 30 m
  k_pad <- pad_kernel(k_raw, pad = 2)
  k <- k_pad / max(k_pad, na.rm = TRUE)
  image(k, main = w, col = hcl.colors(20, "YlOrRd", rev = TRUE), zlim = c(0, 1), asp = 1, cex.main = 1.2)
}
par(oldpar)
```

---

## Notes

- Multi-layer support is built into `fastfocal()`.
- Input can be a single-layer raster or a stack.
- Output preserves layer names and returns a `SpatRaster` with the same geometry.
- Kernel size `d` is in map units (e.g., meters).

---

## Explore more

See the benchmark comparison: `vignettes/benchmark.Rmd` (built as `inst/doc/benchmark.html`).

---

## Session info

```{r}
sessionInfo()
```

## Citation

To cite the package:

Wan, H. Y. (2025). *fastfocal: Fast Multi-scale Raster Extraction and Moving Window Analysis with FFT*. R package version 0.1.3. Zenodo. https://doi.org/10.5281/zenodo.17074691


```{r}
citation("fastfocal")
```
