Spaces:
Sleeping
Sleeping
| # ============================================================================ | |
| # Prep: Nearest RSF Program rasters (30 m, same grid as greenspace) | |
| # ============================================================================ | |
| # Produces: | |
| # - nearest_rsfprogram_dist.tif — distance (m) to nearest RSF program polygon | |
| # - nearest_rsfprogram_id.tif — polygon_id for lookup in RSF gpkg (prj_name) | |
| # | |
| # Source polygons: data/source/RSF_Program_Projects_polygons.gpkg | |
| # Template grid: same slope.tif + SF county mask as making-greenspace-raster.R | |
| # ============================================================================ | |
| library(tidyverse) | |
| library(terra) | |
| library(tidyterra) | |
| library(sf) | |
| library(duckdb) | |
| library(glue) | |
| library(tictoc) | |
| library(tidycensus) | |
| # ============================================================================ | |
| # DuckDB + spatial | |
| # ============================================================================ | |
| tcon <- dbConnect(duckdb::duckdb()) | |
| tcon |> dbExecute(" | |
| INSTALL spatial; | |
| LOAD spatial; | |
| SET memory_limit = '200GB'; | |
| SET preserve_insertion_order = false; | |
| SET threads TO 8; | |
| ") | |
| rsf_gpkg <- "data/source/RSF_Program_Projects_polygons.gpkg" | |
| # ============================================================================ | |
| # SF county boundary + template grid (match greenspace raster script) | |
| # ============================================================================ | |
| sf <- get_acs( | |
| geography = "county", | |
| state = "CA", | |
| variables = "B01003_001", | |
| year = 2016, | |
| geometry = TRUE | |
| ) |> | |
| filter(NAME == "San Francisco County, California") |> | |
| st_transform(3310) |> | |
| st_cast("POLYGON") |> | |
| mutate(area = st_area(geometry)) |> | |
| slice_max(area, n = 1) |> | |
| select(GEOID, NAME, geometry) | |
| empty.sr <- rast("data/source/slope.tif") | |
| template.sr <- empty.sr %>% | |
| mutate(cell_id = 1:ncell(.)) %>% | |
| filter(!is.na(prcnt_slope30)) %>% | |
| crop(sf %>% st_transform(4326), mask = TRUE) %>% | |
| crop(ext(c(-123, -122, 37.65, 37.85))) %>% | |
| trim() | |
| template_pts <- template.sr %>% | |
| as.points(na.rm = TRUE) %>% | |
| st_as_sf() %>% | |
| st_transform(4326) | |
| dir.create("data/intermediate", showWarnings = FALSE) | |
| dir.create("data/output", showWarnings = FALSE) | |
| template_pts |> | |
| write_sf("data/intermediate/template_pts.gpkg") | |
| # Escape single quotes in path for SQL (Windows paths unlikely here) | |
| rsf_gpkg_sql <- gsub("'", "''", normalizePath(rsf_gpkg, winslash = "/", mustWork = TRUE)) | |
| tcon |> dbExecute(glue(" | |
| CREATE OR REPLACE TABLE rsf_geo AS | |
| SELECT | |
| polygon_id, | |
| prj_name, | |
| ST_TRANSFORM(geom, 'EPSG:4326', 'EPSG:3310', always_xy := true) AS geom3310, | |
| ST_SimplifyPreserveTopology( | |
| ST_TRANSFORM(geom, 'EPSG:4326', 'EPSG:3310', always_xy := true), | |
| 10 | |
| ) AS simple_geom3310 | |
| FROM ST_Read('{rsf_gpkg_sql}'); | |
| CREATE INDEX idx_rsf_simple_geom ON rsf_geo USING RTREE (simple_geom3310); | |
| ")) | |
| tcon |> dbExecute(" | |
| CREATE OR REPLACE TABLE template_grid_geo AS | |
| SELECT | |
| cell_id, | |
| geom, | |
| ST_TRANSFORM(geom, 'EPSG:4326', 'EPSG:3310', always_xy := true) AS geom3310 | |
| FROM ST_Read('data/intermediate/template_pts.gpkg'); | |
| CREATE INDEX idx_template_geom ON template_grid_geo USING RTREE (geom3310); | |
| CREATE INDEX idx_template_cell_id ON template_grid_geo (cell_id); | |
| ") | |
| nn_query <- glue(" | |
| CREATE OR REPLACE TABLE rsf_distance_complete AS | |
| WITH distances AS ( | |
| SELECT | |
| template.cell_id, | |
| template.geom AS template_geom, | |
| ST_AsText(template.geom) AS geom_wkt, | |
| rsf.polygon_id, | |
| ST_Distance(template.geom3310, rsf.simple_geom3310) AS distance_meters | |
| FROM template_grid_geo AS template, rsf_geo AS rsf | |
| ) | |
| SELECT | |
| cell_id, | |
| template_geom, | |
| geom_wkt, | |
| MIN(distance_meters) AS distance_to_rsf_meters, | |
| arg_min(polygon_id, distance_meters) AS nearest_rsf_polygon_id | |
| FROM distances | |
| GROUP BY cell_id, template_geom, geom_wkt; | |
| ") | |
| cat("Calculating nearest RSF program distances...\n") | |
| tic() | |
| tcon |> dbExecute(nn_query) | |
| toc() | |
| rsf_dist_complete <- tcon |> | |
| tbl("rsf_distance_complete") |> | |
| select(cell_id, distance_to_rsf_meters, nearest_rsf_polygon_id) |> | |
| collect() | |
| empty_raster <- empty.sr | |
| values(empty_raster) <- NA | |
| aligned_results <- tibble(cells = 1:ncell(empty.sr)) |> | |
| left_join(rsf_dist_complete |> rename(cells = cell_id), by = "cells") | |
| rsf_nearest_dist <- empty_raster | |
| values(rsf_nearest_dist) <- aligned_results$distance_to_rsf_meters | |
| names(rsf_nearest_dist) <- "rsfprogram_nearest_dist" | |
| cropped_rsf_dist <- rsf_nearest_dist %>% | |
| crop(sf %>% st_transform(4326), mask = TRUE) %>% | |
| crop(ext(c(-123, -122, 37.65, 37.85))) %>% | |
| trim() | |
| rsf_nearest_id <- empty_raster | |
| values(rsf_nearest_id) <- as.integer(round(as.numeric(aligned_results$nearest_rsf_polygon_id))) | |
| names(rsf_nearest_id) <- "rsfprogram_nearest_id" | |
| cropped_rsf_id <- rsf_nearest_id %>% | |
| crop(sf %>% st_transform(4326), mask = TRUE) %>% | |
| crop(ext(c(-123, -122, 37.65, 37.85))) %>% | |
| trim() | |
| plot(cropped_rsf_dist) | |
| plot(cropped_rsf_id) | |
| writeRaster(cropped_rsf_dist, "data/output/nearest_rsfprogram_dist.tif", overwrite = TRUE) | |
| writeRaster(cropped_rsf_id, "data/output/nearest_rsfprogram_id.tif", overwrite = TRUE, datatype = "INT4S") | |
| dbDisconnect(tcon, shutdown = TRUE) | |