Repository: bcgov/bcstats-govt-service-locations
Pull request #6: Create maps
Author: login: BonnieJRobert
Created: 2025-04-11T17:23:45Z | Merged: 2025-04-15T22:11:14Z

Please review changes to the following files:

`settings.R`
`R/00-make-data.R` 
`R/00b-make-map-data.R` - mostly preprocessing shapefiles for reduced loading time
`R/fxns/preprocessing.R` - I added `create_crosswalk()` function
`R/fxns/plot.R` - I added the `build_map()` function
`R/plots.R` - I added code to create maps here




Diff:
diff --git a/R/00-make-data.R b/R/00-make-data.R
index 450e51e..e30fbbe 100644
--- a/R/00-make-data.R
+++ b/R/00-make-data.R
@@ -85,6 +85,24 @@ processed_files <- purrr::walk2(
   facility_tag = FACILITY_TAG
 )
 
+#------------------------------------------------------------------------------
+# Create a crosswalk for daid, duid and locality
+#------------------------------------------------------------------------------
+crosswalk_list <- purrr::map2(
+  .x = file_paths$fn,
+  .y = file_paths$loc,
+  .f = create_crosswalk
+)
+
+crosswalk <- bind_rows(crosswalk_list)
+outfile <- glue("{SRC_DATA_FOLDER}/da-db-loc-crosswalk.csv")
+tryCatch({
+  write_csv(crosswalk, outfile)
+}, error = function(e) {
+  message(glue("Error writing file {outfile}:  {e$message}"))
+})
+
+
 # clean up the environment
 rm(list = ls())
 gc()
diff --git a/R/00b-make-map-data.R b/R/00b-make-map-data.R
new file mode 100644
index 0000000..9c4f7b6
--- /dev/null
+++ b/R/00b-make-map-data.R
@@ -0,0 +1,121 @@
+# Copyright 2025 Province of British Columbia
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# ------------------------------------------------------------------------
+# Script: 00b-make-map-data.R
+
+# Description: Loads and processes provincial DA and DB shapefiles.
+# To reduce disk space and load time for development, specific localities are kept,
+# based on a crosswalk file. The processeed shapefiles are written to to disk.
+
+# Requirements:
+#   - Requires R packages: `tidyverse`, `glue`, `janitor`, `sf`.
+#   - Depends on `settings.R` for paths and constants.
+#   - Requires input DA/DB shapefiles and the crosswalk CSV file.
+#   - Requires read/write access to relevant data folders.
+
+# Side Effects/Outputs:
+#   - Writes shapefiles containing filtered DA/DB geometries
+#     with location IDs to disk.
+#   - Prints errors and stops execution if inputs are missing/invalid or
+#     joins yield no results.
+# ------------------------------------------------------------------------
+
+library(tidyverse)
+library(glue)
+library(janitor)
+library(sf)
+
+source("R/settings.R")
+
+
+# -----------------------------------------------------------------------------------------
+# Load and prepare da-db-location crosswalk file
+# -----------------------------------------------------------------------------------------
+
+crosswalk <- tryCatch({
+  read_csv(CROSSWALK_FILEPATH, col_types = cols(.default = "c"))
+}, error = function(e) {
+  stop(glue("Failed to read or process crosswalk file '{CROSSWALK_FILEPATH}': {e$message}"))
+})
+
+if (nrow(crosswalk) == 0) {
+  stop("Crosswalk data is empty after processing.")
+}
+
+# -----------------------------------------------------------------------------------------
+# Load and prepare da shapefile - check for invalid data
+# -----------------------------------------------------------------------------------------
+
+da_shapefiles_processed <- tryCatch({
+    st_read(DA_SHAPE_FILEPATH) %>%
+    filter(PRUID == "59") %>%
+    st_transform(crs = 3005) %>%
+    clean_names() %>%
+    select(daid = dauid, landarea, geometry)
+}, error = function(e) {
+  stop(glue("Failed to read or process da shapefile '{DA_SHAPE_FILEPATH}': {e$message}"))
+})
+
+if (nrow(da_shapefiles_processed) == 0) {
+  stop("DA shapefile data is empty after filtering for BC.")
+}
+
+# -----------------------------------------------------------------------------------------
+# Load and prepare db shapefile - check for invalid data
+# -----------------------------------------------------------------------------------------
+
+db_shapefiles_processed <- tryCatch({
+    st_read(DB_SHAPE_FILEPATH) %>%
+    filter(PRUID == "59") %>%
+    st_transform(crs = 3005) %>%
+    clean_names() %>%
+    select(dissemination_block_id = dbuid, landarea, geometry)
+
+}, error = function(e) {
+  stop(glue("Failed to read or process da shapefile '{DB_SHAPE_FILEPATH}': {e$message}"))
+})
+
+if (nrow(db_shapefiles_processed) == 0) {
+  stop("DB shapefile data is empty after filtering for BC.")
+}
+
+
+# -----------------------------------------------------------------------------------------
+# Filter shapefile to include only those da/db's in our localities of interest
+# could do some light error checking here, but we don't have a true mapping from
+# location to da/db (locality is still not definded and we don't have shapefiles for those)
+# -----------------------------------------------------------------------------------------
+
+db_with_location  <- db_shapefiles_processed  %>%
+   inner_join(crosswalk %>% distinct(daid, dissemination_block_id, location_id), by = "dissemination_block_id")
+
+if (nrow(db_with_location) == 0) {
+  stop("No DAs after joining with the crosswalk.")
+}
+
+da_with_location  <- da_shapefiles_processed  %>%
+   inner_join(crosswalk %>% distinct(daid, location_id), by = "daid")
+
+if (nrow(da_with_location) == 0) {
+  stop("No DAs after joining with the crosswalk.")
+}
+
+
+# -----------------------------------------------------------------------------------------
+# write processed da/db shapefiles to source folder
+# -----------------------------------------------------------------------------------------
+# TODO - add tryCatch logic
+st_write(da_with_location, glue("{SHAPEFILE_OUT}/processed_da_with_location.shp"))
+st_write(db_with_location, glue("{SHAPEFILE_OUT}/processed_db_with_location.shp"))
diff --git a/R/01-descriptive-tables.R b/R/01-descriptive-tables.R
index 97f4198..ff02477 100644
--- a/R/01-descriptive-tables.R
+++ b/R/01-descriptive-tables.R
@@ -167,6 +167,24 @@ tryCatch({
   message(glue("Error writing file {outfile}:  {e$message}"))
 })
 
+#------------------------------------------------------------------------------
+# Write service bc locations to source folder
+#------------------------------------------------------------------------------
+outfile <- SBCLOC_FILEPATH
+
+if (file.exists(outfile)) {
+  warning(glue("Overwriting existing file: {outfile}"))
+}
+
+service_bc_locations <- data %>% 
+  distinct(loc, nearest_facility, coord_x, coord_y)
+
+tryCatch({
+  write_csv(service_bc_locations, outfile)
+}, error = function(e) {
+  message(glue("Error writing file {outfile}:  {e$message}"))
+})
+
 # clean up the environment
 rm(list = ls())
 gc()
