Repository navigation
Bump deps for R v4.6.1 and refactor geoarrow usage - #1100
wrridgeway wants to merge 67 commits into
Conversation
| st_transform(3435) %>% | ||
| st_buffer(dist = units::set_units(2, "m")) |
There was a problem hiding this comment.
This is the only real change to the actual processing here. We project before buffering so sf can use euclidean geometry. This greatly speeds up processing.
There was a problem hiding this comment.
Just making sure we built my dev location tables and views using my dev spatial tables.
| for (year in years) { | ||
| remote_file <- file.path( | ||
| output_bucket, "major_road", | ||
| output_bucket, "major_road_test", |
There was a problem hiding this comment.
We typically don't write data to our raw bucket as parquet files, but where we do I used a _test suffix not to overwrite production data.
| }) %>% | ||
| mutate(year = as.character(x)) | ||
| mutate(year = as.character(x)) %>% | ||
| # The IDOT shapefiles don't have a set CRS. As far as I can tell, the |
There was a problem hiding this comment.
Fixing a previous oversight.
There was a problem hiding this comment.
[Question, non-blocking] How did you determine that the IDOT shapefiles use CRS 3436?
| iasworld_years <- unique(2021:format(Sys.Date(), "%Y")) | ||
| parcels_current_remote_attr <- file.path( | ||
| output_bucket, "parcel", | ||
| iasworld_years <- unique(2000:format(Sys.Date(), "%Y")) |
There was a problem hiding this comment.
I am moving us away from the old attributes source since our current system of record seems to agree much better with tax district data from other county sources.
| SELECT par.parid AS pin, | ||
| par.class, | ||
| leg.taxdist AS tax_code, | ||
| REGEXP_REPLACE(par.nbhd, '([^0-9])', '') AS nbhd_code, | ||
| leg.user1 AS town_code, | ||
| par.taxyr | ||
| FROM iasworld.pardat AS par | ||
| LEFT JOIN iasworld.legdat AS leg ON par.parid = leg.parid | ||
| AND par.taxyr = leg.taxyr | ||
| AND leg.cur = 'Y' | ||
| AND leg.deactivat IS NULL | ||
| WHERE par.cur = 'Y' | ||
| AND par.deactivat IS NULL |
There was a problem hiding this comment.
Updated with deactivat and cur handling.
| tax_code = str_pad(tax_code, 5, "left", "0"), | ||
| nbhd_code = str_sub(str_pad(nbhd_code, 5, "left", "0"), 3, 5), | ||
| town_code = str_sub(tax_code, 1, 2), | ||
| town_code = str_pad(town_code, 2, "left", "0"), |
There was a problem hiding this comment.
Switching over to town code as it's coded in ias.
| # rJava's LD_LIBRARY_PATH from .Renviron isn't picked up by the dynamic | ||
| # linker in time, so preload libjvm.so by absolute path first. | ||
| dyn.load("/usr/lib/jvm/java-21-openjdk-amd64/lib/server/libjvm.so") | ||
| # tabulizer/RJavaTools reflects into JDK internals (e.g. java.util | ||
| # iterators), which Java 16+'s module system denies by default (JEP 396). | ||
| # Must be set before the JVM starts, i.e. before library(rJava). | ||
| options(java.parameters = c( | ||
| "--add-opens=java.base/java.util=ALL-UNNAMED", | ||
| "--add-opens=java.base/java.lang=ALL-UNNAMED" | ||
| )) |
There was a problem hiding this comment.
copilot helped me sort out some issues with rJava being a PitA. We need it to use tabulizer.
There was a problem hiding this comment.
[Suggestion, non-blocking] Hmm, this makes me a bit nervous: The absolute path to libjvm.so feels like it could easily change in the future or be different between machines, and the need to set flags to disable a feature of Java 16+ module system feels brittle. I wonder if it's worth trying to switch to the newer tabulapdf package before we persist these changes, to see if it resolves these problems? Part of my hesitation stems from a lack of full understanding of the problems these two settings are intending to address, so if you feel confident in your understanding and think it's the right call, I trust you to move forward.
| # Loop through each parquet file and process it | ||
| walk(parquet_files, \(file_key) { | ||
| if (!aws.s3::object_exists(file.path(AWS_S3_WAREHOUSE_BUCKET, file_key))) { | ||
| walk2(parquet_files$Key, parquet_files$output_path, \(file_key, output_path) { |
There was a problem hiding this comment.
I'm pretty sure this walk can be reverted, I just needed some flexibility to work with _test data here.
jeancochrane
left a comment
There was a problem hiding this comment.
Thank you for your persistence getting this over the line! Basically no major concerns from me, other than one idea about where the corner lots inconsistency might be coming from. Feel free to punt it to a follow-up if you'd rather get the majority of these changes in before moving on to small fixes.
| "RemoteRepo": "tabulizer", | ||
| "RemoteUsername": "ropensci", | ||
| "RemoteSha": "08e3d763b7bb2becfe5fc95f800b3767ea704439", | ||
| "Version": "0.2.2", |
There was a problem hiding this comment.
[Thought, non-blocking] You probably noticed this already, but it seems like the package is now developed under the name tabulapdf with a new maintainer: https://docs.ropensci.org/tabulapdf/news/index.html#changes-to-tabulapdf-105
| select(-geometry_3435) %>% | ||
| st_transform(3435) %>% |
There was a problem hiding this comment.
[Question, non-blocking] For the sake of my own clarity: It seems a little strange to drop the 3435 geometry column and then immediately transform the main geometry column (presumably 4326) to 3435, but we're doing this because the st_() functions can only operate on the main geometry column, is that right? Is there any consequence to re-transforming the 4326 column instead of using the 3435 column directly? I notice this is a pattern that is now widespread across the scripts so I want to make sure I understand it.
| }) %>% | ||
| mutate(year = as.character(x)) | ||
| mutate(year = as.character(x)) %>% | ||
| # The IDOT shapefiles don't have a set CRS. As far as I can tell, the |
There was a problem hiding this comment.
[Question, non-blocking] How did you determine that the IDOT shapefiles use CRS 3436?
| AWS_S3_WAREHOUSE_BUCKET, "spatial/ccao/county/2019.parquet" | ||
| ) | ||
| ) %>% | ||
| st_transform(4326) |
There was a problem hiding this comment.
[Question, non-blocking] To clarify my understanding: This transformation step is actually unnecessary because the Cook boundary geometry is already 4326, right? I don't necessarily mind the inconsistency, I just want to make sure I understand why we were able to remove the equivalent transform step in spatial-access.R and export-geojson.R.
There was a problem hiding this comment.
[Thought, non-blocking] Good idea! I wonder if this street network might be useful more broadly, such that we might want to save it under a more general location than ccao/corner/? You would have a better sense of its usefulness than I do, so I leave it up to you to decide whether it's worth the trouble.
| # Fetch the OSM street network for the county, removing any OSM way types | ||
| # that are not main roads |
There was a problem hiding this comment.
[Nitpick, optional] Am I right that the raw script is now handling the step of removing way types that are not main roads?
| # Fetch the OSM street network for the county, removing any OSM way types | |
| # that are not main roads | |
| # Fetch the OSM street network for the county |
| st_transform(4326) %>% | ||
| st_minimum_rotated_rectangle() %>% |
There was a problem hiding this comment.
[Question, blocking] I wonder if this might be the source of the right-angle issue you mentioned in the PR body? I'm still a geospatial novice, but my understanding is that 4326 operates in degree space, meaning the rectangle will be stretched into a parallelogram on the ground. It seems like the previous code transformed to 4326 after calculating the rectangle, which makes more sense to me (though I could be wrong):
| # rJava's LD_LIBRARY_PATH from .Renviron isn't picked up by the dynamic | ||
| # linker in time, so preload libjvm.so by absolute path first. | ||
| dyn.load("/usr/lib/jvm/java-21-openjdk-amd64/lib/server/libjvm.so") | ||
| # tabulizer/RJavaTools reflects into JDK internals (e.g. java.util | ||
| # iterators), which Java 16+'s module system denies by default (JEP 396). | ||
| # Must be set before the JVM starts, i.e. before library(rJava). | ||
| options(java.parameters = c( | ||
| "--add-opens=java.base/java.util=ALL-UNNAMED", | ||
| "--add-opens=java.base/java.lang=ALL-UNNAMED" | ||
| )) |
There was a problem hiding this comment.
[Suggestion, non-blocking] Hmm, this makes me a bit nervous: The absolute path to libjvm.so feels like it could easily change in the future or be different between machines, and the need to set flags to disable a feature of Java 16+ module system feels brittle. I wonder if it's worth trying to switch to the newer tabulapdf package before we persist these changes, to see if it resolves these problems? Part of my hesitation stems from a lack of full understanding of the problems these two settings are intending to address, so if you feel confident in your understanding and think it's the right call, I trust you to move forward.
This PR swaps out
geoarrowand replaces it with the basearrowpackage. What this means is that our parquet files in s3 will no longer have spatial metadata where relevant. My solution to this is to make sure that geometry columns and their EPSG identifiers are all recorded for any spatial datasets written as parquets. We can use this data when reading them back in to make sure we are handling spatial data correctly. All of the reading and writing updates are handled inutils.R.Companion aws-infrastructure PR
Before merging
_testpaths from scripts and raw bucket.Renvirongeoarrowchanges toscripts-ccao-data-warehouse-us-east-1/spatial/spatial-environment-road.RAfter merging
s3://ccao-data-warehouse-dev-us-east-1/spatial/intos3://ccao-data-warehouse-us-east-1/spatial/_testsuffix dataScripts that don't work
Couldn't finish running due to OSM timeouts:
Are not reproducing old data:
In the case of corners, the new output is not as good as the old output. For neighborhoods, it seems like there's a genuine improvement. I'm still not sure why this is happening, but given the very minor code changes I made to the algorithms that produce these outputs, I am leaning towards this being an issue with updated dependencies (
sf,rmapshaper). It's obviously worth getting to the bottom of this, but that work can be done after we've merged in the changes made by this PR.This does leave one important issue to resolve before merging, though: we will essentially lift and shift the spatial folder in the dev warehouse bucket into our prod warehouse bucket after we merge this, to make sure we aren't using parquet files with attributes that can't be read by the base
arrowpackage without throwing an error. The updated corner lots file is not in a good place, so we'll need to decide how to handle corners and major/secondary osm roads.geoarrow, we could read them into R with that pakage and then rewrite them out using the new pipeline. this would allow us to preserve the old data while also making it compliant with the new etl pipeline. This will mean what's in athena is not a product of the new pipeline, but that should be fine while we work out what's causing the difference.Testing
Data generated without any spatial code changes other than swapping
arrowforgeoarrowvw_pin10_location_fill
The ultimate test to see if using standard, non-spatial parquet files in athena produces the same spatial results as using geo-parquets made with
geoarrowis to build our location db and check the new version ofvw_pin10_location_fillagainst the old version.We can see almost all the differences are in tax districts. The source of this divergence is the parcel shapefiles. This is also the source of divergence for
combined_municipality_namesince it compares municpality names derived from tax districts to those from spatial joins. If we compare tax codes in the spatial parcel files, it appears the new files are significantly more accurate than the older files:This is almost certainly a product of me changing the way the attributes files are constructed in the raw ingest file for the spatial parcel files.
Rewrites to avoid errors while re-running
Neighborhhods
There are a lot of very small differences, primarily along the borders of the neighborhoods. We're talking about essentially zero-width lines. There are other, larger differences - I've chosen a few neighborhoods where these differences appeared and included them below in the
check_newandcheck_oldobjects. To me, it looks like the new file is doing an objectively better job, for whatever reason. The edits I made to the script didn't change our algorithm, they just handled an error that popped up from trying to negative buffer empty geometries. My only guesses as to why these differences are appearing is that I used different syntax to make absolutely sure we're actually using a 3435 projection during all the buffering or upgradingsforrmapshaperaffected some of the buffering.I thought this might be due to changes in neighborhood #'s between the new and old
spatial.parceltables but that is not the case (though there are some minor differences):returns no rows
And we can see from comparing the old and new parcels for these neighborhoods that that's also not the issue.
Issues
Corner Lots
These are not generating as they did in the past. We can see that here:

The script now filters out parcels it wouldn't have in the past during
filter(max_degree == 90 | n() >= 3, na.rm = TRUE)because some of these angles are now less than 90. I'm still trying to figure out why this is the case.