Add deployment pipeline and clean up repo
- Add Dockerfile, docker-compose.yml, .dockerignore, .env (port 3842) - Add Caddyfile.snippet for analytics gateway import pattern - Add .gitea/workflows/deploy.yaml for act_runner SSH deploy - Untrack sensitive/data files (SCPA xlsx, owners.rds) - Add renv lockfile and infrastructure files - Reorganize data-raw scripts and add SarasotaCounty boundary data - Move www assets to www/images/, add docs PDFs
This commit is contained in:
19
.dockerignore
Normal file
19
.dockerignore
Normal file
@@ -0,0 +1,19 @@
|
||||
renv/library/
|
||||
renv/staging/
|
||||
renv/local/
|
||||
renv/python/
|
||||
|
||||
.Rproj.user/
|
||||
.Rhistory
|
||||
.RData
|
||||
.DS_Store
|
||||
|
||||
.Rprofile
|
||||
renv/activate.R
|
||||
renv/settings.json
|
||||
|
||||
data-raw/
|
||||
|
||||
CLAUDE.md
|
||||
TODO.md
|
||||
README.md
|
||||
33
.gitea/workflows/deploy.yaml
Normal file
33
.gitea/workflows/deploy.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Deploy stAndrews
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: debian:bookworm-slim
|
||||
|
||||
steps:
|
||||
- name: Install SSH client
|
||||
run: apt-get update -y && apt-get install -y --no-install-recommends openssh-client
|
||||
|
||||
- name: Deploy via SSH
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||
SERVER_IP: ${{ secrets.DEPLOY_SERVER_IP }}
|
||||
SERVER_USER: ${{ secrets.DEPLOY_SERVER_USER }}
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
ssh-keyscan -H $SERVER_IP >> ~/.ssh/known_hosts
|
||||
ssh ${SERVER_USER}@${SERVER_IP} << 'EOF'
|
||||
cd /data/projects/r/stAndrews
|
||||
git pull
|
||||
docker compose up -d --build
|
||||
docker exec analytics-gateway caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile
|
||||
EOF
|
||||
31
.gitignore
vendored
31
.gitignore
vendored
@@ -1,11 +1,32 @@
|
||||
# R
|
||||
.Rproj.user/
|
||||
.Rhistory
|
||||
.RData
|
||||
rsconnect/
|
||||
./data-raw/
|
||||
.DS_Store
|
||||
.Rproj.user
|
||||
.Rdata
|
||||
.httr-oauth
|
||||
rsconnect/
|
||||
|
||||
# renv (library is rebuilt from renv.lock — do not commit)
|
||||
renv/library/
|
||||
renv/staging/
|
||||
renv/local/
|
||||
renv/python/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
.quarto
|
||||
|
||||
# Quarto
|
||||
.quarto/
|
||||
|
||||
# Raw data (large files, sensitive property data, not reproducible via code alone)
|
||||
data-raw/property/
|
||||
data-raw/addresses/
|
||||
data-raw/geotagged_street_addresses.rds
|
||||
data-raw/epa/
|
||||
data-raw/uscb/
|
||||
|
||||
# Derived data (rebuilt from data-raw via data-raw/main.R)
|
||||
data/owners.rds
|
||||
data/venice.rds
|
||||
data/venice_facts.rds
|
||||
data/beaches.rds
|
||||
|
||||
191
CLAUDE.md
Normal file
191
CLAUDE.md
Normal file
@@ -0,0 +1,191 @@
|
||||
# stAndrews — Claude Context
|
||||
|
||||
## Required reading
|
||||
|
||||
Before working on this project, read:
|
||||
- `/home/rkw/.claude/CLAUDE.md` — infrastructure context (network, Docker, deployment)
|
||||
- `/home/rkw/CLAUDE.md` — home directory layout
|
||||
|
||||
## What this project is
|
||||
|
||||
A Shiny mobile app (using `shinyMobile` / Framework7) that maps property owners
|
||||
and community information for St. Andrews Park, Venice, Florida. 388 properties
|
||||
across 14 subdivisions in Plantation Golf and Country Club.
|
||||
|
||||
**Entry point:** `app.R` (single-file Shiny app)
|
||||
|
||||
## Stack
|
||||
|
||||
- **UI:** `shinyMobile` (Framework7-based, iOS theme, dark mode)
|
||||
- **Maps:** `leaflet` + `leafpop`
|
||||
- **Tables:** `DT`
|
||||
- **Spatial:** `sf`
|
||||
- **Data wrangling:** `dplyr`
|
||||
|
||||
## Data
|
||||
|
||||
| File | Description | Source |
|
||||
|------|-------------|--------|
|
||||
| `data/owners.rds` | SF object — geocoded property owners (post-QGIS edit) | Built by `data-raw/main.R` |
|
||||
| `data/plats/plats.shp` | Subdivision boundary polygons | Built by `data-raw/create_sbdv_plats.R` |
|
||||
| `data/venice.rds` | Venice city boundary polygon | Built by `data-raw/create_venice_bndry.R` |
|
||||
| `data/venice_facts.rds` | Venice demographic facts table | Built by `data-raw/main.R` or similar |
|
||||
| `data/beaches.rds` | EPA beach monitoring locations | Built from `data-raw/epa/` |
|
||||
|
||||
**Raw inputs** (gitignored — large or sensitive):
|
||||
- `data-raw/property/SCPA Public.xlsx` — Sarasota County Property Appraiser export
|
||||
- `data-raw/addresses/owners_raw.gpkg` — geocoded points before QGIS editing
|
||||
- `data-raw/addresses/owners_moved.gpkg` — points after manual QGIS adjustment (source of truth for `owners.rds`)
|
||||
- `data-raw/geotagged_street_addresses.rds` — cached geocoding results (Google API)
|
||||
|
||||
**Rebuild pipeline:**
|
||||
1. `data-raw/main.R` — geocodes addresses, exports to `owners_raw.gpkg`
|
||||
2. Manual QGIS step — adjust duplicate-location points, save as `owners_moved.gpkg`
|
||||
3. `data-raw/main.R` (continued) — reads QGIS output, writes `data/owners.rds`
|
||||
4. `data-raw/create_sbdv_plats.R` — builds `data/plats/plats.shp`
|
||||
5. `data-raw/create_venice_bndry.R` — builds `data/venice.rds`
|
||||
|
||||
## App tabs
|
||||
|
||||
| Tab | Content |
|
||||
|-----|---------|
|
||||
| About | Community description + photo |
|
||||
| Venice | City boundary map + facts table |
|
||||
| Beach | EPA beach locations map + helpful links |
|
||||
| Owners | Searchable owner map + table (filter by name, address, subdivision) |
|
||||
| Resources | Links to city services + PDF documents |
|
||||
|
||||
PDF documents are served from `www/docs/`.
|
||||
|
||||
## Running locally
|
||||
|
||||
Run inside the `rstudio` Docker container (see global CLAUDE.md):
|
||||
|
||||
```r
|
||||
shiny::runApp("/home/rstudio/projects/r/stAndrews")
|
||||
```
|
||||
|
||||
Or from RStudio with the project open: click **Run App**.
|
||||
|
||||
## Deployment
|
||||
|
||||
Deployed as a Shiny app. Path routing via the analytics gateway:
|
||||
- Add a route to `~/docker/gateway/Caddyfile` on the analytics VM if not already present.
|
||||
|
||||
## Old app
|
||||
|
||||
Previously deployed to shinyapps.io at `https://rob-wiederstein.shinyapps.io/stAndrews/`
|
||||
(account: `rob-wiederstein`, appId: 14173710). No longer maintained — app should be
|
||||
archived/deleted from the shinyapps.io dashboard. The local `rsconnect/` directory
|
||||
has been removed.
|
||||
|
||||
### Problems
|
||||
|
||||
**1. Geocoding was unreliable and required manual correction**
|
||||
|
||||
St. Andrews has three property types: standalone homes, villas (2- and 4-unit structures
|
||||
where each side is a separate unit), and 8-unit buildings. The 8-unit buildings have unit
|
||||
numbers (1–8) in the raw property records. Those unit numbers were stripped before
|
||||
geocoding, so all 8 units in a building returned a single shared coordinate. Google's
|
||||
geocoding was also only approximate — coordinates landed near addresses but not on top
|
||||
of the actual structures. The result was that every point had to be manually reviewed
|
||||
and dragged to its correct location in QGIS. This was labor-intensive and not repeatable.
|
||||
|
||||
**2. No update path**
|
||||
|
||||
The data pipeline was designed as a one-shot process with no way to refresh. Property
|
||||
ownership changes hands regularly — the data should be updated roughly weekly. The
|
||||
manual QGIS correction step makes this especially painful: any new or changed record
|
||||
would require re-running geocoding and repeating the manual editing. There is no
|
||||
mechanism to diff new records against existing ones or to carry forward previously
|
||||
corrected coordinates.
|
||||
|
||||
### Geographic data flow
|
||||
|
||||
There are three independent geographic data flows, all ending in WGS84 (EPSG:4326):
|
||||
|
||||
**1. Owner points** (`data-raw/main.R`)
|
||||
|
||||
Raw property records from the Sarasota County Property Appraiser (Excel) are filtered
|
||||
to the 13 St. Andrews subdivisions. Street addresses are parsed and assembled into
|
||||
geocodable strings, then sent to the Google geocoding API via `tidygeocoder`. Results
|
||||
are cached as `geotagged_street_addresses.rds` so the API isn't called twice. The
|
||||
geocoded points are written to `owners_raw.gpkg`. Because many units share a building
|
||||
address, they all land on the same coordinate — so the file is loaded into QGIS and
|
||||
points are manually dragged to their actual locations. The adjusted file
|
||||
(`owners_moved.gpkg`) is read back into R and saved as `data/owners.rds`.
|
||||
|
||||
**2. Subdivision boundary polygons** (`data-raw/create_sbdv_plats.R`)
|
||||
|
||||
A Sarasota County GIS shapefile (`data-raw/PlatBoundary/`) containing all county plat
|
||||
boundaries is read in, reprojected to WGS84, and filtered to the same 13 subdivision
|
||||
IDs. Subdivision names are cleaned up and the result is written to `data/plats/plats.shp`.
|
||||
|
||||
**3. Venice city boundary** (`data-raw/create_venice_bndry.R`)
|
||||
|
||||
A Sarasota County boundary shapefile (`data-raw/SarasotaCountyBoundary/`) is filtered
|
||||
to the City of Venice (`municipali == "CV"`), keeping only the main polygon (acreage >
|
||||
2500 to drop small outliers). It is simplified with `rmapshaper`, interior holes are
|
||||
removed, reprojected to WGS84, and saved as `data/venice.rds`.
|
||||
|
||||
## New app — decisions
|
||||
|
||||
**Same folder/repo.** The UI in `app.R` is already good — tabs, maps, and layout don't
|
||||
need to change. What needs replacing is entirely in `data-raw/`: the pipeline that
|
||||
produces `data/owners.rds`. The app just consumes that file. Rebuild the pipeline,
|
||||
keep the app.
|
||||
|
||||
**Feasibility hinges on the join.** The geometry side is straightforward — filter
|
||||
building footprints to the subdivision boundary, compute centroids, done. The hard part
|
||||
is linking those geometries to SCPA property records. Two approaches worth investigating:
|
||||
|
||||
- **Address join** — if the footprint layer carries situs addresses in the same format
|
||||
as the SCPA records, a direct join works. If formatting differs, fuzzy matching may
|
||||
be needed.
|
||||
- **Account number join** — the SCPA assigns an account number to each property. It's
|
||||
worth checking whether account numbers follow the *property* (stable, ideal join key)
|
||||
or the *owner* (changes on sale, less useful). If account numbers are property-stable
|
||||
and also appear in the footprint layer, this would be the cleanest and most reliable
|
||||
join key — especially for multi-unit buildings where address disambiguation is messy.
|
||||
|
||||
Also needs confirming: whether the footprint polygons exist at the **unit level** or
|
||||
only at the **building level**. If only building-level, the stacking problem from the
|
||||
old pipeline reappears.
|
||||
|
||||
Data inspection will resolve all of this.
|
||||
|
||||
## New app — proposal
|
||||
|
||||
Instead of geocoding addresses, use the Sarasota County GIS building footprint layer,
|
||||
which contains polygon outlines of every structure. Key advantages:
|
||||
|
||||
- **No geocoding API needed.** Building outlines are already precisely positioned over
|
||||
actual structures. Deriving centroids from the polygons gives accurate, stable
|
||||
coordinates for every unit — far better than approximate Google geocoding results.
|
||||
- **Handles multi-unit buildings cleanly.** Each unit in an 8-unit building has its own
|
||||
footprint polygon, so each gets its own distinct centroid. No stripping of unit numbers,
|
||||
no stacking of points, no manual QGIS correction.
|
||||
- **No new construction to worry about.** St. Andrews is a built-out community with no
|
||||
active development, so the building footprint layer is effectively static.
|
||||
|
||||
**Proposed pipeline:**
|
||||
|
||||
1. Download the Sarasota County building footprint GIS layer.
|
||||
2. Spatially filter it to the St. Andrews subdivision boundary (already have
|
||||
`data/plats/plats.shp`).
|
||||
3. Compute centroids for each building footprint polygon.
|
||||
4. Join centroids to property records from the SCPA using address as the key.
|
||||
5. Save the joined dataset as the owner points layer.
|
||||
|
||||
**Update path:**
|
||||
|
||||
Because the geometry is fixed (building centroids don't change), only the ownership
|
||||
attributes need refreshing. The SCPA property records can be re-downloaded weekly and
|
||||
re-joined to the stable centroid table. This makes weekly updates a simple scripted
|
||||
operation with no manual steps.
|
||||
|
||||
## Notes
|
||||
|
||||
- Geocoding used Google API via `tidygeocoder`; results cached in `data-raw/geotagged_street_addresses.rds` to avoid re-calling the API.
|
||||
- Point deduplication (multiple units at same address) was done manually in QGIS — not scripted. `owners_moved.gpkg` is the authoritative geocoded dataset.
|
||||
- `data-raw/` is gitignored except for the shapefiles in `data-raw/PlatBoundary/` and `data-raw/SarasotaCountyBoundary/` which are committed.
|
||||
5
Caddyfile.snippet
Normal file
5
Caddyfile.snippet
Normal file
@@ -0,0 +1,5 @@
|
||||
# stAndrews — port 3842
|
||||
redir /stAndrews /stAndrews/ 308
|
||||
handle_path /stAndrews/* {
|
||||
reverse_proxy 127.0.0.1:3842
|
||||
}
|
||||
47
Dockerfile
Normal file
47
Dockerfile
Normal file
@@ -0,0 +1,47 @@
|
||||
FROM rocker/shiny:4.5.2
|
||||
|
||||
# 1. System dependencies (spatial stack required for sf)
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
cmake \
|
||||
curl \
|
||||
libabsl-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libfontconfig1-dev \
|
||||
libfreetype6-dev \
|
||||
libfribidi-dev \
|
||||
libgdal-dev \
|
||||
libgeos-dev \
|
||||
libharfbuzz-dev \
|
||||
libicu-dev \
|
||||
libjpeg-dev \
|
||||
libpng-dev \
|
||||
libproj-dev \
|
||||
libssl-dev \
|
||||
libtiff5-dev \
|
||||
libudunits2-dev \
|
||||
libxml2-dev \
|
||||
pkg-config \
|
||||
proj-bin \
|
||||
proj-data \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /srv/shiny-server
|
||||
|
||||
# 2. Copy lockfile first for layer caching
|
||||
COPY renv.lock .
|
||||
|
||||
# 3. Install packages into site-library (accessible to all users including shiny)
|
||||
ENV RENV_PATHS_LIBRARY=/usr/local/lib/R/site-library
|
||||
|
||||
RUN R -q -e "install.packages('renv', repos='https://cloud.r-project.org')" \
|
||||
&& R -q -e "renv::restore(library='/usr/local/lib/R/site-library', prompt=FALSE)"
|
||||
|
||||
# 4. Copy application code
|
||||
COPY . .
|
||||
|
||||
# 5. Permissions
|
||||
RUN chown -R shiny:shiny /srv/shiny-server
|
||||
|
||||
EXPOSE 3838
|
||||
CMD ["/usr/bin/shiny-server"]
|
||||
22
R/geocode.R
22
R/geocode.R
@@ -1,22 +0,0 @@
|
||||
library(dplyr, warn.conflicts = FALSE)
|
||||
library(tidygeocoder)
|
||||
|
||||
# create a dataframe with addresses
|
||||
some_addresses <- tibble::tribble(
|
||||
~name, ~addr,
|
||||
"White House", "1600 Pennsylvania Ave NW, Washington, DC",
|
||||
"Transamerica Pyramid", "600 Montgomery St, San Francisco, CA 94111",
|
||||
"Willis Tower", "233 S Wacker Dr, Chicago, IL 60606",
|
||||
"Rob", "863 Tartan Dr, Venice, FL 34293"
|
||||
)
|
||||
|
||||
# geocode the addresses
|
||||
lat_longs <- some_addresses %>%
|
||||
geocode(
|
||||
addr,
|
||||
method = 'google',
|
||||
lat = latitude ,
|
||||
long = longitude
|
||||
)
|
||||
#
|
||||
#
|
||||
46
README.md
46
README.md
@@ -1,13 +1,45 @@
|
||||
# St. Andrews
|
||||
# St. Andrews Park
|
||||
|
||||
The project maps addresses of St. Andrews in Venice, Florida, to a map. The map is created with `leaflet`. St. Andrews is 388 separate properties in 14 subdivisions.
|
||||
A Shiny mobile app for residents of St. Andrews Park, Venice, Florida.
|
||||
|
||||
## Data
|
||||
## What it does
|
||||
|
||||
The data comes from the Sarasota County Property Appraiser for the property records and the Sarasota County GIS for the subdivision boundaries.
|
||||
- Maps 388 property owners across 14 subdivisions
|
||||
- Search owners by name, address, or subdivision
|
||||
- Shows Venice city boundary and demographic facts
|
||||
- Lists nearby EPA-monitored beach conditions
|
||||
- Links to city services, county resources, and community documents
|
||||
|
||||
## Challenges
|
||||
## Data sources
|
||||
|
||||
The property records are not geocoded, so the addresses need to be assigned a latitude and longitude. The records have two columns regarding address with the first portion being a street address like "123 Main St." and the second portion being a building and unit number. The second portion was inconsistent and only the first portion could be used.
|
||||
- **Owners** — Sarasota County Property Appraiser (SCPA); updated weekly via `data-raw/update_owners.R`
|
||||
- **Subdivision boundaries** — Sarasota County GIS plat layer
|
||||
- **Venice boundary** — Sarasota County GIS municipal boundary layer
|
||||
- **Beaches** — EPA beach monitoring data
|
||||
|
||||
This resulted in a building with 8 residents having 8 points fixed to the same location. This was fixed by manually moving each of the points within QGIS and exporting the data back to the project. Not great for reproducibility, but resulted in a much better and more accurate map.
|
||||
## Weekly update
|
||||
|
||||
```r
|
||||
source("./data-raw/update_owners.R")
|
||||
```
|
||||
|
||||
Downloads fresh SCPA data, joins to stable geometry, overwrites `data/owners.rds`.
|
||||
|
||||
## Geometry
|
||||
|
||||
Owner point locations were geocoded via Google API, manually corrected in QGIS
|
||||
(multi-unit buildings share a street address and required individual point placement),
|
||||
and saved as `data-raw/addresses/owners_moved.gpkg`. This file is the stable geometry
|
||||
source. Account numbers follow the property, so only ownership attributes need
|
||||
refreshing weekly.
|
||||
|
||||
## Note: building footprints not used
|
||||
|
||||
Sarasota County publishes a building footprint GIS layer that could derive accurate
|
||||
centroids without geocoding. However the footprint layer carries only a street number
|
||||
(`buildingid`) with no street name — making a direct attribute join to SCPA records
|
||||
ambiguous across multiple streets. A spatial join via street centerlines would resolve
|
||||
this but adds complexity. Since accurate geometry already exists in `owners_moved.gpkg`
|
||||
and St. Andrews is a built-out community with no new construction, the footprint
|
||||
approach was deferred. It remains the right path if the app is ever extended to
|
||||
additional subdivisions.
|
||||
|
||||
29
TODO.md
Normal file
29
TODO.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# TODO
|
||||
|
||||
## App
|
||||
|
||||
- [ ] Display `last_sale_date` attribute from `owners.rds` somewhere in the UI
|
||||
so users know how current the ownership data is
|
||||
|
||||
## Data
|
||||
|
||||
- [ ] Verify SCPA Public.xlsx column structure is stable across downloads
|
||||
- [ ] Delete app from shinyapps.io (account: rob-wiederstein, appId: 14173710)
|
||||
- [ ] Add cron job to run `update_owners.R` weekly inside the rstudio container:
|
||||
`docker exec rstudio Rscript /home/rstudio/projects/r/stAndrews/data-raw/update_owners.R`
|
||||
Note: app must reload `owners.rds` after refresh — either restart container or
|
||||
make app reactive to file changes
|
||||
|
||||
## Deployment
|
||||
|
||||
Everything must be scripted from within the project folder. No manual steps
|
||||
on the server outside of what the workflow handles.
|
||||
|
||||
- [ ] Create Dockerfile (follow veniceProp pattern — `rocker/shiny:4.5.2`)
|
||||
- [ ] Create docker-compose.yml (bind to `127.0.0.1:<port>:3838`)
|
||||
- [ ] Create `.gitea/workflows/deploy.yaml` that:
|
||||
- Builds the Docker image
|
||||
- Runs `docker compose up -d`
|
||||
- SSHes into analytics VM and idempotently adds route to gateway Caddyfile
|
||||
- Restarts the gateway container
|
||||
- [ ] Push to Gitea — act_runner handles all future deploys automatically
|
||||
344
app.R
344
app.R
@@ -10,15 +10,19 @@ library(DT)
|
||||
# load data ----
|
||||
owners <- readRDS("./data/owners.rds")
|
||||
sbdvn <- sf::st_read("./data/plats/plats.shp")
|
||||
venice_bndry <- readRDS("./data/venice.rds")
|
||||
venice_facts <- readRDS("./data/venice_facts.rds")
|
||||
beaches <- readRDS("./data/beaches.rds")
|
||||
|
||||
# define ui
|
||||
# define ui ----
|
||||
ui <- f7Page(
|
||||
title = "St. Andrews",
|
||||
## header ----
|
||||
tags$head(
|
||||
tags$link(rel = "manifest", href = "manifest.json"),
|
||||
tags$link(rel = "apple-touch-icon", sizes = "180x180", href = "images/apple-touch-icon.png"),
|
||||
tags$meta(name = "apple-mobile-web-app-capable", content = "yes"),
|
||||
tags$meta(name = "apple-mobile-web-app-status-bar-style", content = "default"),
|
||||
tags$link(rel = "manifest", href = "manifest.json"),
|
||||
tags$link(rel = "apple-touch-icon", sizes = "180x180", href = "images/apple-touch-icon.png"),
|
||||
tags$meta(name = "apple-mobile-web-app-capable", content = "yes"),
|
||||
tags$meta(name = "apple-mobile-web-app-status-bar-style", content = "default"),
|
||||
tags$style(HTML("
|
||||
.dataTables_wrapper {
|
||||
color: white;
|
||||
@@ -44,76 +48,256 @@ ui <- f7Page(
|
||||
}
|
||||
"))
|
||||
),
|
||||
## options ----
|
||||
options = list(
|
||||
theme = "ios",
|
||||
dark = TRUE,
|
||||
pullToRefresh = TRUE
|
||||
),
|
||||
f7SingleLayout(
|
||||
navbar = f7Navbar(title = "St. Andrews Park"),
|
||||
toolbar = f7Toolbar(
|
||||
position = "bottom",
|
||||
f7Link(label = "SC-PA", href = "https://www.sc-pa.com/home/"),
|
||||
f7Link(label = "GIS", href = "https://data-sarco.opendata.arcgis.com/")
|
||||
),
|
||||
f7Card(
|
||||
title = "About",
|
||||
divider = "TRUE",
|
||||
img(src = "st_andrews.jpg", width = "100%"),
|
||||
"St. Andrews Park is located in Venice, Florida. The condominiums are a mix of single-family homes, villas (2 and 4 units) and multi-unit buildings (8 units). There are 388 separate properties within 14 subdivisions. St. Andrews Park is one of many communities in the Plantation Golf and Country Club. It is should not be confused with the adjacent community St. Andrews East.",
|
||||
footer = tagList(
|
||||
f7Link("More Info", href = "http://www.cpmi.us/standrews-plantation/outside_home.asp")
|
||||
)
|
||||
),
|
||||
f7Card(
|
||||
title = "Owners:",
|
||||
divider = TRUE,
|
||||
raised = TRUE,
|
||||
f7List(
|
||||
inset = TRUE,
|
||||
dividers = TRUE,
|
||||
strong = TRUE,
|
||||
outline = FALSE,
|
||||
f7Text(
|
||||
inputId = "name",
|
||||
label = "Last Name:",
|
||||
placeholder = "\"Patel\""
|
||||
),
|
||||
f7Text(
|
||||
inputId = "location",
|
||||
label = "Address:",
|
||||
placeholder = "\"123 Chalmers\""
|
||||
),
|
||||
f7Select(
|
||||
inputId = "sub_name",
|
||||
label = "Select Subdivision:",
|
||||
choices = c("All", sort(sbdvn$sub_name))
|
||||
),
|
||||
tags$br(),
|
||||
f7Button(
|
||||
inputId = "filterButton",
|
||||
label = "Find Owners",
|
||||
icon = "",
|
||||
color = "blue"
|
||||
## layout ----
|
||||
f7TabLayout(
|
||||
### panels ----
|
||||
panels = tagList(
|
||||
f7Panel(
|
||||
id = "panel-left",
|
||||
side = "left",
|
||||
effect = "push",
|
||||
title = "Menu",
|
||||
f7PanelMenu(
|
||||
id = "menu",
|
||||
f7PanelItem(
|
||||
tabName = "About",
|
||||
title = "About",
|
||||
icon = f7Icon("info_circle"),
|
||||
active = TRUE
|
||||
),
|
||||
f7PanelItem(
|
||||
tabName = "Venice",
|
||||
title = "Venice",
|
||||
icon = f7Icon("map_pin")
|
||||
),
|
||||
f7PanelItem(
|
||||
tabName = "Beach",
|
||||
title = "Beach",
|
||||
icon = f7Icon("sun_max_fill")
|
||||
),
|
||||
f7PanelItem(
|
||||
tabName = "Owners",
|
||||
title = "Owners",
|
||||
icon = f7Icon("person_2_fill")
|
||||
),
|
||||
f7PanelItem(
|
||||
tabName = "Resources",
|
||||
title = "Resources",
|
||||
icon = f7Icon("hammer_fill")
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
f7Card(
|
||||
title = "Map:",
|
||||
divider = TRUE,
|
||||
leafletOutput("map")
|
||||
### navbar ----
|
||||
navbar = f7Navbar(
|
||||
title = "St. Andrews Park",
|
||||
hairline = TRUE,
|
||||
leftPanel = TRUE
|
||||
),
|
||||
### begin tabs ----
|
||||
f7Tabs(
|
||||
id = "tabs",
|
||||
animated = TRUE,
|
||||
#### about ----
|
||||
f7Tab(
|
||||
title = "About",
|
||||
tabName = "About",
|
||||
icon = f7Icon("info_circle"),
|
||||
active = TRUE,
|
||||
f7Card(
|
||||
title = "About",
|
||||
divider = "TRUE",
|
||||
tags$img(src = "images/st_andrews.jpg", width = "100%"),
|
||||
"St. Andrews Park is located in Venice, Florida. The condominiums are a mix of single-family homes, villas (2 and 4 units) and multi-unit buildings (8 units). There are 388 separate properties within 14 subdivisions. St. Andrews Park is one of many communities in the Plantation Golf and Country Club. It is should not be confused with the adjacent community St. Andrews East.",
|
||||
footer = tagList(
|
||||
f7Link("More Info", href = "http://www.cpmi.us/standrews-plantation/outside_home.asp")
|
||||
)
|
||||
)
|
||||
),
|
||||
f7Card(
|
||||
title = "Table:",
|
||||
divider = TRUE,
|
||||
DTOutput("table")
|
||||
#### venice ----
|
||||
f7Tab(
|
||||
title = "Venice",
|
||||
tabName = "Venice",
|
||||
icon = f7Icon("map_pin"),
|
||||
active = FALSE,
|
||||
f7Card(
|
||||
title = "Venice",
|
||||
divider = "TRUE",
|
||||
leafletOutput("venice_map")
|
||||
),
|
||||
f7Card(
|
||||
title = "Facts",
|
||||
divider = "TRUE",
|
||||
DTOutput("venice_facts")
|
||||
)
|
||||
),
|
||||
#### beaches ----
|
||||
f7Tab(
|
||||
title = "Beach",
|
||||
tabName = "Beach",
|
||||
icon = f7Icon("sun_max_fill"),
|
||||
active = TRUE,
|
||||
f7Card(
|
||||
title = "Beaches",
|
||||
divider = "TRUE",
|
||||
leafletOutput("beach_map"),
|
||||
footer = p("Source: Environmental Protection Agency")
|
||||
),
|
||||
f7Block(
|
||||
h3("Helpful Links:"),
|
||||
f7List(
|
||||
inset = TRUE,
|
||||
dividers = TRUE,
|
||||
strong = TRUE,
|
||||
outline = FALSE,
|
||||
f7ListItem(
|
||||
title = "EPA Beaches",
|
||||
href = "https://www.epa.gov/beaches",
|
||||
external = TRUE
|
||||
),
|
||||
f7ListItem(
|
||||
title = "Red Tide Forecast",
|
||||
href = "https://habforecast.gcoos.org/",
|
||||
external = TRUE
|
||||
),
|
||||
f7ListItem(
|
||||
title = "Healthy Beaches Program",
|
||||
href = "https://fdoh.maps.arcgis.com/apps/instant/nearby/index.html?appid=7106a20597de4bff98cc5ebc7f932047&findSource=0&find=1600%2520Harbor%2520Dr%2520S%252C%2520Venice%252C%2520Florida%252C%252034285&sliderDistance=2",
|
||||
external = TRUE
|
||||
),
|
||||
f7ListItem(
|
||||
title = "MOTE Beach Conditions",
|
||||
href = "https://visitbeaches.org/beach/6/report/53033",
|
||||
external = TRUE
|
||||
),
|
||||
f7ListItem(
|
||||
title = "National Hurricane Center",
|
||||
href = "https://www.nhc.noaa.gov/",
|
||||
external = TRUE
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
#### owners ----
|
||||
f7Tab(
|
||||
title = "Owners",
|
||||
tabName = "Owners",
|
||||
icon = f7Icon("person_2_fill"),
|
||||
f7Card(
|
||||
title = "Owners:",
|
||||
divider = TRUE,
|
||||
raised = TRUE,
|
||||
f7List(
|
||||
inset = TRUE,
|
||||
dividers = TRUE,
|
||||
strong = TRUE,
|
||||
outline = FALSE,
|
||||
f7Text(
|
||||
inputId = "name",
|
||||
label = "Last Name:",
|
||||
placeholder = "\"Patel\""
|
||||
),
|
||||
f7Text(
|
||||
inputId = "location",
|
||||
label = "Address:",
|
||||
placeholder = "\"123 Chalmers\""
|
||||
),
|
||||
f7Select(
|
||||
inputId = "sub_name",
|
||||
label = "Select Subdivision:",
|
||||
choices = c("All", sort(sbdvn$sub_name))
|
||||
),
|
||||
tags$br(),
|
||||
f7Button(
|
||||
inputId = "filterButton",
|
||||
label = "Find Owners",
|
||||
icon = "",
|
||||
color = "blue"
|
||||
)
|
||||
)
|
||||
),
|
||||
f7Card(
|
||||
title = "Map:",
|
||||
divider = TRUE,
|
||||
leafletOutput("map")
|
||||
),
|
||||
f7Card(
|
||||
title = "Table:",
|
||||
divider = TRUE,
|
||||
DTOutput("table")
|
||||
)
|
||||
),
|
||||
#### services ----
|
||||
f7Tab(
|
||||
title = "Resources",
|
||||
tabName = "Resources",
|
||||
icon = f7Icon("hammer_fill"),
|
||||
f7BlockTitle(title = "Services:", size = "medium"),
|
||||
f7Block(
|
||||
f7List(
|
||||
mode = "links",
|
||||
inset = TRUE,
|
||||
outline = TRUE,
|
||||
dividers = TRUE,
|
||||
strong = TRUE,
|
||||
f7Link(label = "City of Venice", href = "https://www.venicegov.com/"),
|
||||
f7Link(label = "Florida Power & Light", href = "https://www.fpl.com/"),
|
||||
f7Link(label = "Sarasota County", href = "https://www.scgov.net/"),
|
||||
f7Link(label = "Property Appraiser", href = "https://www.sc-pa.com/"),
|
||||
f7Link(label = "Open GIS Portal", href = "https://data-sarco.opendata.arcgis.com/"),
|
||||
f7Link(label = "Waste & Recycling", href = "https://www.venicegov.com/government/public-works/waste-and-recycling"),
|
||||
f7Link(label = "Condo Regulation", href = "https://condos.myfloridalicense.com/"),
|
||||
f7Link(label = "Property Records Search", href = "https://www.sarasotaclerk.com/records/official-records/search-land-records")
|
||||
)
|
||||
),
|
||||
f7BlockTitle(title = "Documents:", size = "medium"),
|
||||
f7Block(
|
||||
f7List(
|
||||
mode = "links",
|
||||
inset = TRUE,
|
||||
outline = TRUE,
|
||||
dividers = TRUE,
|
||||
strong = TRUE,
|
||||
f7Link(label = "St. Andrews Covenants", href = "docs/2000_01_01_st_andrews_covenants.pdf"),
|
||||
f7Link(label = "St. Andrews (unrecorded)", href = "docs/2004_06_23_sap_map.pdf"),
|
||||
f7Link(label = "Patios 2", href = "docs/1997_08_12_patios_2_plat.pdf"),
|
||||
f7Link(label = "Patios 3", href = "docs/1998_11_17_patios_3_plat.pdf"),
|
||||
f7Link(label = "Villas 2", href = "docs/1998_09_14_villas_2_plat.pdf")
|
||||
)
|
||||
)
|
||||
)
|
||||
### end tabs----
|
||||
),
|
||||
### begin scripts ----
|
||||
tags$script(
|
||||
HTML(
|
||||
"
|
||||
$(document).on('click', '#pdfLink', function(event) {
|
||||
event.preventDefault(); // Prevent the default link behavior
|
||||
window.open($(this).attr('href'), '_blank'); // Open in a new tab
|
||||
});
|
||||
"
|
||||
)
|
||||
)
|
||||
### end scripts ----
|
||||
)
|
||||
)
|
||||
# end ui ----
|
||||
|
||||
|
||||
# define server ----
|
||||
server <- function(input, output) {
|
||||
# update tabs depending on side panel
|
||||
observeEvent(input$menu, {
|
||||
updateF7Tabs(id = "tabs",
|
||||
selected = input$menu)
|
||||
})
|
||||
|
||||
filteredSbdvn <- reactive({
|
||||
if (is.null(input$sub_name) || input$sub_name == "All") {
|
||||
@@ -225,7 +409,47 @@ server <- function(input, output) {
|
||||
)
|
||||
)
|
||||
})
|
||||
# venice map ----
|
||||
output$venice_map <- renderLeaflet({
|
||||
leaflet() %>%
|
||||
addProviderTiles("CartoDB.Voyager") %>%
|
||||
setView(lng = -82.4313, lat = 27.1059, zoom = 12) %>%
|
||||
addPolygons(
|
||||
data = venice_bndry,
|
||||
color = "red",
|
||||
weight = 2,
|
||||
opacity = 0.5,
|
||||
fillOpacity = 0.2
|
||||
)
|
||||
})
|
||||
# venice facts ----
|
||||
output$venice_facts <- renderDT({
|
||||
datatable(
|
||||
venice_facts,
|
||||
rownames = FALSE,
|
||||
options = list(
|
||||
pageLength = 10,
|
||||
scrollX = TRUE,
|
||||
searching = FALSE,
|
||||
lengthMenu = c(5, 10, 25, 50),
|
||||
dom = 'tpi'
|
||||
)
|
||||
)
|
||||
})
|
||||
# beach map ----
|
||||
output$beach_map <- renderLeaflet({
|
||||
leaflet() %>%
|
||||
addProviderTiles("CartoDB.Voyager") %>%
|
||||
setView(lng = -82.4603, lat = 27.0999, zoom = 12) %>%
|
||||
addMarkers(
|
||||
data = beaches,
|
||||
lat = ~lat,
|
||||
lng = ~lng,
|
||||
popup = ~beach_name
|
||||
)
|
||||
})
|
||||
}
|
||||
# end server ----
|
||||
|
||||
# Run the app
|
||||
shinyApp(ui, server)
|
||||
shinyApp(ui, server)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
UTF-8
|
||||
BIN
data-raw/SarasotaCountyBoundary/SarasotaCountyBoundary.dbf
Normal file
BIN
data-raw/SarasotaCountyBoundary/SarasotaCountyBoundary.dbf
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
||||
PROJCS["NAD_1983_HARN_StatePlane_Florida_West_FIPS_0902_Feet",GEOGCS["GCS_North_American_1983_HARN",DATUM["D_North_American_1983_HARN",SPHEROID["GRS_1980",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",656166.667],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",-82.0],PARAMETER["Scale_Factor",0.999941177],PARAMETER["Latitude_Of_Origin",24.3333333333333],UNIT["US survey foot",0.304800609601219]]
|
||||
BIN
data-raw/SarasotaCountyBoundary/SarasotaCountyBoundary.shp
Normal file
BIN
data-raw/SarasotaCountyBoundary/SarasotaCountyBoundary.shp
Normal file
Binary file not shown.
BIN
data-raw/SarasotaCountyBoundary/SarasotaCountyBoundary.shx
Normal file
BIN
data-raw/SarasotaCountyBoundary/SarasotaCountyBoundary.shx
Normal file
Binary file not shown.
71
data-raw/SarasotaCountyBoundary/SarasotaCountyBoundary.xml
Normal file
71
data-raw/SarasotaCountyBoundary/SarasotaCountyBoundary.xml
Normal file
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><MD_Metadata xmlns:gts="http://www.isotc211.org/2005/gts" xmlns:gco="http://www.isotc211.org/2005/gco" xmlns:xalan="http://xml.apache.org/xalan" xmlns:srv="http://www.isotc211.org/2005/srv" xmlns:gmx="http://www.isotc211.org/2005/gmx" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gml="http://www.opengis.net/gml" xmlns="http://www.isotc211.org/2005/gmd">
|
||||
<characterSet>
|
||||
<MD_CharacterSetCode codeList="http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml#MD_CharacterSetCode" codeListValue="utf8" codeSpace="ISOTC211/19115">utf8</MD_CharacterSetCode>
|
||||
</characterSet>
|
||||
<hierarchyLevel>
|
||||
<MD_ScopeCode codeList="http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml#MD_ScopeCode" codeListValue="dataset" codeSpace="ISOTC211/19115">dataset</MD_ScopeCode>
|
||||
</hierarchyLevel>
|
||||
<hierarchyLevelName>
|
||||
<gco:CharacterString>dataset</gco:CharacterString>
|
||||
</hierarchyLevelName>
|
||||
<contact gco:nilReason="missing"/>
|
||||
<dateStamp gco:nilReason="missing"/>
|
||||
<metadataStandardName>
|
||||
<gco:CharacterString>ISO 19139 Geographic Information - Metadata - Implementation Specification</gco:CharacterString>
|
||||
</metadataStandardName>
|
||||
<metadataStandardVersion>
|
||||
<gco:CharacterString>2007</gco:CharacterString>
|
||||
</metadataStandardVersion>
|
||||
<identificationInfo>
|
||||
<MD_DataIdentification>
|
||||
<citation>
|
||||
<CI_Citation>
|
||||
<title>
|
||||
<gco:CharacterString>SarasotaCountyBoundary</gco:CharacterString>
|
||||
</title>
|
||||
<date gco:nilReason="missing"/>
|
||||
</CI_Citation>
|
||||
</citation>
|
||||
<abstract>
|
||||
<gco:CharacterString>The Sarasota County Boundary layer contains both Sarasota County and municipal boundary lines and areas.</gco:CharacterString>
|
||||
</abstract>
|
||||
<purpose>
|
||||
<gco:CharacterString>The Sarasota County Boundary layer contains both Sarasota County and municipal boundary lines and areas.</gco:CharacterString>
|
||||
</purpose>
|
||||
<descriptiveKeywords>
|
||||
<MD_Keywords>
|
||||
<keyword>
|
||||
<gco:CharacterString>Cadastral</gco:CharacterString>
|
||||
</keyword>
|
||||
<keyword>
|
||||
<gco:CharacterString>Planning</gco:CharacterString>
|
||||
</keyword>
|
||||
</MD_Keywords>
|
||||
</descriptiveKeywords>
|
||||
<language gco:nilReason="missing"/>
|
||||
<characterSet>
|
||||
<MD_CharacterSetCode codeList="http://www.isotc211.org/2005/resources/Codelist/gmxCodelists.xml#MD_CharacterSetCode" codeListValue="utf8" codeSpace="ISOTC211/19115">utf8</MD_CharacterSetCode>
|
||||
</characterSet>
|
||||
<extent>
|
||||
<EX_Extent>
|
||||
<geographicElement>
|
||||
<EX_GeographicBoundingBox>
|
||||
<westBoundLongitude>
|
||||
<gco:Decimal>-82.6425729822934</gco:Decimal>
|
||||
</westBoundLongitude>
|
||||
<eastBoundLongitude>
|
||||
<gco:Decimal>-82.0565543313639</gco:Decimal>
|
||||
</eastBoundLongitude>
|
||||
<southBoundLatitude>
|
||||
<gco:Decimal>26.9440430628277</gco:Decimal>
|
||||
</southBoundLatitude>
|
||||
<northBoundLatitude>
|
||||
<gco:Decimal>27.391160931026</gco:Decimal>
|
||||
</northBoundLatitude>
|
||||
</EX_GeographicBoundingBox>
|
||||
</geographicElement>
|
||||
</EX_Extent>
|
||||
</extent>
|
||||
</MD_DataIdentification>
|
||||
</identificationInfo>
|
||||
</MD_Metadata>
|
||||
24
data-raw/create_geometry_lookup.R
Normal file
24
data-raw/create_geometry_lookup.R
Normal file
@@ -0,0 +1,24 @@
|
||||
# create_geometry_lookup.R
|
||||
# One-time script. Extracts account_number -> geometry from the manually
|
||||
# corrected QGIS file and saves as a stable lookup table.
|
||||
# Re-run only if geometry ever needs to be corrected.
|
||||
# Output: data-raw/addresses/geometry_lookup.rds
|
||||
|
||||
library(sf)
|
||||
library(dplyr)
|
||||
|
||||
raw <- st_read(
|
||||
"./data-raw/addresses/owners_moved.gpkg",
|
||||
layer = "owners_raw",
|
||||
quiet = TRUE
|
||||
)
|
||||
cat("Columns:", paste(names(raw), collapse = ", "), "\n")
|
||||
|
||||
lookup <- raw |>
|
||||
select(account_number)
|
||||
|
||||
cat("Lookup rows:", nrow(lookup), "\n")
|
||||
cat("Any duplicate account numbers:", anyDuplicated(lookup$account_number) > 0, "\n")
|
||||
|
||||
saveRDS(lookup, "./data-raw/addresses/geometry_lookup.rds")
|
||||
cat("Saved to data-raw/addresses/geometry_lookup.rds\n")
|
||||
15
data-raw/create_venice_bndry.R
Normal file
15
data-raw/create_venice_bndry.R
Normal file
@@ -0,0 +1,15 @@
|
||||
library(rmapshaper)
|
||||
library(dplyr)
|
||||
library(sf)
|
||||
library(leaflet)
|
||||
library(sfheaders)
|
||||
|
||||
df <- st_read("./data-raw/SarasotaCountyBoundary/") %>%
|
||||
filter(municipali == "CV") %>%
|
||||
filter(acreage > 2500) %>%
|
||||
ms_simplify(keep = .025) %>%
|
||||
mutate(geometry = sfheaders::sf_remove_holes(geometry)) %>%
|
||||
sf::st_transform(crs = 4326) %>%
|
||||
mutate(label = "City of Venice", .before = boundarycl) %>%
|
||||
saveRDS(., file = "./data/venice.rds")
|
||||
|
||||
@@ -4,7 +4,7 @@ library("readxl")
|
||||
library("janitor")
|
||||
library("dplyr")
|
||||
library("tidyr")
|
||||
library("tidygeotag")
|
||||
library("tidygeocoder")
|
||||
library("sf")
|
||||
|
||||
# choose columns ---
|
||||
Binary file not shown.
BIN
data-raw/sarco/building_footprints/building_footprints.gpkg
Normal file
BIN
data-raw/sarco/building_footprints/building_footprints.gpkg
Normal file
Binary file not shown.
56
data-raw/sarco/create_building_footprints.R
Normal file
56
data-raw/sarco/create_building_footprints.R
Normal file
@@ -0,0 +1,56 @@
|
||||
# create_building_footprints.R
|
||||
# Pull Sarasota County building footprints clipped to St. Andrews boundary.
|
||||
# Source: https://ags3.scgov.net/server/rest/services/Hosted/BuildingFootprint/FeatureServer/0
|
||||
# Output: data-raw/sarco/building_footprints/building_footprints.gpkg
|
||||
# No esri2sf needed — queries ArcGIS REST API directly via GeoJSON URL.
|
||||
|
||||
library(sf)
|
||||
library(dplyr)
|
||||
|
||||
# load st. andrews subdivision boundary ----
|
||||
plats <- st_read("./data/plats/plats.shp", quiet = TRUE) |> st_transform(4326)
|
||||
boundary <- st_union(plats)
|
||||
bb <- st_bbox(boundary)
|
||||
|
||||
# build arcgis rest query url ----
|
||||
base_url <- "https://ags3.scgov.net/server/rest/services/Hosted/BuildingFootprint/FeatureServer/0/query"
|
||||
geometry <- paste(bb["xmin"], bb["ymin"], bb["xmax"], bb["ymax"], sep = ",")
|
||||
params <- paste0(
|
||||
"?where=1=1",
|
||||
"&geometry=", geometry,
|
||||
"&geometryType=esriGeometryEnvelope",
|
||||
"&inSR=4326",
|
||||
"&spatialRel=esriSpatialRelIntersects",
|
||||
"&outFields=*",
|
||||
"&returnGeometry=true",
|
||||
"&f=geojson"
|
||||
)
|
||||
url <- paste0(base_url, params)
|
||||
cat("URL:\n", url, "\n\n")
|
||||
|
||||
# fetch ----
|
||||
cat("Querying feature service...\n")
|
||||
footprints_raw <- st_read(url, quiet = TRUE)
|
||||
cat("Raw rows:", nrow(footprints_raw), "\n")
|
||||
cat("Raw fields:", paste(names(footprints_raw), collapse = ", "), "\n\n")
|
||||
|
||||
# clip to exact subdivision boundary ----
|
||||
footprints <- footprints_raw |>
|
||||
st_transform(4326) |>
|
||||
st_filter(boundary)
|
||||
|
||||
# inspect ----
|
||||
cat("Rows:", nrow(footprints), "\n")
|
||||
cat("\nFields:\n")
|
||||
print(names(footprints))
|
||||
cat("\nSample rows:\n")
|
||||
print(head(st_drop_geometry(footprints)))
|
||||
|
||||
# save ----
|
||||
st_write(
|
||||
footprints,
|
||||
"./data-raw/sarco/building_footprints/building_footprints.gpkg",
|
||||
delete_dsn = TRUE
|
||||
)
|
||||
|
||||
cat("\nSaved to data-raw/sarco/building_footprints/building_footprints.gpkg\n")
|
||||
82
data-raw/update_owners.R
Normal file
82
data-raw/update_owners.R
Normal file
@@ -0,0 +1,82 @@
|
||||
# update_owners.R
|
||||
# Weekly update script. Reads fresh SCPA property data, joins to stable
|
||||
# geometry lookup by account_number, saves data/owners.rds.
|
||||
# Only the SCPA xlsx needs to be replaced to refresh ownership data.
|
||||
# Input: data-raw/property/SCPA Public.xlsx (replace weekly)
|
||||
# data-raw/addresses/geometry_lookup.rds (static)
|
||||
# Output: data/owners.rds
|
||||
|
||||
library(readxl)
|
||||
library(janitor)
|
||||
library(dplyr)
|
||||
library(stringr)
|
||||
library(sf)
|
||||
|
||||
subdivisions <- c(
|
||||
"8120", "8113", "8171", "8195", "8221",
|
||||
"8163", "8240", "8159", "8149", "8110", "8254", "8215", "8143"
|
||||
)
|
||||
|
||||
# load geometry lookup (static) ----
|
||||
geometry_lookup <- readRDS("./data-raw/addresses/geometry_lookup.rds")
|
||||
|
||||
# download fresh scpa data ----
|
||||
download.file(
|
||||
url = "https://www.sc-pa.com/downloads/SCPA%20Public.xlsx",
|
||||
destfile = "./data-raw/property/SCPA Public.xlsx",
|
||||
mode = "wb"
|
||||
)
|
||||
|
||||
# load and clean scpa data ----
|
||||
owners_raw <-
|
||||
readxl::read_xlsx(
|
||||
path = "./data-raw/property/SCPA Public.xlsx",
|
||||
n_max = Inf,
|
||||
.name_repair = ~janitor::make_clean_names(.x)
|
||||
) |>
|
||||
filter(subdivision %in% subdivisions) |>
|
||||
rename(
|
||||
situs_address = situs_address_property_address,
|
||||
homestead = homestead_exemption_yes_or_no
|
||||
) |>
|
||||
filter(!is.na(situs_address)) |>
|
||||
filter(!grepl("^0", situs_address)) |>
|
||||
mutate(
|
||||
# extract clean street address (before multiple spaces / unit suffix)
|
||||
label = str_trim(str_extract(situs_address, "^\\d+\\s+\\S+\\s+\\S+")),
|
||||
location = paste0(label, ", Venice FL")
|
||||
) |>
|
||||
select(account_number, owner_1, owner_2, subdivision, homestead, label, location)
|
||||
|
||||
# join to geometry ----
|
||||
owners <- owners_raw |>
|
||||
inner_join(geometry_lookup, by = "account_number") |>
|
||||
st_as_sf(sf_column_name = "geom")
|
||||
|
||||
# report any unmatched records ----
|
||||
n_unmatched <- nrow(owners_raw) - nrow(owners)
|
||||
if (n_unmatched > 0) {
|
||||
cat("WARNING:", n_unmatched, "records had no matching geometry and were dropped.\n")
|
||||
missing <- anti_join(owners_raw, st_drop_geometry(geometry_lookup), by = "account_number")
|
||||
print(missing)
|
||||
}
|
||||
|
||||
# report most recent sale date in st. andrews ----
|
||||
latest_sale <-
|
||||
readxl::read_xlsx(
|
||||
path = "./data-raw/property/SCPA Public.xlsx",
|
||||
n_max = Inf,
|
||||
.name_repair = ~janitor::make_clean_names(.x)
|
||||
) |>
|
||||
filter(subdivision %in% subdivisions) |>
|
||||
select(account_number, owner_1, contains("sale")) |>
|
||||
filter(!is.na(account_number)) |>
|
||||
mutate(last_sale_date = as.Date(last_sale_date, format = "%m/%d/%Y")) |>
|
||||
arrange(desc(last_sale_date)) |>
|
||||
head(1)
|
||||
|
||||
cat("Owners written:", nrow(owners), "\n")
|
||||
attr(owners, "last_sale_date") <- latest_sale$last_sale_date
|
||||
saveRDS(owners, "./data/owners.rds")
|
||||
cat("Saved to data/owners.rds\n")
|
||||
cat("Most recent sale date:", format(latest_sale$last_sale_date, "%B %d, %Y"), "\n")
|
||||
BIN
data/owners.rds
BIN
data/owners.rds
Binary file not shown.
18
docker-compose.yml
Normal file
18
docker-compose.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
services:
|
||||
shiny:
|
||||
build: .
|
||||
container_name: standrews_shiny
|
||||
restart: always
|
||||
ports:
|
||||
- "127.0.0.1:${PORT}:3838"
|
||||
environment:
|
||||
- SHINY_LOG_STDERR=1
|
||||
volumes:
|
||||
- .:/srv/shiny-server
|
||||
# Prevents local renv library from shadowing the container's library
|
||||
- /srv/shiny-server/renv/library
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3838/"]
|
||||
interval: 1m
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
7
renv/.gitignore
vendored
Normal file
7
renv/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
library/
|
||||
local/
|
||||
cellar/
|
||||
lock/
|
||||
python/
|
||||
sandbox/
|
||||
staging/
|
||||
1419
renv/activate.R
Normal file
1419
renv/activate.R
Normal file
File diff suppressed because it is too large
Load Diff
20
renv/settings.json
Normal file
20
renv/settings.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"bioconductor.version": null,
|
||||
"external.libraries": [],
|
||||
"ignored.packages": [],
|
||||
"package.dependency.fields": [
|
||||
"Imports",
|
||||
"Depends",
|
||||
"LinkingTo"
|
||||
],
|
||||
"ppm.enabled": null,
|
||||
"ppm.ignored.urls": [],
|
||||
"r.version": null,
|
||||
"snapshot.dev": false,
|
||||
"snapshot.type": "implicit",
|
||||
"use.cache": true,
|
||||
"vcs.ignore.cellar": true,
|
||||
"vcs.ignore.library": true,
|
||||
"vcs.ignore.local": true,
|
||||
"vcs.manage.ignores": true
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
Version: 1.0
|
||||
ProjectId: c59d4069-2369-4870-a54f-b22df07f3e3e
|
||||
ProjectId: ab728b89-b094-4c83-8cc4-50bda69048f8
|
||||
|
||||
RestoreWorkspace: Default
|
||||
SaveWorkspace: Default
|
||||
|
||||
BIN
www/docs/1997_08_12_patios_2_plat.pdf
Normal file
BIN
www/docs/1997_08_12_patios_2_plat.pdf
Normal file
Binary file not shown.
BIN
www/docs/1998_09_14_villas_2_plat.pdf
Normal file
BIN
www/docs/1998_09_14_villas_2_plat.pdf
Normal file
Binary file not shown.
BIN
www/docs/1998_11_17_patios_3_plat.pdf
Normal file
BIN
www/docs/1998_11_17_patios_3_plat.pdf
Normal file
Binary file not shown.
BIN
www/docs/2000_01_01_st_andrews_covenants.pdf
Normal file
BIN
www/docs/2000_01_01_st_andrews_covenants.pdf
Normal file
Binary file not shown.
BIN
www/docs/2004_06_23_sap_map.pdf
Normal file
BIN
www/docs/2004_06_23_sap_map.pdf
Normal file
Binary file not shown.
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 184 KiB |
Reference in New Issue
Block a user