--- title: "Location geocoding workflow" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Location geocoding workflow} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>", eval = FALSE, purl = FALSE) ``` `locatr` turns a messy address table into an audit-ready crosswalk. The chunks below are not evaluated because they need live geocoding services, but they show the intended end-to-end run. The package assumes you already have a data frame; it does not connect to databases or source systems. `locatr` does not replace `tidygeocoder`; it builds on it. tidygeocoder is the geocoding engine for the main batch passes. `locatr` adds the workflow layer: address cleaning, bad-address flagging, region validation, tier-by-tier audit columns, local geography joins, review exports, manual overrides, and final crosswalk output. If your addresses are already clean and you only need one service's coordinates, call `tidygeocoder::geo()` or `tidygeocoder::geocode()` directly. Use `locatr` when the coordinates need to be defensible and reusable. ## 1. Pull and clean ```{r, eval=FALSE, purl=FALSE} library(locatr) cleaned <- records %>% clean_addresses( id = `Location ID`, address = Address, city = City, zip = Zip, name = `Location Name` ) %>% flag_bad_addresses() ``` `clean_addresses()` adds `*_clean` columns and a `full_address_clean` string; `flag_bad_addresses()` sends PO boxes and placeholders straight to review. Only address and city are required. If your file has no ID, locatr generates row-number IDs; if it has no ZIP, `zip_clean` stays `NA` and the single-line address omits the ZIP instead of ending in `NA`: ```{r, eval=FALSE, purl=FALSE} cleaned_minimal <- records %>% clean_addresses(address = Address, city = City, state = "NJ") %>% flag_bad_addresses() ``` Missing ZIP is recorded as `bad_address_flag == "missing_zip"` for audit, but it does not block geocoding when address + city + state are present. For one-off review, use `geocode_address()` to see ranked ArcGIS candidates for a single address: ```{r, eval=FALSE, purl=FALSE} if (interactive()) { geocode_address("1600 Pennsylvania Ave NW", city = "Washington", state = "DC") } ``` ## 2. Geocode with a guarded cascade ```{r, eval=FALSE, purl=FALSE} geocoded <- geocode_records(cleaned) # Census -> ArcGIS -> name lookup ``` `geocode_records()` runs each tier in turn and validates against the configured region after each pass, so a later, fuzzier service only retries what is still unplaced. The `geocode_pass` column records which tier placed each row. ## 3. Geography join ```{r, eval=FALSE, purl=FALSE} with_geography <- add_county_muni(geocoded, state = "NJ") ``` The geography step is independent of the input address columns once coordinates exist. `add_county_muni()` builds Census TIGER/Line geography and attaches county/locality fields. Pass an `sf` boundary layer to adapt the geography join, e.g. `add_muni_from_shapes(geocoded, muni_shapes = my_local_shapes)`, or use `add_muni_from_key()` when your records and geography share a code column. ## 4. Review, override, export ```{r, eval=FALSE, purl=FALSE} write_geocode_review(with_geography, "manual_review.csv") final <- with_geography %>% apply_manual_overrides("manual_review_completed.csv") %>% export_location_crosswalk("location_crosswalk.csv") ``` `final` is ready for Tableau, GIS joins, or a reusable reference table, and every row carries the audit columns that explain how its coordinate was produced. ## 5. Reproducible runs and provenance Because the cascade calls external services, reuse a `locatr_cache()` to make a run reproducible and cheap to repeat: ```{r, eval=FALSE, purl=FALSE} cache <- locatr_cache("geocode_cache.rds") # omit the path for a memory-only cache geocoded <- geocode_records(cleaned, cache = cache) # a repeat run replays cached coordinates instead of re-querying geocoded_again <- geocode_records(cleaned, cache = cache) cache_info(cache) ``` The cache is keyed by the exact query and request parameters and stores one row per candidate result (with a no-match sentinel so misses replay too). Pass `refresh = TRUE` to re-query and overwrite. Nothing is written to disk unless you give `locatr_cache()` a path. Every `geocode_records()` result also carries a run manifest and two per-row provenance columns: ```{r, eval=FALSE, purl=FALSE} geocode_provenance(geocoded) geocoded[, c("record_id", "placed_at", "cache_status")] ``` `cache_status` is `fresh`, `cached`, `reference`, `manual`, or `unplaced`, and `placed_at` is when the coordinate actually entered the output (the cached timestamp for cached rows, not the current run). Both columns are carried into `export_location_crosswalk()`. The manifest is attached as an attribute, so read it with `geocode_provenance()` right after the run, before any later data-frame operation that might drop attributes.