---
title: "Example"
author:
- name: Yuxuan Wang
  affiliation: Sichuan University
  email: wyx1743@gmail.com
  orcid: "0009-0003-7817-748X"
date: "`r Sys.Date()`"
output:
  html_document:
    toc: true
    toc_float: true
    number_sections: false
    df_print: paged
    theme: readable
    highlight: tango
  pdf_document:
    toc: false
  rmarkdown::html_vignette:
    toc: false
vignette: >
  %\VignetteIndexEntry{Example}
  %\VignetteEncoding{UTF-8}
  %\VignetteEngine{knitr::rmarkdown}
bibliography: references.bib
---

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE, eval = F)
```

## Overview

VasGBIF provides a reproducible, tracheophyte-optimized, and computationally efficient framework for transforming GBIF records into analysis-ready datasets. The package functions are organized into 8 steps. This basic example demonstrates the complete workflow using the built-in example dataset.

Each step below progressively filters records through taxonomic, quality, and coordinate checks, so the dataset shrinks from the raw download to a high-quality, analysis-ready set.

```{r init}
library(VasGBIF)
library(data.table)
VasGBIF_summary <- list()
```

## Import Records

`import_records()` reads a GBIF occurrence download ZIP ('SIMPLE_CSV' or Darwin Core Archive), extracts the occurrence table, and returns an `"import"` data.table of the fields required by the workflow. No records are filtered at this stage — the raw `issue` column is preserved for the next step.

The built-in example dataset is a GBIF download of *Medicago sativa* (preserved specimens with coordinates, obtained 23 July 2026): <https://doi.org/10.15468/dl.nt5exp>.

```{r import_records}
gbif_file <- system.file(
  "extdata", "0003386-260721160103020.zip",
  package = "VasGBIF"
)
occ_import <- import_records(path = gbif_file)
VasGBIF_summary$initial_records <- nrow(occ_import)
VasGBIF_summary$initial_taxa <- uniqueN(occ_import$scientificName)
print(occ_import)
```

The returned `"import"` object is a `data.table` with the GBIF fields used by the workflow (`gbifID` coerced to character, plus the raw `issue` column).

## Check Taxon Name

`check_taxon()` submits species- and infraspecific-rank names to the Taxonomic Name Resolution Service (TNRS; Boyle et al. 2013) for resolution against the World Checklist of Vascular Plants (WCVP) or World Flora Online (WFO). Synonyms are resolved to accepted names; records that fail the match-score threshold or lack an accepted/synonym status are excluded from the downstream table and reported in the `summary` for manual review.

```{r check_taxon}
taxa_checked <- check_taxon(occ_import = occ_import, accuracy = 0.85)
VasGBIF_summary$after_taxon_resolved <- nrow(taxa_checked$occ_taxa_checked)
VasGBIF_summary$final_taxa <- uniqueN(taxa_checked$occ_taxa_checked$Accepted_name)
print(taxa_checked)
```

The `occ_taxa` object contains:

- `occ_taxa_checked`: records with resolved taxonomy
- `summary`: one row per submitted name, for reviewing match quality
- `runtime`: execution time

## Customized Filter

`customized_filter()` joins the imported records with the resolved taxonomy and the parsed issue flags, then applies the enabled filter rules to retain only high-quality records. By default the `countryCode`, `coordinateUncertainty` (\<= 10,000 m), and `gbif_issues_max` (\<= 5) rules are enabled; `date`, `identifiedBy`, and `recordedBy` are opt-in. Every step is recorded in the per-rule `summary` table.

```{r customized_filter}
filtered <- customized_filter(
  occ_import = occ_import,
  taxa_checked = taxa_checked
)
VasGBIF_summary$after_customized_filter <- nrow(filtered$occ_filtered)
print(filtered)
```

The `customFiltered` object contains:

- `occ_filtered`: the filtered occurrence table
- `occ_marked`: the records failed customized quality rules
- `summary`: per-rule `rule`, `dropped`, and `remaining` counts

## Clean Coordinates

`par_clean_coordinates()` validates coordinates with CoordinateCleaner (Zizka et al. 2019) to flag spatial errors such as centroids, capitals, marine coordinates, and zero coordinates, splitting records into cleaned and problematic tables. Validation is parallelized across user-specified threads.

```{r clean_coordinates}
cleaned_coordinates <- par_clean_coordinates(
  input = filtered$occ_filtered,
  species = 'Accepted_name_id',
  latitude = 'decimalLatitude',
  longitude = 'decimalLongitude',
  threads = 8,tests = c("capitals", "centroids", "equal", "gbif", "institutions", "outliers", "seas", "zeros"))
