Build the frame before st_sf() sees it, so the reporting columns survive a tibble - #36
Merged
NewGraphEnvironment merged 6 commits intoAug 30, 2026
Conversation
fly_footprint() loses footprint_basis, footprint_terrain, height_agl and dem_coverage whenever its input carries tbl_df — which is what bcdata::collect() returns, so the documented data source is exactly the caller that cannot see them. Every fixture in this package is plain `sf, data.frame`: the bundled gpkg reads back that way, and both synthesized fixtures are built by st_sf() on plain vectors. So no case added along the existing axis could have found this. centroid_shapes() sweeps the axis instead, reading the tibble honestly via st_read(as_tibble = TRUE) rather than overwriting class(), and asserting that premise inline so a future sf change fails by naming the real cause. These fail on unmodified source. Refs #35 Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01GBKqedyBysV7hB4DuL98ZR
st_sf() keeps only its first argument when that argument is a tibble:
`else if (inherits(x[[1]], c("tbl_df","tbl"))) x[[1]]`. Every trailing
named column is discarded, so footprint_basis, footprint_terrain,
height_agl and dem_coverage reached no bcdata caller — which is to say
no caller using this package's own documented data source. Assigning
them onto the attribute frame puts them inside x[[1]], where they
survive, and leaves the caller's class alone.
Three things the reviews found, each reproduced before acting:
- 0-row input returned footprint_basis and footprint_terrain as
logical, because ifelse(logical(0), ...) is logical(0). An empty
result would not bind to a populated one, which is exactly the
per-AOI ledger this reporting surface exists for. Seeded with
as.character().
- An input already carrying footprint_basis got a duplicate
footprint_basis.1 under the old code, and the filter the docs
prescribe read the caller's column while fly's answer sat unread
beside it. The computed value now wins.
- The class contract was stated too strongly. st_transform(), not
st_sf(), moves sf to the front, so a bcdc_sf input returns
sf, bcdc_sf, ... The set survives; the order does not.
Each new guard was checked by restoring the defect from 8585fd5 --
not a reconstruction -- patching both the namespace and the attached
binding, and printing a value only the broken code could produce.
225 -> 338 tests. Refs #35
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GBKqedyBysV7hB4DuL98ZR
Title and Description both predated fly_fetch(), fly_georef() and fly_bearing(), so the pkgdown landing page and any generated citation described roughly half of what is here. README and CLAUDE.md were widened already; DESCRIPTION was the remainder. R CMD check Status OK, which is the only gate on the title-case and Description sentence rules -- this repo ships pkgdown.yaml and no R-CMD-check workflow, so CI does not repeat it. Closes #31 Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01GBKqedyBysV7hB4DuL98ZR
The headline feature of 0.5.0 was unreachable from the data source this package documents, so the patch is the point of the release rather than housekeeping. NEWS records what was actually lost: geometry and every downstream number were always correct, and only the audit trail went missing -- which is why nothing errored and why the suite stayed green through two releases. Verified end to end against the live catalogue: 1405 real centroids come back as bcdc_sf, sf, tbl_df, ..., all four columns arrive, and the documented footprint_basis filter excludes 151 frames that were invisible before. No tag here. /gh-pr-merge does the tagging after merge; a tag on a branch tip that review may still amend moves silently. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01GBKqedyBysV7hB4DuL98ZR
Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01GBKqedyBysV7hB4DuL98ZR
NewGraphEnvironment
deleted the
35-fly-footprint-drops-footprint-basis-and
branch
August 30, 2026 04:52
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
fly_footprint()silently droppedfootprint_basis,footprint_terrain,height_aglanddem_coveragewhenever its input carried thetbl_dfclass.bcdata::collect()returns exactly that class, so every caller queryingWHSE_IMAGERY_AND_BASE_MAPS.AIMG_PHOTO_CENTROIDS_SP— the documented source for this package — lost the whole reporting surface 0.4.0 (#30) and 0.5.0 (#9) added.Nothing errored. Geometry and every downstream number were correct; only the audit trail was gone, which is why two releases shipped with it.
Cause
sf::st_sf()builds its attribute frame with a branch that keeps only its first argument when that argument is a tibble:Every trailing named column is discarded. A general R trap rather than a
flyquirk. The fix assigns the four columns onto the attribute frame first, so they are insidex[[1]]where they survive, and the caller keeps the class they passed in.Why the suite could not see it
Every fixture in the package reads back as plain
sf, data.frame— the bundled GeoPackage, and both synthesized fixtures, which are built byst_sf()on plain vectors. No case added along the existing axis could have found this. Seventh instance of the pattern ininst/notes/terrain-correction.md.So the tests sweep the input-class axis instead — plain / tibble / grouped /
bcdc_sf— asserting identical names, identical values and identical geometry across shapes. The tibble shape is read honestly viast_read(as_tibble = TRUE)rather than by overwritingclass(), and the premise is asserted inline so a futuresfchange fails by naming the real cause.Two further defects the reviews surfaced
Both reproduced before acting, both now guarded:
footprint_basis, the old code appended a duplicatefootprint_basis.1and left the caller value under the documented name — sofootprints$footprint_basis != "unknown_format", the filter the docs prescribe, read the caller's column while fly's own answer sat unread beside it. This hit plainsfcallers too, who were never affected by the tibble bug.logicalcolumns.ifelse(logical(0), ...)islogical(0), so an empty result would notbind_rows()onto a populated one — exactly the per-AOI ledger this reporting surface exists for (stac_airphoto_bc#16).Corrections to my own claims
Two things I wrote and the reviews falsified:
@returnsaid "the input class is preserved". The class set survives; the order does not —st_transform()(notst_sf()) movessfto the front, sobcdc_sf, sf, ...returnssf, bcdc_sf, .... Anidentical(class(out), class(in))assertion would have passed on the three shapes I had and failed on the one caller the issue is about.fly_footprint(fly_footprint(x))idempotent. It does not: a plain 20-row sf comes back with 100 rows, silently, becausest_coordinates()on polygons yields 5 rows per feature. Pre-existing and unchanged, but the sentence invited the round trip. Cut.Verification
FAIL 0 | WARN 0 | SKIP 0R CMD checkStatus: OK (the only gate on the Title rules — this repo has no R-CMD-check workflow)8585fd5— not a reconstruction — patching bothasNamespace("fly")and the attached binding, with a proof-of-patch value printed before assertinglintrunchanged;NAMESPACEbyte-identical at 9 exportsAlso
Widens the
DESCRIPTIONTitle and Description, which described roughly half the package — they predatedfly_fetch(),fly_georef()andfly_bearing().Released as v0.5.1. Not tagged on the branch —
/gh-pr-mergetags after merge, and a tag on a branch tip that review may amend moves silently.Follow-up worth filing
The
st_sf()trailing-column behaviour belongs insoul'sconventions/code-check.mdas the general rule — a constructor taking...may discard trailing arguments depending on the class of the first one; pass a fully-built frame instead — with a sweep across the other NGE packages.Closes #35
Closes #31
🤖 Generated with Claude Code
https://claude.ai/code/session_01GBKqedyBysV7hB4DuL98ZR