diff --git a/R/02-pilot-locality-regions.R b/R/02-pilot-locality-regions.R
new file mode 100644
index 0000000..d0ff1ee
--- /dev/null
+++ b/R/02-pilot-locality-regions.R
@@ -0,0 +1,146 @@
+# Copyright 2025 Province of British Columbia
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script loads aggregated csv data files containing spatial data for 
+# municipality of interest in BC (loc). 
+# It plots the locations on top of a map of B.C. 
+
+# ------------------------------------------------------------------------
+# Script: 02-plot-localities.R
+
+# Description: plot localities on a map of BC
+
+# Requirements:
+#   - Requires R packages: `tidyverse`, `glue`, `janitor`, `sf`. `rmapshaper`, `rnaturalearth`
+#   - Depends on `settings.R` for paths and constants.
+#   - Requires input DA/DB shapefiles and the crosswalk CSV file.
+#   - Requires read/write access to relevant data folders.
+
+# Side Effects/Outputs:
+#   - Makes a map of localities we used
+# ------------------------------------------------------------------------
+
+library(tidyverse)
+library(glue)
+library(janitor)
+library(sf)
+library(rmapshaper)    # simplify geometries
+library(bcmaps) # get BC outline
+
+source("R/settings.R")
+source("R/fxns/pre-processing.R") # for read_all_locs()
+# ----------------------------------------------------------------------------
+# Load and prepare mapping data
+# ----------------------------------------------------------------------------
+
+# map of BC
+bc_map <- bc_bound() |> 
+  st_transform(crs = 3005)
+
+# da level shapefiles for each locality
+fn <- glue("{SHAPEFILE_OUT}/processed_da_with_location.shp")
+da_shapefile <- tryCatch({
+  st_read(fn) %>%
+    rename("landarea" = "landare",
+           "loc" = "loctn_d") %>%
+    mutate(across(c(daid, loc), as.character)) # Explicitly declare data types on join columns
+}, error = function(e) {
+  message(glue("Error reading or processing file {fn}: {e$message}"))
+  return(NULL) # Return NULL on error
+})
+
+# simplify geographies to 1 per locality
+localities <- da_shapefile |> 
+  group_by(loc) |> 
+  summarize(geometry = st_union(geometry)) |> 
+  ungroup() |> 
+  mutate(
+    locality = paste0(LOC_LIST[loc],' (',loc,')')
+  )
+
+# get centroids for labels
+locality_centroids <- st_centroid(localities) 
+locality_centroids_nudged <- locality_centroids |> 
+mutate(
+    geometry = case_when(
+      loc == '227' ~ geometry + c(-150000, 40000),
+      loc == '909' ~ geometry + c(130000, 10000), # move east for langford
+      TRUE ~ geometry + c(0, 70000)  # move label north to not overlap 
+      )
+    )|>  
+  st_set_crs(st_crs(locality_centroids))
+
+# locations of all nearby SBC locations 
+sbc_locs <- read_csv(glue("{SRC_DATA_FOLDER}/service_bc_locs.csv")) |>
+  st_as_sf(
+    coords = c('coord_x', 'coord_y'),
+    crs = 3005
+  )
+
+#-----------------------------------------------------------------------------
+# build map 
+#-----------------------------------------------------------------------------
+
+out <- ggplot() + 
+  geom_sf(
+    data = bc_map, 
+    fill = 'white', 
+    color='black'
+    ) +
+  geom_sf(
+    data = localities, 
+    aes(fill = as.factor(locality)),
+    color = 'darkgrey',
+    alpha = 1
+  ) +
+  geom_sf(
+    data = sbc_locs,
+    shape = 23,
+    fill = 'yellow',
+    color = 'black',
+    size = 2,
+    stroke = 1.1
+  ) +
+  geom_sf_text(
+    data = locality_centroids_nudged,
+    aes(label = locality), 
+    size=4, 
+    fontface='bold'
+    ) +
+  scale_fill_brewer(
+    palette = 'Set2',
+    name = 'Locality'
+    ) +
+  coord_sf(expand = FALSE) + # remove whitespace
+  labs(
+    title = 'Pilot Regions',
+    
+  ) +
+  MAP_THEME +
+  theme(
+    legend.position="none",
+    axis.title = element_blank()
+    )
+
+out
+
+# save
+ggsave(
+   filename = glue("pilot_regions.png"),
+   path = MAP_OUT,
+   plot = out,
+   width = 8,
+   height = 7,
+ dpi = 300
+)
diff --git a/R/02-plots.R b/R/02-plots.R
new file mode 100644
index 0000000..fdb7ee9
--- /dev/null
+++ b/R/02-plots.R
@@ -0,0 +1,237 @@
+# Copyright 2025 Province of British Columbia
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script loads aggregated csv data files containing spatial data for 
+# municipality of interest in BC (loc). It produces maps at the dissemination block level 
+# displaying quantitative information on basic descriptive statisics
+
+# ------------------------------------------------------------------------
+# Script: 02-plots.R
+
+# Description: Loads previously processed DA and DB shapefiles (containing location IDs)
+# and corresponding processed drive time summary statistics CSV files. Prepares the final 
+# spatial data frames ready for mapping.  A sample map visualization is generated and saved to disk.
+
+# Requirements:
+#   - Requires R packages: `tidyverse`, `glue`, `janitor`, `sf`, `svglite`, `scales`
+#   - Depends on `settings.R` for configuration constants (paths, `MAP_THEME`).
+#   - Requires shapefiles in `SHAPEFILE_DIR`.
+#   - Requires drive time summary CSV files in `SRC_DATA_FOLDER` containing variables (numeric type) to map
+#   - Requires the `build_map` function in `R/fxns/plots.r`
+
+# Side Effects/Outputs:
+#   - Calls `build_map` function, which prints a ggplot map object to
+#     the default graphics device.
+#   - Prints errors and stops execution if input files are missing.
+# ------------------------------------------------------------------------
+
+
+library(tidyverse)
+library(glue)
+library(janitor)
+library(sf)
+library(svglite)
+library(scales)
+library(snakecase)
+
+source("R/settings.R")
+source("R/fxns/plots.R")
+
+#------------------------------------------------------------------------------
+# Read shape file data from source folder
+# Note: saving shapeiles truncates the column names, reassignment needed
+#------------------------------------------------------------------------------
+
+fn <- glue("{SHAPEFILE_OUT}/processed_da_with_location.shp")
+da_shapefile <- tryCatch({
+  st_read(fn) %>%
+  rename("landarea" = "landare",
+         "loc" = "loctn_d") %>%
+  mutate(across(c(daid, loc), as.character)) # Explictly declare data types on join columns
+}, error = function(e) {
+   message(glue("Error reading or processing file {fn}: {e$message}"))
+   return(NULL) # Return NULL on error
+})
+
+if (is.null(da_shapefile)) {
+  stop("Failed to load or process DA shapefile. Stopping script.")
+}
+
+fn <- glue("{SHAPEFILE_OUT}/processed_db_with_location.shp")
+db_shapefile <- tryCatch({
+  st_read(fn) %>%
+  rename("dissemination_block_id" = "dssmn__",
+         "landarea" = "landare",
+         "loc" = "loctn_d") %>% 
+  mutate(across(c(dissemination_block_id, loc), as.character)) # Explictly declare data types on join columns
+}, error = function(e) {
+   message(glue("Error reading or processing file {fn}: {e$message}"))
+   return(NULL) # Return NULL on error
+})
+
+if (is.null(db_shapefile)) {
+  stop("Failed to load or process DB shapefile. Stopping script.")
+}
+
+#------------------------------------------------------------------------------
+# Read drive time data from source folder and
+# keep all shapes and potentially colour the missing ones differently
+#------------------------------------------------------------------------------
+fn <- glue("{SRC_DATA_FOLDER}/da_average_times_dist_all_locs.csv")
+da_drivetime_data <- tryCatch({
+  read_csv(fn) %>%
+  clean_names() %>%
+  mutate(across(c(daid, loc), as.character)) # Explictly declare data types on join columns
+}, error = function(e) {
+   message(glue("Error reading or processing file {fn}: {e$message}"))
+   return(NULL) # Return NULL on error
+})
+
+if (is.null(da_drivetime_data)) {
+  stop("Failed to load or process DA drivetime data. Stopping script.")
+}
+
+fn <- glue("{SRC_DATA_FOLDER}/db_average_times_dist_all_locs.csv")
+db_drivetime_data <-  tryCatch({
+  read_csv(fn) %>%
+  clean_names() %>%
+  mutate(across(c(dissemination_block_id, loc), as.character)) # Explictly declare data types on join columns
+}, error = function(e) {
+   message(glue("Error reading or processing file {fn}: {e$message}"))
+   return(NULL) # Return NULL on error
+})
+
+if (is.null(db_drivetime_data)) {
+  stop("Failed to load or process DB drivetime data. Stopping script.")
+}
+
+#------------------------------------------------------------------------------
+# Read service bc location data from source folder
+#------------------------------------------------------------------------------
+
+fn <- SBCLOC_FILEPATH
+servicebc <- tryCatch({
+  read_csv(fn) %>%
+  mutate(across(c(loc), as.character)) %>% # Explictly declare data types on join columns
+  st_as_sf(coords = c("coord_x", "coord_y"), crs = 3005)
+}, error = function(e) {
+   message(glue("Error reading or processing file {fn}: {e$message}"))
+   return(NULL) # Return NULL on error
+})
+
+if (is.null(servicebc)) {
+  stop("Failed to load or process DA shapefile. Stopping script.")
+}
+
+#------------------------------------------------------------------------------
+# Join shapefiles to data for mapping
+# Use left_join to color differently those da/db's missing data
+#------------------------------------------------------------------------------
+da_drivetime_map_data <- da_shapefile %>%
+  left_join(da_drivetime_data, by = join_by(daid, loc)) 
+
+if (nrow(da_drivetime_map_data) == 0)  {
+  stop("No DA map data after joining with shapefiles")
+}
+
+na_prop <- sum(is.na(da_drivetime_map_data$n_address))/ nrow(da_drivetime_map_data)
+message(glue("({percent(na_prop)}) of NAs in DA map data"))
+
+low_counts_prop <- sum(da_drivetime_map_data$n_address < 5) / nrow(da_drivetime_map_data)
+message(glue("({percent(low_counts_prop)}) of DA regions contain fewer than 5 observations"))
+
+db_drivetime_map_data <- db_shapefile %>%
+  left_join(db_drivetime_data, by = join_by(dissemination_block_id, loc))
+
+if (nrow(db_drivetime_map_data) == 0)  {
+  stop("No DB map data after joining with shapefiles")
+}
+
+na_prop <- sum(is.na(db_drivetime_map_data$n_address))/ nrow(db_drivetime_map_data)
+message(glue("({percent(na_prop)}) of NAs in DB map data"))
+
+low_counts_prop <- sum(db_drivetime_map_data$n_address < 5) / nrow(db_drivetime_map_data)
+message(glue("({percent(low_counts_prop)}) of DB regions have fewer than 5 observations"))
+
+
+# ------------------------------------------------------------------
+# testing only
+# just for checking low counts
+# ------------------------------------------------------------------
+
+da_drivetime_data$loc_name <-unlist(LOC_LIST[da_drivetime_data$loc])
+db_drivetime_data$loc_name <-unlist(LOC_LIST[db_drivetime_data$loc])
+
+da_drivetime_data %>%
+  filter(n_address < 5) %>%
+  select(!starts_with("drv")) %>%
+  sf::st_drop_geometry() %>%
+  write_csv(glue("{TABLES_OUT }/low_counts_da.csv"))
+
+db_drivetime_data %>%
+  filter(n_address < 5) %>%
+  select(!starts_with("drv")) %>%
+  sf::st_drop_geometry() %>%
+  write_csv(glue("{TABLES_OUT }/low_counts_db.csv"))
+
+#------------------------------------------------------------------------------
+# build map - this is where we provide options for build map function
+#------------------------------------------------------------------------------
+
+# user-defined map parameters
+var <- "n_address"  # colnames(map_data) for other options
+var_title <- "Count of Addresses"
+region_title <- "Dissemination Area"
+plot_subtitle <- "(Regions with 0-4 data points are shown in red)"
+
+map_data  <- db_drivetime_map_data
+
+if(region_title == "Dissemination Area"){
+  map_data  <- da_drivetime_map_data
+}
+
+map_data <- map_data %>%
+  mutate(n_address = if_else(n_address < 5, NA, n_address))
+
+for (loc in names(LOC_LIST)) {
+  loc_name <- LOC_LIST[[loc]]
+  message(glue("Generating map for {loc_name} ({loc})..."))
+
+  plot_title <- glue("{var_title} by {region_title}, {loc_name}")
+
+  map_plot <- build_map(
+    data = map_data,
+    servicebc_data = servicebc,
+    varname = var,
+    loc_id = loc,
+    map_theme = MAP_THEME,
+    fill_scale = FILL_THEME,
+    plot_title = plot_title,
+    plot_subtitle = plot_subtitle,
+    legend_title = var_title
+  )
+
+  # Save the plot
+  fn <- to_snake_case(glue("{var} by {region_title}, {loc_name}"))
+  fn <- glue("{fn}.svg")
+  ggsave(
+    filename = fn,
+    path = MAP_OUT,
+    plot = map_plot,
+    width = 8,
+    height = 7,
+    device = "svg"
+  )
+}
+
diff --git a/R/02-visualizations.R b/R/02-visualizations.R
deleted file mode 100644
index 1e0c8f2..0000000
--- a/R/02-visualizations.R
+++ /dev/null
@@ -1,88 +0,0 @@
-# Copyright 2025 Province of British Columbia
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#    http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# This script loads aggregated csv data files containing spatial data for 
-# municipality of interest in BC (loc). It produces maps at the dissemination block level 
-# displaying quantitative information on basic descriptive statisics
-
-source("R/settings.R")  # load constants and other settings (including libraries)
-source("fxns/maps.R") # functions for plotting maps
-
-#------------------------------------------------------------------------------
-# Load Reqd Libraries
-#------------------------------------------------------------------------------
-
-library(tidyverse)
-library(safepaths)
-library(glue)
-library(janitor)
-library(e1071)
-library(sf)
-
-## some of these 
-# library(cancensus) 
-# library(cansim)
-# library(bcmaps) 
-# library(geojsonsf) 
-# library(jsonlite) imported by bcmap
-# library(cowplot) for aligning multiple plots 
-# library(patchwork)
-# Load the rlang package for the bang-bang operator - imported by cowplot
-# library(rlang) 
-
-#------------------------------------------------------------------------------
-# load aggregated data average drive time and distance by DA and DB 
-# Langford: locality 909
-# Smithers: locality 227
-# Dawson Creek: locality 213
-# Kamploops: locality 420
-#------------------------------------------------------------------------------
-
-data_folder <- safepaths::use_network_path()
-loc <- "213" # Dawson Creek: locality 213
-outfolder_da <- glue::glue("{data_folder}/data/da_average_times_dist_loc_{loc}")
-outfolder_db <- glue::glue("{data_folder}/data/db_average_times_dist_loc_{loc}")
-
-#------------------------------------------------------------------------------
-# DA shp file
-#------------------------------------------------------------------------------
-download.file("https://www12.statcan.gc.ca/census-recensement/2021/geo/sip-pis/boundary-limites/files-fichiers/lda_000b21a_e.zip", # nolint: line_length_linter.
-              destfile = glue::glue("{data_folder}/data/raw/boundaries/lda_000a21a_e.zip")) # nolint: line_length_linter.
-
-# file_path <- glue::glue("{data_folder}/data/raw/lda_000a21a_e/lda_000b21a_e.shp") # nolint
-file_path <- "data/lda_000a21a_e/lda_000b21a_e.shp"
-
-# Load the dissemination area shapefile
-da_shapefile <- st_read(file_path)
-da_shapefile <- da_shapefile %>%
-  filter(PRUID == "59") %>%
-  st_transform(crs = 3005)
-
-#------------------------------------------------------------------------------
-# DB shp file
-#------------------------------------------------------------------------------
-
-plot.dat <- da_shapefile %>% 
-  inner_join(avg_dist_drvtime_by_db_service %>%
-               distinct(daid), by = c("DAUID" = "daid")) # nolint: line_length_linter.
-
-ggplot(plot.dat) +
-  geom_sf() +
-  geom_sf(aes(address_sf_with_da))
-
-ggplot() +
-  geom_sf(data = plot.dat, fill = "white") +
-  geom_sf(data = address_sf_with_da,
-          aes(color = drv_dist, alpha = 0.1)) +
-  theme_minimal()
diff --git a/R/fxns/maps.r b/R/fxns/maps.r
deleted file mode 100644
index 618ad02..0000000
--- a/R/fxns/maps.r
+++ /dev/null
@@ -1,350 +0,0 @@
-# ------------------------------------------------------------------------------
-# Validation: color map
-# ------------------------------------------------------------------------------
-
-my_map_theme <- theme_minimal() +
-  theme(
-    plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
-    plot.subtitle = element_text(hjust = 0.5, size = 12),
-    legend.title = element_text(size = 12),
-    legend.text = element_text(size = 10)
-  )
-
-
-# ------------------------------------------------------------------------------
-# This function generates a map for a specified Census Subdivision (CSD) that
-# displays the average distance to a given facility
-
-# The functions takes the following arguments:
-# facility_name: string specifying the type of facility to plot
-# fill_var: string specifying the variable name to plot
-# fill_var_name: string specifying the name of the variable for legend/labels
-# label_var Logical value - include labels showing the average distance values?
-
-# The function returns A ggplot object representing the map.
-# ------------------------------------------------------------------------------
-
-plot_bc_address_map <- function(
-  data,
-  address_sf = address_sf_with_da,
-  fill_var = "avg_drv_dist",
-  fill_var_name = "Average Drive Distance",
-  facility_name = "servicebc",
-  save_png = T
-) {
-  address_avg_dist_da_facility_p <- ggplot(
-    data = data %>% filter(tag == facility_name)
-  ) +
-    geom_sf(
-      aes(fill = !!sym(fill_var)),
-      color = "gray"
-    ) +
-    scale_fill_viridis_c(option = "viridis") +
-    geom_sf(
-      data = address_sf %>% filter(tag == facility_name),
-      color = "red",
-      size = 1,
-      fill = "red",
-      alpha = 0.1
-    ) +
-    theme_minimal() +
-    labs(
-      title = glue::glue(
-        "{fill_var_name} to {facility_name}"
-      )
-    ) +
-    my_map_theme +
-    guides(
-      fill = guide_legend(
-        title = glue::glue("{fill_var_name}")
-      )
-    )
-
-  print(address_avg_dist_da_facility_p)
-  # Save the plot
-  if (save_png) {
-    ggsave(
-      glue::glue(
-        "./out/address_{facility_name}_{fill_var_name}_with_da_map.png"
-      ),
-      address_avg_dist_da_facility_p,
-      width = 10,
-      height = 8,
-      dpi = 300
-    )
-  }
-}
-
-#' Plot a Map of Census Subdivisions (CSDs) Showing the Average Distance to a Specific Facility
-#'
-#' This function generates a map of Census Subdivisions (CSDs) displaying the average distance to a specified facility (e.g., servicebc, schools, hospitals).
-#'
-#' @param csd_shapefile Simple Features object containing the CSD boundaries and average distance data.
-#' @param fill_var Character string specifying the variable name in the data frame that contains the average distance values.
-#' @param fill_var_name Character string specifying the name of the variable to use in the legend and labels.
-#' @param facility_name Character string specifying the type of facility to plot (e.g., "servicebc", "schools", "hospitals").
-#' @param save_png Logical value indicating whether to save the plot as a PNG file (default: TRUE).
-#'
-#' @return A ggplot object representing the CSD map.
-#' @export
-
-plot_csd_avg_map_fn <- function(
-  csd_shapefile,
-  fill_var = "AVG_DRV_DIST",
-  fill_var_name = "Average Drive Distance",
-  facility_name = "servicebc",
-  save_png = T
-) {
-  csd_p <- ggplot(
-    data = csd_shapefile %>% filter(TAG_2 == facility_name)
-  ) +
-    geom_sf(aes(fill = !!sym(fill_var)), color = "gray") +
-    scale_fill_viridis_c(option = "viridis") +
-    labs(
-      title = glue::glue("{fill_var_name} to {facility_name} by CSD"),
-      subtitle = "Remoteness measurement in BC",
-      x = "Longitude",
-      y = "Latitude"
-    ) +
-    my_map_theme +
-    guides(
-      fill = guide_legend(title = glue::glue("{fill_var_name}"))
-    )
-
-  print(csd_p)
-
-  # Save the plot
-  if (save_png) {
-    ggsave(
-      glue::glue("./out/csd_{fill_var_name}_dist_{facility_name}.png"),
-      csd_p,
-      width = 10,
-      height = 8,
-      dpi = 300
-    )
-    ggsave(
-      use_network_path(
-        glue::glue(
-          "2024 SES Index/data/output/remoteness/csd_{fill_var_name}_{facility_name}.png"
-        )
-      ),
-      csd_p,
-      width = 10,
-      height = 8,
-      dpi = 300
-    )
-  }
-}
-
-
-#' Plot a Map for a Given Census Subdivision (CSD) Showing the Average Distance to a Specific Facility
-#'
-#' This function generates a map for a specified Census Subdivision (CSD) that displays the average distance to a given facility (e.g., servicebc, schools, hospitals).
-#'
-#' @param csd Character string specifying the name of the Census Subdivision (CSD) to plot.
-#' @param facility Character string specifying the type of facility to plot (e.g., "servicebc", "schools", "hospitals").
-#' @param fill_var Character string specifying the variable name in the data frame that contains the average distance values.
-#' @param fill_var_name Character string specifying the name of the variable to use in the legend and labels.
-#' @param csd_sf Simple Features object containing the CSD boundaries and average distance data.
-#' @param facility_sf Simple Features object containing the locations of the service facilities.
-#' @param min_val Numeric value specifying the minimum value for the fill scale (default: 0).
-#' @param max_val Numeric value specifying the maximum value for the fill scale (default: 12).
-#' @param step Numeric value specifying the step size for the fill scale breaks (default: 1.5).
-#' @param label_var Logical value indicating whether to include labels showing the average distance values (default: FALSE).
-#'
-#' @return A ggplot object representing the map.
-#' @export
-plot_csd_facility_map_fn <- function(
-  csd,
-  facility,
-  fill_var,
-  fill_var_name,
-  csd_sf,
-  facility_sf,
-  min_val = 0,
-  max_val = 12,
-  step = 1.5,
-  label_var = FALSE
-) {
-  # Define constants for better readability
-  CONST_MIN_VAL <- min_val
-  CONST_MAX_VAL <- max_val
-  CONST_STEP <- step
-
-  # Filter CSD data for the specified CSD and facility
-  csd_data <- csd_sf %>%
-    filter(MUN_NAME_2021 == csd) %>%
-    filter(TAG_2 == facility)
-
-  # Filter facility data for the specified CSD and facility
-  facility_data <- facility_sf %>%
-    filter(MUN_NAME_2021 == csd) %>%
-    filter(TAG_2 == facility)
-
-  # Create the map plot
-  plot <- csd_data %>%
-    ggplot() +
-    geom_sf(aes(fill = !!sym(fill_var))) +
-    scale_fill_viridis_c(
-      limits = c(CONST_MIN_VAL, CONST_MAX_VAL),
-      breaks = seq(CONST_MIN_VAL, CONST_MAX_VAL, by = CONST_STEP)
-    )
-
-  # Add labels if requested
-  if (label_var) {
-    plot <- plot +
-      geom_sf_label(
-        aes(label = format(!!sym(fill_var), digits = 2)),
-        colour = "black"
-      )
-  }
-
-  # Add title, theme, and legend
-  plot <- plot +
-    labs(
-      subtitle = glue::glue(
-        "{fill_var_name} to {facility}"
-      ),
-      x = "Longitude",
-      y = "Latitude"
-    ) +
-    my_map_theme +
-    guides(
-      fill = guide_legend(
-        title = glue::glue(
-          "{stringr::str_to_title(fill_var_name)}"
-        )
-      )
-    )
-
-  # Add facility points
-  plot <- plot +
-    geom_point(
-      data = facility_data,
-      aes(geometry = geometry),
-      shape = 23,
-      color = "red",
-      fill = "red",
-      size = 5,
-      alpha = 0.5,
-      stat = "sf_coordinates"
-    )
-
-  return(plot)
-}
-
-
-#' Compare Two CSDs in a Map
-#'
-#' This function compares two Census Subdivisions (CSDs) by plotting their remoteness measurement on a map.
-#' It generates two separate plots for each CSD, displaying the average distance to a specified service (e.g., Service BC office),
-#' and combines them into a single plot with a shared title.
-#'
-#' @param CSD1 The name of the first Census Subdivision to compare.
-#' @param CSD2 The name of the second Census Subdivision to compare.
-#' @param facility The name of the facility to measure remoteness from (e.g., "servicebc").
-#' @param fill_var The variable name in the csd_sf data frame that represents the remoteness measurement (e.g., "AVG_DRV_DIST").
-#' @param fill_var_name A descriptive name for the fill_var variable to use in the plot legend.
-#' @param csd_sf A simple features data frame containing the Census Subdivision boundaries and remoteness measurement data.
-#' @param facility_sf A simple features data frame containing the facility locations.
-#' @param label_var1 A logical indicating whether to label the first CSD plot with facility names.
-#' @param label_var2 A logical indicating whether to label the second CSD plot with facility names.
-#'
-#' @return This function returns a combined plot of the two CSDs, displaying their remoteness measurement, and saves the plot to a file.
-#' @export
-compare_two_csd_in_map <- function(
-  CSD1 = "Vancouver",
-  CSD2 = "Smithers",
-  facility = "servicebc",
-  fill_var = "AVG_DRV_DIST",
-  fill_var_name = "Average distance",
-  csd_sf = CSD_DA_avg_dist_drvtime_by_service_sf,
-  facility_sf = facility_sf,
-  label_var1 = FALSE,
-  label_var2 = TRUE,
-  save_png = T
-) {
-  # two CSDs maps should share the same color scale
-
-  color_range <- csd_sf %>%
-    filter(MUN_NAME_2021 == c(CSD1, CSD2)) %>%
-    filter(TAG_2 %in% c(facility)) %>%
-    filter(!is.na(!!sym(fill_var))) %>%
-    pull(!!sym(fill_var)) %>%
-    range()
-
-  min_val <- floor(color_range[1])
-  max_val <- ceiling(color_range[2])
-  step <- (max_val - min_val) / (12 - 1)
-
-  # Generate plots for both CSDs
-  plot_a <- plot_csd_facility_map_fn(
-    CSD1,
-    facility,
-    fill_var,
-    fill_var_name,
-    csd_sf,
-    facility_sf,
-    min_val,
-    max_val,
-    step,
-    label_var1
-  )
-
-  plot_b <- plot_csd_facility_map_fn(
-    CSD2,
-    facility,
-    fill_var,
-    fill_var_name,
-    csd_sf,
-    facility_sf,
-    min_val,
-    max_val,
-    step,
-    label_var2
-  )
-
-  # Combine the two plots using patchwork and add a shared title
-  combined_plot <- (plot_a +
-    plot_b +
-    plot_layout(ncol = 2, guides = "collect") &
-    theme(legend.position = "bottom")) +
-    plot_annotation(
-      title = glue::glue("Remoteness measurement: {CSD1} vs {CSD2}"),
-      theme = theme(
-        plot.title = element_text(
-          size = 16,
-          face = "bold",
-          hjust = 0.5
-        )
-      )
-    )
-
-  # Display the combined plot
-  print(combined_plot)
-
-  # Save the combined plot
-  if (save_png) {
-    ggsave(
-      glue::glue(
-        "./out/{CSD1}_{CSD2}_{facility}_{stringr::str_to_title(fill_var_name)}.png"
-      ),
-      combined_plot,
-      width = 14,
-      height = 7,
-      dpi = 300
-    )
-
-    ggsave(
-      use_network_path(
-        glue::glue(
-          "2024 SES Index/data/output/remoteness/two_csd_{CSD1}_{CSD2}_{facility}_{stringr::str_to_title(fill_var_name)}.png"
-        )
-      ),
-      plot = combined_plot,
-      width = 14,
-      height = 7,
-      dpi = 300
-    )
-  }
-}
\ No newline at end of file
diff --git a/R/fxns/plots.r b/R/fxns/plots.r
new file mode 100644
index 0000000..d2692f5
--- /dev/null
+++ b/R/fxns/plots.r
@@ -0,0 +1,121 @@
+# Copyright 2025 Province of British Columbia
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# ------------------------------------------------------------------------
+# Function: build_map
+
+# Description: Generates a thematic map (ggplot object) from an input spatial
+# data frame (`sf` object).  Allows customization of titles, theme, and 
+# fill color scale.
+
+# Inputs:
+#   - data: An sf data frame containing geometries and associated data,
+#   - varname: Character string specifying the name of the column in `data`
+#     to use for the map's fill aesthetic.
+#   - loc_id: The specific locality which we are mapping. 
+#   - loc_col: The name of the column in `data` that contains the locality identifiers. 
+#     Defaults to "loc".
+#   - plot_title: Optional character string for the main plot title. Defaults
+#     to empty string.
+#   - legend_title: Optional character string for the fill legend title.
+#     Defaults to empty string.
+#   - map_theme: Optional ggplot theme object. Defaults to `theme_minimal()`.
+#   - fill_scale: Optional ggplot continuous fill scale function. Defaults
+#                 to `scale_fill_viridis_c(option = "viridis")`.
+
+# Outputs:
+#   - Returns a ggplot object representing the thematic map for the specified
+#     locality and variable.
+#   - If no data remains after filtering by `loc_id`, returns an empty ggplot
+#     object with a title indicating no data, and prints a warning.
+# ------------------------------------------------------------------------
+
+build_map <- function(
+    data,
+    servicebc_data,
+    varname,
+    loc_id,
+    loc_col = "loc",
+    plot_title = "",
+    plot_subtitle = "",
+    legend_title = "",
+    map_theme = theme_minimal(),
+    fill_scale = scale_fill_viridis_c(option = "viridis"),
+    scale_limits = NULL
+) {
+
+  # --- Prepare arguments as symbols ---
+  varname_sym <- rlang::sym(varname)
+  loc_col_sym <- rlang::sym(loc_col)
+
+  # --- Prepare titles as strings --
+  default_title <- glue::glue("{varname} for Locality {loc_id}")
+  plot_title <- plot_title %||% default_title
+  legend_title <- legend_title %||% varname
+
+  # dynamically set limits
+  fill_theme <- fill_scale$clone()
+  fill_theme$limits <- range(data[[varname_sym]])
+  fill_theme$oob <- scales::squish
+
+  map_data <- data %>%
+    filter(!!loc_col_sym == loc_id)
+
+  points_data <- servicebc_data %>%
+    filter(!!loc_col_sym == loc_id)
+
+  # Check if filtering resulted in data
+  if (nrow(map_data) == 0) {
+    warning(glue("Warning: No data found for loc_id '{loc_id}'"))
+    return(ggplot() + theme_void() + labs(title = glue("No data for {loc_id}")))
+  }
+
+  ## Build the ggplot object
+  map <-  ggplot() +
+    geom_sf(
+        data = map_data,
+        aes(fill = !!varname_sym),
+        color = "gray50",
+        lwd = 0.1
+    ) +
+    fill_theme +
+    geom_sf(data = points_data,
+      aes(shape = "Nearest Service BC Location"),
+      fill = 'yellow',
+      color = 'black',
+      size = 2,
+      stroke = 1.1) +
+    scale_shape_manual(
+      name = NULL,
+      values = c("Nearest Service BC Location" = 23) 
+    ) +
+    guides(
+      shape = guide_legend(
+        override.aes = list(
+          fill = "yellow", 
+          size = 4)
+      )
+    ) +
+    labs(
+      title = plot_title,
+      subtitle = plot_subtitle,
+      fill = legend_title,
+      x = "\nLongitude",
+      y = "Latitude\n"
+    ) +
+    map_theme
+
+  return(map)
+
+}
diff --git a/R/fxns/pre-processing.r b/R/fxns/pre-processing.r
index 2bff482..d3cb061 100644
--- a/R/fxns/pre-processing.r
+++ b/R/fxns/pre-processing.r
@@ -166,3 +166,72 @@ read_all_locs <- function(fn){
 
   return(data)
 }