VasGBIF_summary$cleaned <- nrow(cleaned_coordinates$CoordinateCleaned)
VasGBIF_summary$problematic <- nrow(cleaned_coordinates$CoordinateProblematic)
print(cleaned_coordinates)
```

The `CoordinateRefined` object contains:

- `CoordinateCleaned`: records with valid coordinates
- `CoordinateProblematic`: records failing one or more CoordinateCleaner tests (including those lack coordinates)
- `runtime`: execution time

## Detect Native Status

`detect_native_coord()` and `detect_native_country()` match each record against WCVP distribution data (the internal `Distributions` dataset) via WGSRPD Level 3 areas to classify it as native, introduced, extinct, location_doubtful, or unknown. Records with validated coordinates are matched spatially by `detect_native_coord()`; records without coordinates or failed with `cleaned_coordinates()` are matched through their country code by `detect_native_country()`. Every classification records how it was obtained in `native_status_source`.

```{r detect_native}
native_detected_coord <- detect_native_coord(
  input = cleaned_coordinates$CoordinateCleaned,
  species = 'Accepted_name',
  latitude = 'decimalLatitude',
  longitude = 'decimalLongitude', buffer_km = 10, buffer_chunk_size = 2000)
native_detected_country <- detect_native_country(
  input = cleaned_coordinates$CoordinateProblematic,
  country = 'countryCode',
  species = 'Accepted_name')
native_detected <- rbind(
  native_detected_coord,
  native_detected_country[is.na(decimalLatitude) | is.na(decimalLongitude),],
  use.names = TRUE,
  fill = TRUE
)
VasGBIF_summary$native <- native_detected[native_status == "native", .N]
VasGBIF_summary$introduced <- native_detected[native_status == "introduced", .N]
VasGBIF_summary$extinct <- native_detected[native_status == "extinct", .N]
VasGBIF_summary$location_doubtful <- native_detected[
  native_status == "location_doubtful", .N
]
VasGBIF_summary$unknown <- native_detected[native_status == "unknown", .N]
print(native_detected)
```

The `nativeDetected` objects are `data.table`s keyed by `gbifID`. Each retains every column of the input records and appends `LEVEL3_COD`, `native_status`, `native_status_source`, and `buffered`. The spatial result (`native_detected_coord`) carries coordinates for every record and can be passed straight to `export_records()` and `map_records()` without joining back to `cleaned_coordinates`.

## Map Visualiztion

`map_records()` renders the records on an interactive map via `mapview`, with geohash-based decluttering and colour-coding by native status.

```{r map_records, eval=FALSE}
map_records(
  native_detected_coord = native_detected_coord,
  precision = 3,
  cex = 3
)
```

## Export Records

`export_records()` writes the classified records to disk as two gzip-compressed CSV files:

- `all_records.csv.gz`: all classified records with validated coordinates, with their native status
- `native_records.csv.gz`: the subset classified as native

```{r export_records, eval=FALSE, include=T}
export_records(
  native_detected_coord = native_detected_coord,
  native_detected_country = native_detected_country,
  export_path = getwd()
)
```

## Summary

The summary statistics provide a quantitative overview of data reduction through the pipeline:

```{r summary}
VasGBIF_summary |>
  as.data.frame() |>
  t()
```

| Statistic | Description |
|----|----|
| `initial_records` | Total GBIF occurrence records imported |
| `initial_taxa` | Unique scientific names before resolution |
| `after_taxon_resolved` | Records retained after TNRS resolution (score and status thresholds) |
| `after_customized_filter` | Records retained after the quality filter rules |
| `cleaned` | Records with valid coordinates after CoordinateCleaner |
| `problematic` | Records failing one or more coordinate tests |
| `native` | Records classified as native |
| `introduced` | Records classified as introduced |
| `extinct` | Records classified as extinct |
| `location_doubtful` | Records classified as location_doubtful |
| `unknown` | Records with no usable distribution evidence |
| `final_taxa` | Unique accepted taxon names in the final dataset |

The workflow progressively filters the dataset through taxonomic resolution, quality rules, and coordinate validation. The drop from `initial_records` to `after_customized_filter` reflects unresolved names and records failing the quality rules; the drop from `after_customized_filter` to `cleaned` reflects records that failed the coordinate tests being set aside (records without coordinates are not carried by `clean_coordinates()`); and the transition from `initial_taxa` to `final_taxa` reflects taxonomic resolution.

## Performance

VasGBIF achieves its speed through:

- **C/C++ backends**: core operations delegated to `data.table`, `stringi`, and `terra`
- **Vectorisation**: issue-flag detection in `extract_issues()` and native-status lookups in `detect_native_coord()` and `detect_native_country()` process entire columns in compiled calls
- **Memory-efficient design**: in-place modification (`:=`, `set()`) avoids unnecessary copies
- **Selective parallelisation**: `par_clean_coordinates()` distributes CoordinateCleaner validation across threads

On a standard laptop, VasGBIF compiles one million occurrence records within 15 minutes.

## References

GBIF.org (23 July 2026) GBIF Occurrence Download <https://doi.org/10.15468/dl.nt5exp>