+
+
+# ------------------------------------------------------------------------
+# Function: create_crosswalk
+
+# Description: Reads a csv file passed as a parameter,and returns a data frame
+# unique combinations of `daid` and `dissemination_block_id`. Adds a column
+# containing the user specified `location_id`.
+#
+# Inputs:
+#   - filepath: Character string specifying the full path to the input CSV file.
+#   - location_id: Character string representing the location identifier to be
+#                  added to the crosswalk.
+#
+# Outputs:
+#   - Returns a data frame with columns `daid`, `dissemination_block_id`, and
+#     `location_id`, containing the unique combinations found in the input file.
+#   - Returns NULL if an error occurs during file reading or if required
+#     columns are missing.
+# ------------------------------------------------------------------------
+
+
+
+create_crosswalk <- function(filepath, location_id) {
+
+  # -----------------------------------------------------------------------------------------
+  # validate user-defined inputs - return NULL on failure
+  # -----------------------------------------------------------------------------------------
+  if (!is.character(filepath) || length(filepath) != 1) {
+    message("Error: 'filepath' must be a single character string.")
+    return(NULL) 
+  }
+  if (!is.character(location_id) || length(location_id) != 1) {
+    message("Error: 'location_id' must be a single character string.")
+    return(NULL)
+  }
+
+  # -----------------------------------------------------------------------------------------
+  # read data - return NULL on failure, check for missing data
+  # -----------------------------------------------------------------------------------------
+  data <- tryCatch({
+    read_csv(filepath, col_types = cols(.default = "c"), show_col_types = FALSE)
+  }, error = function(e) {
+    message(glue::glue("Error reading file '{filepath}': {e$message}"))
+    return(NULL)
+  })
+
+  if (is.null(data)) {
+    return(NULL)
+  }
+
+  if (!("DISSEMINATION_BLOCK_ID" %in% colnames(data))) {
+    message(glue::glue("Error: DISSEMINATION_BLOCK_ID column missing in '{filepath}'."))
+    return(NULL)
+  }
+
+# -----------------------------------------------------------------------------------------
+# Create crosswalk with unique combinations of daid and dissemination_block_id, and location_id
+# -----------------------------------------------------------------------------------------
+
+  crosswalk_data <- data %>%
+    select(DISSEMINATION_BLOCK_ID) %>% 
+    clean_names() %>%
+    mutate(location_id = {{ location_id }}) %>%
+    mutate(daid = str_sub(dissemination_block_id, 1, 8)) %>%
+    distinct(daid, dissemination_block_id, location_id)
+
+  return(crosswalk_data)
+}
diff --git a/R/settings.R b/R/settings.R
index 9efcff8..5a21690 100644
--- a/R/settings.R
+++ b/R/settings.R
@@ -45,6 +45,7 @@ library(glue)
 # Constants declaration
 #------------------------------------------------------------------------------
 EXPECTED_LOCALITIES <- c("909", "227", "213", "420")
+LOC_LIST <- setNames(as.list(c("Langford", "Dawson Creek", "Smithers", "Kamloops")), c("909", "227", "213", "420"))
 
 # File paths
 LAN_FOLDER <- use_network_path()
@@ -52,11 +53,21 @@ SRC_DATA_FOLDER <- glue("{LAN_FOLDER}/2025 Government Service Locations/data/sou
 RAW_DATA_FOLDER <- glue("{LAN_FOLDER}/2025 Government Service Locations/data/raw/")
 RAW_POP_FILEPATH <- glue("{RAW_DATA_FOLDER}/statscan/98100015-eng/98100015.csv")
 
+CROSSWALK_FILEPATH <- glue("{SRC_DATA_FOLDER}/da-db-loc-crosswalk.csv")
+DA_SHAPE_FILEPATH <-  glue("{RAW_DATA_FOLDER}/statscan/lda_000b21a_e/lda_000b21a_e.shp")
+DB_SHAPE_FILEPATH <-  glue("{RAW_DATA_FOLDER}/statscan/ldb_000b21a_e/ldb_000b21a_e.shp")
+SBCLOC_FILEPATH <- glue("{SRC_DATA_FOLDER}/service_bc_locs.csv")
+
 # Output filenames
 OUTPUT_DB_STATS_FILENAME  <- "db_average_times_dist_all_locs.csv"
 OUTPUT_DA_STATS_FILENAME  <- "da_average_times_dist_all_locs.csv"
 OUTPUT_LOC_STATS_FILENAME <- "loc_average_times_dist_all_locs.csv"
 
+
+SHAPEFILE_OUT <- glue("{SRC_DATA_FOLDER}/shapefiles/")
+MAP_OUT <- glue("{LAN_FOLDER}/2025 Government Service Locations/outputs/visuals")
+TABLES_OUT <- glue("{LAN_FOLDER}/2025 Government Service Locations/outputs/tables")
+
 # Patterns for cleaning
 POP_GUI_PREFIX_PATTERN <- "^2021S[0-9]{4}"
 POP_GUI_BC_PATTERN <- glue("{POP_GUI_PREFIX_PATTERN}59")
@@ -72,4 +83,20 @@ NO_ERRS_FILE_PATTERN <- "no_errors.csv"
 # Columns
 REQUIRED_COLS <- c("site_albers_x", "site_albers_y", "dissemination_block_id", "drv_time_sec", "drv_dist", "tag")
 POP_COLS <- c("region_name", "area_sq_km", "population", "dwellings", "households")
-FACILITY_TAG <- "servicebc"
\ No newline at end of file
+FACILITY_TAG <- "servicebc"
+
+# Constants for visualizations
+MAP_THEME <- theme_minimal() +
+  theme(
+    plot.title = element_text(hjust = 0.5, size = 14, face = "bold"),
+    plot.subtitle = element_text(hjust = 0.5, size = 10),
+    legend.title = element_text(size = 10, face = "bold"),
+    legend.text = element_text(size = 9),
+    axis.text = element_text(size = 8),
+    axis.title = element_text(size = 9),
+    legend.position = "bottom",
+    legend.box = "horizontal",
+    legend.title.position = "top")
+
+ FILL_THEME <- scale_fill_viridis_c(option = "mako", alpha = 0.75, na.value = "red")
+


Reviews:
user: login: lindsay-fredrick
body: I think this looks amazing overall! I just have a couple of requests:

* Could we have the plots show the nearest SBC locations that are within each region?
* Could we have the min/max color scale on each plot line up so that we can easily compare across regions?
* Could the legend be moved to below the plot? tends to take up less whitespace that way.
* Any way to color the missing DAs/DBs a different color? There's what looks like a couple of obviously missing regions that I'm not totally sure I understand why it's missing. 
* If you wanted, could you check out the code I just pushed that does a quick and dirty overview of which localities we are looking at overall? 
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T22:03:21Z
user: login: BonnieJRobert
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T18:22:08Z
user: login: BonnieJRobert
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T18:23:02Z
user: login: BonnieJRobert
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T18:23:37Z
user: login: BonnieJRobert
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T18:25:16Z
user: login: BonnieJRobert
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T18:30:56Z
user: login: BonnieJRobert
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T18:33:43Z
user: login: BonnieJRobert
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T18:36:37Z
user: login: lindsay-fredrick
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T18:51:57Z

Review comments:
diff_hunk: @@ -0,0 +1,165 @@
+# Copyright 2025 Province of British Columbia
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script loads aggregated csv data files containing spatial data for 
+# municipality of interest in BC (loc). It produces maps at the dissemination block level 
+# displaying quantitative information on basic descriptive statisics
+
+# ------------------------------------------------------------------------
+# Script: 02-plots.R
+
+# Description: Loads previously processed DA and DB shapefiles (containing location IDs)
+# and corresponding processed drive time summary statistics CSV files. Prepares the final 
+# spatial data frames ready for mapping.  A sample map visualization is generated and saved to disk.
+
+# Requirements:
+#   - Requires R packages: `tidyverse`, `glue`, `janitor`, `sf`.
+#   - Depends on `settings.R` for configuration constants (paths, `MAP_THEME`).
+#   - Requires shapefiles in `SHAPEFILE_DIR`.
+#   - Requires drive time summary CSV files in `SRC_DATA_FOLDER` containing variables (numeric type) to map
+#   - Requires the `build_map` function to be defined and available.
+#   - Requires read access to input files/paths.
+
+# Side Effects/Outputs:
+#   - Calls the `build_map` function, which prints a ggplot map object to
+#     the default graphics device.
+#   - Prints errors and stops execution if input files are missing.
+#   - (Currently does not write any new files to disk unless `build_map` is modified to do so).
+# ------------------------------------------------------------------------
+
+
+library(tidyverse)
+library(glue)
+library(janitor)
+library(sf)
+
+source("R/settings.R")
+source("R/fxns/plots.R")
+
+#------------------------------------------------------------------------------
+# Read shape file data from source folder
+# Note: saving shapeiles truncates the column names, reassignment needed
+#------------------------------------------------------------------------------
+
+fn <- glue("{SHAPEFILE_OUT}/processed_da_with_location.shp")
+da_shapefile <- tryCatch({
+  st_read(fn) %>%
+  rename("landarea" = "landare",
+         "loc" = "loctn_d") %>%
+  mutate(across(c(daid, loc), as.character)) # Explictly declare data types on join columns
+}, error = function(e) {
+   message(glue("Error reading or processing file {fn}: {e$message}"))
+   return(NULL) # Return NULL on error
+})
+
+if (is.null(da_shapefile)) {
+  stop("Failed to load or process DA shapefile. Stopping script.")
+}
+
+fn <- glue("{SHAPEFILE_OUT}/processed_db_with_location.shp")
+db_shapefile <- tryCatch({
+  st_read(fn) %>%
+  rename("dissemination_block_id" = "dssmn__",
+         "landarea" = "landare",
+         "loc" = "loctn_d") %>% 
+  mutate(across(c(dissemination_block_id, loc), as.character)) # Explictly declare data types on join columns
+}, error = function(e) {
+   message(glue("Error reading or processing file {fn}: {e$message}"))
+   return(NULL) # Return NULL on error
+})
+
+if (is.null(db_shapefile)) {
+  stop("Failed to load or process DB shapefile. Stopping script.")
+}
+
+#------------------------------------------------------------------------------
+# Read drive time data from source folder an
path: R/02-plots.R
user: login: lindsay-fredrick
body: ```suggestion
# Read drive time data from source folder and
```
created_at: 2025-04-11T21:29:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,165 @@
+# Copyright 2025 Province of British Columbia
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script loads aggregated csv data files containing spatial data for 
+# municipality of interest in BC (loc). It produces maps at the dissemination block level 
+# displaying quantitative information on basic descriptive statisics
+
+# ------------------------------------------------------------------------
+# Script: 02-plots.R
+
+# Description: Loads previously processed DA and DB shapefiles (containing location IDs)
+# and corresponding processed drive time summary statistics CSV files. Prepares the final 
+# spatial data frames ready for mapping.  A sample map visualization is generated and saved to disk.
+
+# Requirements:
+#   - Requires R packages: `tidyverse`, `glue`, `janitor`, `sf`.
+#   - Depends on `settings.R` for configuration constants (paths, `MAP_THEME`).
+#   - Requires shapefiles in `SHAPEFILE_DIR`.
+#   - Requires drive time summary CSV files in `SRC_DATA_FOLDER` containing variables (numeric type) to map
+#   - Requires the `build_map` function to be defined and available.
+#   - Requires read access to input files/paths.
+
+# Side Effects/Outputs:
+#   - Calls the `build_map` function, which prints a ggplot map object to
+#     the default graphics device.
+#   - Prints errors and stops execution if input files are missing.
+#   - (Currently does not write any new files to disk unless `build_map` is modified to do so).
+# ------------------------------------------------------------------------
+
+
+library(tidyverse)
+library(glue)
+library(janitor)
+library(sf)
+
+source("R/settings.R")
+source("R/fxns/plots.R")
+
+#------------------------------------------------------------------------------
+# Read shape file data from source folder
+# Note: saving shapeiles truncates the column names, reassignment needed
+#------------------------------------------------------------------------------
+
+fn <- glue("{SHAPEFILE_OUT}/processed_da_with_location.shp")
+da_shapefile <- tryCatch({
+  st_read(fn) %>%
+  rename("landarea" = "landare",
+         "loc" = "loctn_d") %>%
+  mutate(across(c(daid, loc), as.character)) # Explictly declare data types on join columns
+}, error = function(e) {
+   message(glue("Error reading or processing file {fn}: {e$message}"))
+   return(NULL) # Return NULL on error
+})
+
+if (is.null(da_shapefile)) {
+  stop("Failed to load or process DA shapefile. Stopping script.")
+}
+
+fn <- glue("{SHAPEFILE_OUT}/processed_db_with_location.shp")
+db_shapefile <- tryCatch({
+  st_read(fn) %>%
+  rename("dissemination_block_id" = "dssmn__",
+         "landarea" = "landare",
+         "loc" = "loctn_d") %>% 
+  mutate(across(c(dissemination_block_id, loc), as.character)) # Explictly declare data types on join columns
+}, error = function(e) {
+   message(glue("Error reading or processing file {fn}: {e$message}"))
+   return(NULL) # Return NULL on error
+})
+
+if (is.null(db_shapefile)) {
+  stop("Failed to load or process DB shapefile. Stopping script.")
+}
+
+#------------------------------------------------------------------------------
+# Read drive time data from source folder an
+# eep all shapes and potentially colour the missing ones differently
path: R/02-plots.R
user: login: lindsay-fredrick
body: ```suggestion
# keep all shapes and potentially colour the missing ones differently
```
created_at: 2025-04-11T21:29:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,156 @@
+# Copyright 2025 Province of British Columbia
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script loads aggregated csv data files containing spatial data for 
+# municipality of interest in BC (loc). 
+# It plots the locations on top of a map of B.C. 
+
+# ------------------------------------------------------------------------
+# Script: 02-plot-localities.R
+
+# Description: plot localities on a map of BC
+
+# Requirements:
+#   - Requires R packages: `tidyverse`, `glue`, `janitor`, `sf`. `rmapshaper`, `rnaturalearth`
+#   - Depends on `settings.R` for paths and constants.
+#   - Requires input DA/DB shapefiles and the crosswalk CSV file.
+#   - Requires read/write access to relevant data folders.
+
+# Side Effects/Outputs:
+#   - Makes a map of localities we used
+# ------------------------------------------------------------------------
+
+library(tidyverse)
+library(glue)
+library(janitor)
+library(sf)
+library(rmapshaper)    # simplify geometries
+library(rnaturalearth) # get BC outline
path: R/02-pilot-locality-regions.R
user: login: BonnieJRobert
body: from Steph H: "Noticed you are pulling bc admin boundary from rnaturalearth, just a reminder that BC manages these boundaries as well and data is available in the catalogue (and so from bcdata or bcmaps), including a regular and highres BC boundary." 
created_at: 2025-04-15T18:22:08Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,156 @@
+# Copyright 2025 Province of British Columbia
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script loads aggregated csv data files containing spatial data for 
+# municipality of interest in BC (loc). 
+# It plots the locations on top of a map of B.C. 
+
+# ------------------------------------------------------------------------
+# Script: 02-plot-localities.R
+
+# Description: plot localities on a map of BC
+
+# Requirements:
+#   - Requires R packages: `tidyverse`, `glue`, `janitor`, `sf`. `rmapshaper`, `rnaturalearth`
+#   - Depends on `settings.R` for paths and constants.
+#   - Requires input DA/DB shapefiles and the crosswalk CSV file.
+#   - Requires read/write access to relevant data folders.
+
+# Side Effects/Outputs:
+#   - Makes a map of localities we used
+# ------------------------------------------------------------------------
+
+library(tidyverse)
+library(glue)
+library(janitor)
+library(sf)
+library(rmapshaper)    # simplify geometries
+library(rnaturalearth) # get BC outline
+
+source("R/settings.R")
+
path: R/02-pilot-locality-regions.R
user: login: BonnieJRobert
body: ```suggestion
source("R/fxns/pre-processing.R" # for read_all_locs()
```
created_at: 2025-04-15T18:23:02Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,156 @@
+# Copyright 2025 Province of British Columbia
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script loads aggregated csv data files containing spatial data for 
+# municipality of interest in BC (loc). 
+# It plots the locations on top of a map of B.C. 
+
+# ------------------------------------------------------------------------
+# Script: 02-plot-localities.R
+
+# Description: plot localities on a map of BC
+
+# Requirements:
+#   - Requires R packages: `tidyverse`, `glue`, `janitor`, `sf`. `rmapshaper`, `rnaturalearth`
+#   - Depends on `settings.R` for paths and constants.
+#   - Requires input DA/DB shapefiles and the crosswalk CSV file.
+#   - Requires read/write access to relevant data folders.
+
+# Side Effects/Outputs:
+#   - Makes a map of localities we used
+# ------------------------------------------------------------------------
+
+library(tidyverse)
+library(glue)
+library(janitor)
+library(sf)
+library(rmapshaper)    # simplify geometries
+library(rnaturalearth) # get BC outline
+
+source("R/settings.R")
+
+# ----------------------------------------------------------------------------
+# Load and prepare mapping data
+# ----------------------------------------------------------------------------
+
+# map of BC
+bc_map <- ne_states(country = 'canada', returnclass = 'sf') |> 
path: R/02-pilot-locality-regions.R
user: login: BonnieJRobert
body: Switch to bcmaps as per Steph H request. 
created_at: 2025-04-15T18:23:37Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,156 @@
+# Copyright 2025 Province of British Columbia
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script loads aggregated csv data files containing spatial data for 
+# municipality of interest in BC (loc). 
+# It plots the locations on top of a map of B.C. 
+
+# ------------------------------------------------------------------------
+# Script: 02-plot-localities.R
+
+# Description: plot localities on a map of BC
+
+# Requirements:
+#   - Requires R packages: `tidyverse`, `glue`, `janitor`, `sf`. `rmapshaper`, `rnaturalearth`
+#   - Depends on `settings.R` for paths and constants.
+#   - Requires input DA/DB shapefiles and the crosswalk CSV file.
+#   - Requires read/write access to relevant data folders.
+
+# Side Effects/Outputs:
+#   - Makes a map of localities we used
+# ------------------------------------------------------------------------
+
+library(tidyverse)
+library(glue)
+library(janitor)
+library(sf)
+library(rmapshaper)    # simplify geometries
+library(rnaturalearth) # get BC outline
+
+source("R/settings.R")
+
+# ----------------------------------------------------------------------------
+# Load and prepare mapping data
+# ----------------------------------------------------------------------------
+
+# map of BC
+bc_map <- ne_states(country = 'canada', returnclass = 'sf') |> 
+  filter(name_en == 'British Columbia') |> 
+  st_transform(crs = 3005)
+
+# da level shapefiles for each locality
+fn <- glue("{SHAPEFILE_OUT}/processed_da_with_location.shp")
+da_shapefile <- tryCatch({
+  st_read(fn) %>%
+    rename("landarea" = "landare",
+           "loc" = "loctn_d") %>%
+    mutate(across(c(daid, loc), as.character)) # Explicitly declare data types on join columns
+}, error = function(e) {
+  message(glue("Error reading or processing file {fn}: {e$message}"))
+  return(NULL) # Return NULL on error
+})
+
+# simplify geographies to 1 per locality
+localities <- da_shapefile |> 
+  group_by(loc) |> 
+  summarize(geometry = st_union(geometry)) |> 
+  ungroup() |> 
+  mutate(
+    locality = paste0(LOC_LIST[loc],' (',loc,')')
path: R/02-pilot-locality-regions.R
user: login: BonnieJRobert
body: 👍🏼 
created_at: 2025-04-15T18:25:15Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,156 @@
+# Copyright 2025 Province of British Columbia
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script loads aggregated csv data files containing spatial data for 
+# municipality of interest in BC (loc). 
+# It plots the locations on top of a map of B.C. 
+
+# ------------------------------------------------------------------------
+# Script: 02-plot-localities.R
+
+# Description: plot localities on a map of BC
+
+# Requirements:
+#   - Requires R packages: `tidyverse`, `glue`, `janitor`, `sf`. `rmapshaper`, `rnaturalearth`
+#   - Depends on `settings.R` for paths and constants.
+#   - Requires input DA/DB shapefiles and the crosswalk CSV file.
+#   - Requires read/write access to relevant data folders.
+
+# Side Effects/Outputs:
+#   - Makes a map of localities we used
+# ------------------------------------------------------------------------
+
+library(tidyverse)
+library(glue)
+library(janitor)
+library(sf)
+library(rmapshaper)    # simplify geometries
+library(rnaturalearth) # get BC outline
+
+source("R/settings.R")
+
+# ----------------------------------------------------------------------------
+# Load and prepare mapping data
+# ----------------------------------------------------------------------------
+
+# map of BC
+bc_map <- ne_states(country = 'canada', returnclass = 'sf') |> 
+  filter(name_en == 'British Columbia') |> 
+  st_transform(crs = 3005)
+
+# da level shapefiles for each locality
+fn <- glue("{SHAPEFILE_OUT}/processed_da_with_location.shp")
+da_shapefile <- tryCatch({
+  st_read(fn) %>%
+    rename("landarea" = "landare",
+           "loc" = "loctn_d") %>%
+    mutate(across(c(daid, loc), as.character)) # Explicitly declare data types on join columns
+}, error = function(e) {
+  message(glue("Error reading or processing file {fn}: {e$message}"))
+  return(NULL) # Return NULL on error
+})
+
+# simplify geographies to 1 per locality
+localities <- da_shapefile |> 
+  group_by(loc) |> 
+  summarize(geometry = st_union(geometry)) |> 
+  ungroup() |> 
+  mutate(
+    locality = paste0(LOC_LIST[loc],' (',loc,')')
+  )
+
+# get centroids for labels
+locality_centroids <- st_centroid(localities) 
+locality_centroids_nudged <- locality_centroids |> 
+  mutate(
+    geometry = case_when(
+      loc!= '909' ~ geometry + c(0, 70000), # move label north to not overlap 
+      TRUE ~ geometry + c(-200000, 0) # move west for langford
+      )
+    )|>   
+  st_set_crs(st_crs(locality_centroids))
+
+# locations of all nearby SBC locations 
+fls <- list.files(SRC_DATA_FOLDER, full.names = TRUE, pattern = INPUT_ADDR_DA_PATTERN, recursive = TRUE)
+
+# map_dfr automatically handles NULLs from read_all_locs
+data <- map_dfr(.x = fls, .f = read_all_locs)
+
+if (nrow(data) == 0) {
+  stop("No data successfully loaded. Check input files.")
+}
+
+sbc_locs <- data |> 
+  distinct(loc, nearest_facility, coord_x, coord_y) |> 
+  st_as_sf(
+    coords = c('coord_x', 'coord_y'),
+    crs = 3005
+  )
path: R/02-pilot-locality-regions.R
user: login: BonnieJRobert
body: ```suggestion
sbc_locs <- read_csv(glue("{SRC_DATA_FOLDER}/service_bc_locs.csv")) |>
  st_as_sf(
    coords = c('coord_x', 'coord_y'),
    crs = 3005
  )
```
created_at: 2025-04-15T18:30:56Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,156 @@
+# Copyright 2025 Province of British Columbia
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script loads aggregated csv data files containing spatial data for 
+# municipality of interest in BC (loc). 
+# It plots the locations on top of a map of B.C. 
+
+# ------------------------------------------------------------------------
+# Script: 02-plot-localities.R
+
+# Description: plot localities on a map of BC
+
+# Requirements:
+#   - Requires R packages: `tidyverse`, `glue`, `janitor`, `sf`. `rmapshaper`, `rnaturalearth`
+#   - Depends on `settings.R` for paths and constants.
+#   - Requires input DA/DB shapefiles and the crosswalk CSV file.
+#   - Requires read/write access to relevant data folders.
+
+# Side Effects/Outputs:
+#   - Makes a map of localities we used
+# ------------------------------------------------------------------------
+
+library(tidyverse)
+library(glue)
+library(janitor)
+library(sf)
+library(rmapshaper)    # simplify geometries
+library(rnaturalearth) # get BC outline
+
+source("R/settings.R")
+
+# ----------------------------------------------------------------------------
+# Load and prepare mapping data
+# ----------------------------------------------------------------------------
+
+# map of BC
+bc_map <- ne_states(country = 'canada', returnclass = 'sf') |> 
+  filter(name_en == 'British Columbia') |> 
+  st_transform(crs = 3005)
+
+# da level shapefiles for each locality
+fn <- glue("{SHAPEFILE_OUT}/processed_da_with_location.shp")
+da_shapefile <- tryCatch({
+  st_read(fn) %>%
+    rename("landarea" = "landare",
+           "loc" = "loctn_d") %>%
+    mutate(across(c(daid, loc), as.character)) # Explicitly declare data types on join columns
+}, error = function(e) {
+  message(glue("Error reading or processing file {fn}: {e$message}"))
+  return(NULL) # Return NULL on error
+})
+
+# simplify geographies to 1 per locality
+localities <- da_shapefile |> 
+  group_by(loc) |> 
+  summarize(geometry = st_union(geometry)) |> 
+  ungroup() |> 
+  mutate(
+    locality = paste0(LOC_LIST[loc],' (',loc,')')
+  )
+
+# get centroids for labels
+locality_centroids <- st_centroid(localities) 
+locality_centroids_nudged <- locality_centroids |> 
+  mutate(
+    geometry = case_when(
+      loc!= '909' ~ geometry + c(0, 70000), # move label north to not overlap 
+      TRUE ~ geometry + c(-200000, 0) # move west for langford
+      )
+    )|>   
path: R/02-pilot-locality-regions.R
user: login: BonnieJRobert
body: ```suggestion
mutate(
    geometry = case_when(
      loc == '227' ~ geometry + c(-150000, 40000),
      loc == '909' ~ geometry + c(130000, 10000), # move east for langford
      TRUE ~ geometry + c(0, 70000)  # move label north to not overlap 
      )
    )|>  
```
created_at: 2025-04-15T18:33:42Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,156 @@
+# Copyright 2025 Province of British Columbia
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script loads aggregated csv data files containing spatial data for 
+# municipality of interest in BC (loc). 
+# It plots the locations on top of a map of B.C. 
+
+# ------------------------------------------------------------------------
+# Script: 02-plot-localities.R
+
+# Description: plot localities on a map of BC
+
+# Requirements:
+#   - Requires R packages: `tidyverse`, `glue`, `janitor`, `sf`. `rmapshaper`, `rnaturalearth`
+#   - Depends on `settings.R` for paths and constants.
+#   - Requires input DA/DB shapefiles and the crosswalk CSV file.
+#   - Requires read/write access to relevant data folders.
+
+# Side Effects/Outputs:
+#   - Makes a map of localities we used
+# ------------------------------------------------------------------------
+
+library(tidyverse)
+library(glue)
+library(janitor)
+library(sf)
+library(rmapshaper)    # simplify geometries
+library(rnaturalearth) # get BC outline
+
+source("R/settings.R")
+
+# ----------------------------------------------------------------------------
+# Load and prepare mapping data
+# ----------------------------------------------------------------------------
+
+# map of BC
+bc_map <- ne_states(country = 'canada', returnclass = 'sf') |> 
+  filter(name_en == 'British Columbia') |> 
+  st_transform(crs = 3005)
+
+# da level shapefiles for each locality
+fn <- glue("{SHAPEFILE_OUT}/processed_da_with_location.shp")
+da_shapefile <- tryCatch({
+  st_read(fn) %>%
+    rename("landarea" = "landare",
+           "loc" = "loctn_d") %>%
+    mutate(across(c(daid, loc), as.character)) # Explicitly declare data types on join columns
+}, error = function(e) {
+  message(glue("Error reading or processing file {fn}: {e$message}"))
+  return(NULL) # Return NULL on error
+})
+
+# simplify geographies to 1 per locality
+localities <- da_shapefile |> 
+  group_by(loc) |> 
+  summarize(geometry = st_union(geometry)) |> 
+  ungroup() |> 
+  mutate(
+    locality = paste0(LOC_LIST[loc],' (',loc,')')
+  )
+
+# get centroids for labels
+locality_centroids <- st_centroid(localities) 
+locality_centroids_nudged <- locality_centroids |> 
+  mutate(
+    geometry = case_when(
+      loc!= '909' ~ geometry + c(0, 70000), # move label north to not overlap 
+      TRUE ~ geometry + c(-200000, 0) # move west for langford
+      )
+    )|>   
path: R/02-pilot-locality-regions.R
user: login: BonnieJRobert
body: Playing with the labels a bit so they stand out more. 
created_at: 2025-04-15T18:36:37Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,156 @@
+# Copyright 2025 Province of British Columbia
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script loads aggregated csv data files containing spatial data for 
+# municipality of interest in BC (loc). 
+# It plots the locations on top of a map of B.C. 
+
+# ------------------------------------------------------------------------
+# Script: 02-plot-localities.R
+
+# Description: plot localities on a map of BC
+
+# Requirements:
+#   - Requires R packages: `tidyverse`, `glue`, `janitor`, `sf`. `rmapshaper`, `rnaturalearth`
+#   - Depends on `settings.R` for paths and constants.
+#   - Requires input DA/DB shapefiles and the crosswalk CSV file.
+#   - Requires read/write access to relevant data folders.
+
+# Side Effects/Outputs:
+#   - Makes a map of localities we used
+# ------------------------------------------------------------------------
+
+library(tidyverse)
+library(glue)
+library(janitor)
+library(sf)
+library(rmapshaper)    # simplify geometries
+library(rnaturalearth) # get BC outline
path: R/02-pilot-locality-regions.R
user: login: lindsay-fredrick
body: good call. Updating
created_at: 2025-04-15T18:51:57Z
author_association: CONTRIBUTOR

PR comments:
user: login: BonnieJRobert
created_at: 2025-04-14T17:15:59Z
body: > * Any way to color the missing DAs/DBs a different color? There's what looks like a couple of obviously missing regions that I'm not totally sure I understand why it's missing.

All of the DA/DB map data contain at least one data point per shape (the inner join at lines 101 and 109 of 00b-make-map-data ensure  this). The only truly missing data is for metrics like standard deviation which rely on more than one data point to calculate.   The DA/DB's with very low counts tend to be around the exterior of the municipality, and often have a higher census population/dwelling/household count. It might be these low count areas that you are seeing as they show up black.  

I've added a setting to fill those areas missing a value by "red".  It will stands out better for QA.


author_association: MEMBER
user: login: BonnieJRobert
created_at: 2025-04-15T18:42:13Z
body: > I think this looks amazing overall! I just have a couple of requests:
> 
> * Could we have the plots show the nearest SBC locations that are within each region?
> * Could we have the min/max color scale on each plot line up so that we can easily compare across regions?
> * Could the legend be moved to below the plot? tends to take up less whitespace that way.
> * Any way to color the missing DAs/DBs a different color? There's what looks like a couple of obviously missing regions that I'm not totally sure I understand why it's missing.
> * If you wanted, could you check out the code I just pushed that does a quick and dirty overview of which localities we are looking at overall?

@lindsay-fredrick your code runs great (need to source `R/fxns/pre_preprocessing.R`) and produces a map in the right location.  I reviewed the code and left a few suggestions.   

I've addressed the main points (above) - do you want to review the changes?
author_association: MEMBER
user: login: lindsay-fredrick
created_at: 2025-04-15T19:08:54Z
body: No further comments from me, looks like a great start to me. 
author_association: CONTRIBUTOR