gq 0.16.0
-
The QML corpus is re-vendored: rfp had repaired two styles and gq had not pulled them (#90).
vector/floodplains.qmlnamedfeature_nameas itspreviewExpressionwhere rfp had corrected it tofloodplain_name— so a floodplain identified itself by the wrong attribute in QGIS’s identify panel and layer tree.vector/bcfishobs_fiss_fish_observations.qmlcarried 5 fields the renamedbcfishobs.observationsno longer has (fish_obsrvtn_event_id,wscode_ltree,localcode_ltree,waterbody_key,species_id), each across five blocks, and apreviewExpressionnaming a column that no longer exists. Its configured roster goes 24 to 19. Measured over the whole corpus, not just the two: 60 files checked, 2 drifted before, 0 after.The direction is upstream-fixed and gq-stale, so this is a re-run of
data-raw/styles_vendor.R, not an edit here.inst/styles/index.csvis byte-unchanged.This resolves part of #86 — the preview expression, and 5 of the 7 fields it listed. Four
fish_obsrvtn_pnt_distinct_idreferences remain. Whether the other two (fid,linear_feature_id) are dead at all is now an open question rather than a finding: rfp#307 measured the surviving 19 against the live object and reports 0 dangling, which #86’s original list contradicts. #86 has been re-scoped and retitled; its premise — that the byte-identity guard was green because upstream was identically wrong — expired when rfp moved. -
The raster-exclusion guard is a positive set pin rather than a list of the styles to exclude.
test-gq_style_qml.Rassertedexpect_false(any(c("dem_hillshade", "dem_turbo") %in% ...)). A negative literal set goes blind exactly as upstream grows: rfp later addedairphoto_gray, which no template uses either, and the guard had nothing to say about a name written after the list. It is now pinned by what the corpus does hold —habitat_lateral, and the six services — which cannot be outgrown, since a raster arriving upstream and being wrongly vendored fails whether or not anyone anticipated its name.Demonstrated rather than asserted: the new guard fails on an unanticipated raster, on
airphoto_grayspecifically, and on a wrongly dropped service, and the old guard passes onairphoto_gray.Nothing in the suite surfaced this — the vendoring script’s own stdout did, reporting three skipped rasters where its comment named two. That comment is corrected too, and so is its stated reason: the skip rule is absence from the template roster, not membership in
rfp_raster_styles(). Those are different sets, and the fourth member of that roster ishabitat_lateral, which gq does vendor. -
The vendoring script’s gap report derives its form/gap split instead of restating it. The comment said “the 4 forms are owned by
rfp_form_build()”, written whengroups.csvcarried four form keys; it carries two today, so a reader of the message was told 4 of the 8 reported gaps were out-of-scope forms when the truth was 2 forms and 6 genuine. The split is now counted at run time and cannot drift from the file it describes.That was the third stale population claim in this one script, and the enumeration that followed found two more of a different kind. A first sweep covered counts, member lists and superlatives and reported the class closed; review then found a causal claim and a historical one, both false, in the very paragraph that sweep had ticked. The comment had explained the
<map-layer-style-manager>split as “QGIS-authored sidecars”, which rfp’s own reference nodes refute — all nine are QGIS-written and all nine open with<flags>— and asserted thatairphoto_grayhad been “repaired out of that form”, when it was<flags>at every revision including the one that created it.That paragraph then took two more attempts, each wrong in a different shape. The original had the polarity backwards. The first repair threw out the variable instead of flipping it — “authorship does not discriminate”, reasoned from nine reference nodes that are all QGIS-authored and all
<flags>, a sample with no variation in the thing being tested. The second stated authorship as the cause, whichhabitat_lateralrefutes from three lines up in the same comment: rfp lifted that one out of the templates itself and it opens with<flags>, so QGIS-authored implies<flags>while<flags>implies nothing.The cause is the export boundary, and rfp states it in one sentence (
R/rfp_qgs_style_set.R:522): those two carry the tag first “because they were exported under the oldorder =override”. Their child lists are the<maplayer>tail from the point a positional scan stopped. Now stated and cited.The mechanism behind all five: every one of these claims is about rfp, and every sweep measured rfp’s data — never rfp’s prose. That settles counts, member lists and superlatives, and structurally cannot settle a causal or historical claim, because the output is what such a claim is about. Those are settled by reading the producer’s own account: roxygen at the site, the test that pins the behaviour, the README, then
git log --follow --name-statusfor provenance. Both this and #88 were invisible to CI for the same structural reason: the drift guard
skip_if_nots without an rfp checkout, so it is green in CI and red only on a developer machine. That inversion is #78 and is not addressed here.
gq 0.15.0
-
The theme roster carries the three remaining xyz basemaps and
Trails(#88). rfp#185 re-saved all nine visibility presets to hold every opaque basemap present-and-off, andthemes.csvstill recorded the pre-change membership, so gq and rfp disagreed about what nine themes contain. Re-extracted withdata-raw/reg_extract_themes.Ragainst rfp 0.76.0: 232 to 268 rows, 36 additions and 0 removals, the 9 template-theme pairs unchanged. The added rows arebing_aerial,esri_satellite,google_satelliteandtrails, each atfalsein every preset of both templates.rfp carries the cross-repo drift guard for exactly this (
test-qgs_build_harness.R), and it goes from FAIL 9 to FAIL 0. Itskip_if_nots without a gq checkout, so it is green in CI and red only on a developer machine — the inverse of where a failure gets noticed, which is how the roster came to be stale in the first place. The opaque-basemap guard now covers all four basemaps by data rather than one by coincidence.
test-gq_groups.Rpinned that the roster named onlyesri_world_topo, with a comment recording that the pin existed to fail at this exact regeneration rather than let the shape counts be re-pinned as routine growth while a live satellite rode through on a green suite. It fired as designed. The set pin now names all four, and each is additionally asserted present in all 9 pairs: a key named by only some presets is unguarded in the rest, and the set pin alone cannot see that because the key is still present somewhere.
gq 0.14.0
-
Three
awsrows now say what the bucket actually stages (#82). gq’s registry is what rfp requests —rfp_project_create()resolves its layer set throughgq_template_layers(), not through rfp’s ownrfp_source_aws.txt, which is only the gq-absent fallback. So a wrongsource_layerhere is a failed download on every project build, reported as one line in a long log and otherwise indistinguishable on a device from a layer that is empty in the AOI. Measured against the bucket, 7 of 9 distinctawssource_layers resolved; now 9 of 10, and the one remaining 404 is deliberate and filed upstream.-
habitat_lateralwas typedlocalwith the sentinelsource_layer == layer_key, which is what stopped rfp ever asking for it — so every project clipped the raster by hand. It is nowaws/habitat_lateral.tif, the one row whosesource_layeris a filename rather than aschema.table: rfp fetches it withrio maskand writes a standalone.tifbeside the project. This is not the project-rastersource_type#72 wants, and deliberately so — rfp computes its passes asintersect(setdiff(rfp_manifest_types(), "frozen"), source_type)— note thesetdiff, sincefrozenis in that vector and still does not download, so vector membership alone is not the rule — and a term rfp does not know is silently dropped and the layer never downloads at all. -
bcfishobs_fiss_fish_observationsfollows an upstream rename tobcfishobs.observations, read from the job that stages it rather than inferred from a bucket listing.source_layeris the S3 object stem, the GeoPackage table name and the.qgslayername=at once, so this fixes the download only — and it must not ship ahead of NewGraphEnvironment/rfp#305. rfp drops a maplayer whose target is absent and not in the requested set. Before this change the old name was requested, so the 404 left the styled layer in place for a later refresh; with the rename in and rfp’s templates not, the old-named maplayer is removed from the project, symbology and all, while the data lands under a name nothing references. Projects built in that window need the layer re-added by hand. -
damis unchanged on purpose.bcfishpass.damsis staged by no job on any branch — a gap, not a retirement — and dropping the row would have hidden it (NewGraphEnvironment/db_newgraph#20).
-
The
aws/bcdata/fwa/osmsource rule no longer blesses any string with a dot in it. It checkedgrepl(".", source_layer, fixed = TRUE)while its own comment claimed “a real, schema-qualified table”, and the two part company as soon as a source is a file. It is now an explicitschema.tableshape plus a named file-target entry, so a layer added tomorrow must either look like a table or be a deliberate decision — never exempt by default. Worth knowing that shape cannot separate the two:habitat_lateral.tifmatches a^schema\.table$regex as readily as a filename one, becausetifis a valid table token.The
bcfishobscorrection lives indata-raw/reg_build_main.R, not in the extracted registry, following the pattern that script already uses for bcfishpass#13. The extracted JSON is a record of what rfp’s template says, so editing it would make the file lie about its provenance and be reverted by the next re-extraction;reg_custom.csvis not the route either, becausegq_reg_merge()replaces a layer entry rather than merging its fields — a one-column row would have deleted the layer’smarkandlabeland it would have drawn as nothing. The block is self-retiring: it stops the build with “delete this block” once rfp’s templates carry the new name.
gq 0.13.1
.claude/,gq.Rprojand a worktree’s.gitno longer ship in the tarball.R CMD buildships every top-level entry nothing excludes, so all three were landing in the library of anyone installing gq from GitHub, andR CMD check --as-cranreported the first two as NOTEs. Three.Rbuildignorelines;Status: 2 WARNINGs, 2 NOTEsbecomesStatus: 2 WARNINGs, and the tarball is otherwise unchanged at 226 paths — none lost, none gained. The two remaining WARNINGs are separate and tracked in #51.-
R CMD buildships the.gitfile when you build from a git worktree, and that is worth knowing outside gq. R excludes version-control entries with anisdir-gated rule, and a worktree’s.gitis a file containing an absolute path to the developer’s machine — so nothing excluded it..gitignore,.gitattributesand.gitmodulesare safe, being covered by an ungated rule;.gitis the only version-control name with a legitimate file form and no ungated rule, so it is the whole exposure. Any package built from a worktree has been shipping it.It stayed hidden because the guard that would have caught it tested
dir.exists(".git"), which is false in a worktree — so in the checkout layout our conventions prescribe, the guard silently skipped. A guard that cannot run is not a weaker guard, it is an absent one. -
tests/testthat/test-build_hygiene.Rfails the suite when a top-level entry is neither shipped on purpose nor excluded on purpose. The NOTE this replaces is non-blocking under the workflow’s deliberateerror-on: "error", so it had printed on every CI run since CI landed and gated nothing; a test failure is an ERROR inchecking tests. The guard also asserts the other direction — that no.Rbuildignorepattern matches a path we ship, checked at every depth, since nothing in R reports a file that quietly stopped shipping.Also swept in:
.gitignoreexcluded.venv,__pycache__,dist,.vscode,.idea,*.egg-infoandThumbs.dbwhile.Rbuildignoreexcluded none of them, so any machine holding one shipped it.
gq 0.13.0
-
High Detail - Crossingsshows its layers inbcrestoration_mobileagain. The roster recorded that theme as enumerating 28 layers and showing none of them. That was a faithful extraction of a template rfp shipped as a stub, repaired upstream in NewGraphEnvironment/rfp#217 and released in rfp v0.47.0. 27 rows flip to visible;esri_world_topostays off, as it is inbcfishpass_mobiletoo. Totals are unchanged at 232 rows over 9 template-theme pairs, andreg_main.jsonandgroups.csvare untouched.It is the most used theme in the fleet, so it is worth being right.
-
The 0.3.0 note below is wrong, and is left standing as the record. That entry justified keying themes by template with “
High Detail - Crossingsshows 27 layers inbcfishpass_mobileand 0 inbcrestoration_mobile”. The zero was the stub, not a design.templateis still rightly part of the key —Land Tenureships in one template only, and the two templates are separate files free to drift — but the illustration was a defect mistaken for evidence.The general form is worth carrying: when a registry difference is the evidence for a schema decision, check that it is a decision.
-
The assertion that pinned the zero is replaced by two guards. One reports drift between templates sharing a theme name; one reports any theme that shows nothing, over every template-theme pair rather than only the shared ones, because
Land Tenureis restoration-only and a shared-scoped check cannot see an unshared theme at all. (Not because of the stub itself:High Detail - Crossingsships in both templates, so a shared-scoped check would have caught rfp#217. The widening is justified by the theme the stub happened to miss.) Alongside them the roster’s shape is now pinned (232 rows, 9 pairs, no duplicate key,Land Tenureat 26/22) and no theme may switch on any of the opaque xyz basemaps — with every wms layer and every raster pinned as opaque or overlay, and the subset the roster currently names pinned beside it, so neither a new basemap nor the pending rfp#185 regeneration can be absorbed as roster growth. To escape the guard now a layer would have to be an opaque basemap that is neither a wms nor a raster, which is definitional rather than a coincidence in the current data. Each guard was run against a restored defect rather than assumed to work.What none of them assert is that the roster still equals what the templates say; that needs a live-template test and is tracked separately.
gq 0.12.0
-
gq_form_types()— the roster of Mergin survey forms, vendored from rfp intoinst/registry/form_types.csv. gq declared four forms; rfp registers fourteen. The roster is a separate table fromgroups.csvbecause the two answer different questions:rfp_qgs_form_add()injects forms per project and a project’s config selects which, whilegroups.csvmodels per-template contents. Folding the roster in would have madegq_template_layers()report thirteen forms for a template shipping two.form_ednaandform_monitoringleavegroups.csvand land here. They are real forms with collected field data; neither is in either shipped template. habitat_lateralis registered, and it is the registry’s first raster. It carries the full QGIS palette — band values 1 and 2, the QML’s colours and labels, and the renderer’s 0.4 opacity.gq_tmap_classes()reads it; the translators do not render rasters, sogq_style_qml()remains the answer for anything QGIS-facing.-
Breaking:
gq_tmap_style()andgq_mapgl_classes()now refuse a layer whose type they cannot render, where both previously answered. The first returned an empty argument list, whichdo.call(tm_polygons, list())turns into tmap’s own defaults — a map that looks fine and is not the registry’s. The second returned a well-formed MapLibre match expression whose["get", field]reads a feature property, so on a raster source it resolved against nothing and painted every pixel the fallback colour.gq_mapgl_classes()also now requires atype, matchinggq_mapgl_style(), which has always required one. gq_reg_custom()coercesclass_valueto character before using it as a class key. It was positional assignment for a numeric key, which the new raster convention — a paletted band keys on pixel value — invites.
gq 0.11.0
-
Group names now match the shipped QGIS templates, and four of them changed.
rfp_project_create()copies a template and trims it, so a project’s tree node is literally the template’s string. gq spelling a group differently made the join fail in both directions, silently.was is Other Point FeaturesOther point featuresRoads/Rails/PipelinesRoads,Railways,PipelinesStreams/Habitat ModelsStreams/Habitat modelsBasemap/BECBasemap/Terrestrial EcologyGrep your code for the four old strings.
gq_group_layers()returns a zero-row data frame for an unknown group, with no error and no warning, so a hardcoded old name gets an empty result and no signal — the same silent-drop class this release exists to remove.rfpis the only known consumer and does not pass group names, so no shim is shipped. Base - Orthoimageryis gone; it never existed in any template. 0.10.0 added itstemplates.csvrow fromgroups.csvwithout checking a.qgs, atgroup_order11 — belowBase - misc, which holds all four opaque xyz basemaps. The registry was declaringorthophoto_tilesbeneath ESRI World Topo, which is exactly the failure that has twice cost a field user a layer.orthophoto_tilesmoves toBasemap/Terrestrial Ecology, where the template keeps it.FloodplainandRestorationmove aboveBasemapinbcrestoration_mobile.Basemapholds the waterbody fills that draw over a land-cover change product — 46% of one project’s change area is water-class.New guard:
inst/registry/template_groups.csvandtests/testthat/test-template_drift.R. The registry is now checked against the group tree of the shipped templates — composition both directions, byte- exact names, relative order on the intersection, and no group declared below the bottom of the template’s stack. Divergences are declared exemptions carrying a reason and an issue, each asserted still needed. #66
gq 0.10.0
Template composition is now guarded, and two groups that were never mapped are.
gq_template_layers()returns more layers for both shipped templates —bcrestoration_mobile57 → 64,bcfishpass_mobile58 → 61. A project built from a template was displaying cartography for data it never downloaded, because the chaintemplates.csv → groups.csv → reg_main.jsonis three joins deep and none of them reported a miss.Base - Orthoimageryhad been mapped to zero templates for the entire history of the file with a green suite. #40source_typenow drives a real check, and two layers had the wrong value.harvest_areaandplanting_sitechange frombcdatatolocal— a value change in a columngq_groups()returns, not just extra rows. They are project-authored layers (“buffered river corridor”, “proposed restoration planting location”), never BCGW tables, so they now carry thesource_layer == layer_keysentinel thatform_pscisandform_fiss_sitealready used.Two new registry layers.
old_growth_management_areas— the most-used layer in a 16-project corpus that the registry did not carry — with symbology extracted from a project that styles it.national_parkwith symbology authored, labelled as such in itsnote, reusingprovincial_park’s colours rather than an invented hue. Neither has a renderer in the shipped.qgstemplates, which is why neither was ever extracted.group_orderis documented as a sort key only: per-template, requiring neither contiguity, a 1-based start, nor cross-template agreement. The two shipped templates happen to have those properties and do not define them.
gq 0.9.0
-
Breaking for label text: the
ACCESSmapping_codeclasses are relabelled.mapping_codeis<habitat use>;<barrier status>[;INTERMITTENT], but the source QGIS project labelled token 1ACCESSwith the token 2 vocabulary, so every ACCESS class read back as a self-contradiction:class was now ACCESS;NONENo known barriers; no known barriersAccessible; no known barriersACCESS;MODELLEDNo known barriers; potential barrierAccessible; potential barrierACCESS;ASSESSEDNo known barriers; known barrierAccessible; known barrierACCESS;DAMNo known barriers; damAccessible; damACCESS;REMEDIATEDNo known barriers; remediatedAccessible; remediated…and the five
;INTERMITTENTvariants of each, onstreams_salmon,streams_standstreams_bt— 30 classes in all. Colours, widths, dashes and class keys are untouched.If your project hand-decodes these tokens, you can delete that code. At least three did. Anything that string-matches the old label text needs updating.
This corrects an upstream authoring bug (NewGraphEnvironment/bcfishpass#13) at gq’s registry build.
gq_reg_main()is the corrected surface. Deliberately not corrected, because both must stay faithful to their source:gq_qgs_extract(), which copies the category label verbatim from whatever.qgsyou hand it, and the shipped QML corpus (gq_style_qml()), which is byte-identical to rfp’s store — so a style loaded into QGIS Desktop or QWC2 still shows the upstream wording until #13 lands. Thereg_qgis_*.jsonextraction artifacts are likewise left as-extracted. Closes #33, #37. -
The composition vignette’s map now describes itself. Its most prominent feature — a 397-feature salmon habitat network — was styled from the registry and missing from the legend entirely, while the prose beneath described its widths and dashes. Fixed, along with the reason it went unnoticed: a new test parses the map chunk and fails the build if any layer drawn from the registry is absent from the legend.
The map also gained AOI containment, type sized for the width it is published at, and an editorial cut — 130 of 146 crossings are modelled candidates rather than surveyed sites, and at their correct size they buried the network the map exists to show. #61.
Both vignettes moved to
bookdown::html_vignette2with numbered figure captions;bookdownadded toSuggests.Removed the dead
registry/directory at the repo root — a build-ignored fossil superseded byinst/registry/at the original scaffold.
gq 0.8.0
-
Point symbols now render at the size the registry says. The mm-to-renderer conversion was a guessed constant in both backends — tmap divided by 3, mapgl divided by nothing — so the two disagreed by 3x and neither matched QGIS. Measured on the drawn ink, tmap was putting every marker on the page 27% oversized.
New
gq_symbol_size()does the actual unit conversion, and it depends on the shape. tmap sizes symbols in grid “lines” (5.08 mm), but R’s graphics engine then applies a per-pchfactor, and base R normalises those by area where QGIS normalises by extent. A circle draws 3.81 mm of ink per size unit, a square 3.38, a triangle 5.13 — so one divisor cannot serve them all. MapLibre’scircle-radiusis a true radius in CSS pixels, somm / 2 * 96/25.4.Every point layer changes size. On gq’s own vignettes the crossings shrank 41% and stopped burying the stream network, while the falls doubled and the fish observations quadrupled to their true relative weight. A
scaleargument shrinks a whole map uniformly if you need that — one number, rather than the per-layer hand-tuning this replaces.Note the registry’s
radiusfield is a diameter:gq_qgs_extract()reads the QGISSimpleMarkeroption namedsize, the marker’s overall extent, and stores it under a misleading name. New:
gq_symbol_shape(). The registry has carried a mark shape for 15 layers since extraction — circle, square, star, triangle — and nothing translated it, so maps hardcoded their own marker codes.starbecomespch = 8, which draws a star and takes no fill; a filled circle would have silently discarded the distinction. mapgl returnsNULL, since a MapLibrecirclelayer has no shape concept.
gq 0.7.0
-
The default basemap provider changed, because the old one started serving a watermark. Carto made their basemaps key-only; without a key they return an “API KEY REQUIRED” image. That image is a structurally valid PNG, so nothing errored, and a watermarked map reached the published documentation site.
gq_basemap_tiles()now defaults toEsri.WorldGrayCanvas— keyless, and verified clean over BC extents.If you passed
providerexplicitly you are unaffected. If you relied on the default, your basemap changes appearance: the gray canvas is flatter than Positron, so a hillshade blend carries more of the terrain. In gq’s own vignettes the result is more legible, not less. -
New: a flat-tile warning. A provider can answer HTTP 200 with an image that is not a map.
gq_basemap_tiles()now warns when a tile comes back as a single flat colour —Esri.WorldTerraindoes exactly this over parts of BC. The tile is still returned, since a genuinely uniform extent (open ocean) looks identical and dropping it would destroy valid data.Watermarked tiles are deliberately not detected. Measured across zoom levels, a clean tile and a watermarked one have indistinguishable dark-pixel fractions, so any threshold would pass watermarks while looking like a check.
?gq_basemap_tilescarries the numbers and the reasoning. -
Fixed: the documented “a failed tile fetch costs the basemap, not the figure” pattern never worked. Both vignettes held a
NULLraster and passed it totm_shape(), which rejectsNULLoutright — so the guard moved the failure into map composition instead of preventing it. If you copied that block, build the whole layer conditionally rather than the data:basemap <- if (is.null(canvas) || is.null(relief)) NULL else tm_shape(gq_basemap_blend(canvas, relief)) + tm_rgb() m <- basemap + tm_shape(aoi) + ...NULL + tm_shape(...)composes correctly, so an absent layer is the shape that holds.
gq 0.6.0
-
Classified layers now render every aesthetic the registry defines, not just colour. Line width, line dash and point size were collapsed to the first registry class and emitted as a scalar. For the
mapping_codehabitat layers that meant half the layer silently disappeared: habitat use drives width and barrier status drives colour, so every reach — spawning, rearing and access — drew at spawning width while the colours rendered correctly.Each axis now maps through its own scale, keyed on the same ordered class vector as colour.
do.call()callers need no change.Dash had a second symptom worth naming:
gq_tmap_legend()has emitted per-classltysince 0.3.0 while the map never read it, so the legend drew a dashed key beside a line the map drew solid — 15 of 30 stream classes. The returned list changes shape for classified layers:
lwdandsizeare now the classification field name rather than a number, alongside newlwd.scale/lty.scale/size.scaleentries. Numeric axes fall back to the old scalar when the registry defines the value for only some classes.Unknown class values no longer abort the render. Fixed as a side effect of 0.5.1 passing
levels— a layer whose data carries a code absent from the registry used to error withAll levels should occur in the vector names of values.
gq 0.5.1
-
Classified layers no longer mislabel when the data carries a subset of the registry’s classes.
tm_scale_categorical()matches colours by name but labels by position, and derives its levels from the data — so with 3 of 26 road classes present, tmap took the first three labels whichever classes those were. The package’s own vignette was an instance: its arterial, highway and local roads drew as “Freeway” and “Highway”.gq_tmap_style()now passeslevelsalongsidelabels, from the same ordered registry vector, so the two cannot drift whatever the data holds. No signature changed — there is nothing for callers to pass or remember.Scope was narrower than it first appeared: classified layers set
tm_legend(show = FALSE), and colours already matched by name, so the drawn map was never wrong. What changes is the warning on every classified draw, and the legend for anyone who turns it back on.
gq 0.5.0
-
Map composition, not just style translation. Six helpers encode the cartography conventions as defaults:
gq_bbox_aspect(),gq_bbox_clip(),gq_scale_breaks(),gq_basemap_tiles()/gq_basemap_blend(),gq_tmap_legend()andgq_tmap_keymap().Each replaces three to five copies scattered across the reporting repos, the package’s own vignette and the cartography skill. Two of those copies documented themselves as ports of this vignette, so the code had already made the round trip out of gq and back.
Extracting them settled three disagreements the copies did not know they had.
gq_bbox_aspect()applies the latitude correction from the CRS rather than hardcoding one answer — a projected copy and a geographic copy each had it wrong for the other’s input.gq_basemap_blend()ships two named operators, because the gamma form suited to a relief tile service and the capped linear form suited to a DEM hillshade were believed to be the same thing.gq_bbox_clip()keeps selecting features distinct from cutting them, which two near-identically named copies had blurred.gq_tmap_legend()merges simple and classified layers into one legend per geometry type and can cut classified entries to the values present in the data. Layout is delegated to tmap 4.4’sz/group_id/tm_components()rather than reimplemented.gq_tmap_legend()collapses rows that render identically, soroads_dra’s 26 classes become the 8 distinct appearances they actually draw as. A layer named twice therefore yields one entry unless the labels differ — the label is part of the key.Verified against the fish passage reporting originals on real cached rasters: identical bbox padding and scale breaks, and a bit-identical blend across 14.7 million cells.
gq 0.4.0
-
The QGIS-native styles now ship alongside the registry.
inst/styles/carries 60 QML files — 50 shared vector styles, 3 per-template overrides, 1 raster, 6 services — andgq_style_qml(layer_key, template)resolves a key to one.The registry models roughly 20 symbol properties and a single symbol layer, because that is what tmap and mapgl can render; a QML carries everything QGIS authored, including multi-layer symbols, casing and overlay, and per-class dash. Use the registry for tmap and mapgl, and the corpus for anything that speaks to QGIS — Desktop, Mergin field projects, QGIS Server / QWC2, or a
layer_stylestable.Passing a template is always safe: an override wins where one exists, the shared style otherwise, which covers the 50 of 53 layers that do not diverge.
Unlike every other export this returns a file path, so callers get the bytes QGIS wrote without gq re-serializing them.
gq does not lift QML from a
.qgs.data-raw/styles_vendor.Rvendors the artifact rfp already extracts and commits, a dev-only dependency matching the registry extract scripts. See?gq_style_qmland the corpus section ofCLAUDE.md.
gq 0.3.0
-
Themes describe what the templates actually ship.
themes.csvnamed three themes — Field View, Analysis View, UAV View — that exist in no template, at a granularity QGIS does not use. It is now extracted from the templates bydata-raw/reg_extract_themes.Rastemplate,theme,layer_key,visible: 232 rows over the 9 themes the two templates carry.templateis part of the key rather than a filter, because the same theme name carries different content in different templates —High Detail - Crossingsshows 27 layers inbcfishpass_mobileand 0 inbcrestoration_mobile. Breaking:
gq_theme_groups()is replaced bygq_theme_layers(theme, template = NULL). Group-granular rows could not express a theme that discriminates within a group, which is what QGIS themes do.gq_themes()gains atemplateargument and returns the four-column frame.habitat_lateraljoinsgroups.csv. It is a raster, so the vector-onlygq_qgs_extract()never saw it, yet every theme in both templates references it.gq_qgs_extract()now uses the samenormalize_layer_name()helper as name-based lookup instead of its own copy of the rule.
gq 0.2.1
- The four xyz basemaps (
esri_world_topo,bing_aerial,esri_satellite,google_satellite) join the roster inBase - misc. They were modelled nowhere before — baked into the upstream templates only — while the two data services inWeb Mapping Serviceswere already here. Measured against the templates rather than assumed: the six remote layers do not share a group.
gq 0.2.0
-
Trail symbology. The registry carried no trail, path, footway, cycleway or bridleway layer, so a project that added a trail network had nothing to style it with.
trailsis now a classified line layer onhighway— the only tag populated on every feature, wherebicyclesits on 16.4% and would put most features in a fallback class. Classes differ by colour, dash and width, so the map reads in printed greyscale as well as in colour. - The style is authored in a QGIS project and extracted, not hand-written as registry rows. A classified line authored through
reg_custom.csvcomes back with per-classwidthanddashbothNULL, because that path emitsoutline_width/outline_colorwhile the translators readwidth/dash— and dash is exactly what distinguishes a trail from a road. -
data-raw/reg_extract_restoration.RacceptsRFP_TEMPLATE, pointing at a source checkout. Without itsystem.file()resolves to the installed package, which is routinely behind: when this style was extracted the installed copy was three releases old and its template carried no trail layer, so the run would have silently produced a registry missing the layer it was made for.
gq 0.1.0
First release.
Capture the QGIS dash pattern on classified line layers (#32). The extractor previously dropped per-class line style, so dashed classes (e.g. the
;INTERMITTENTstream classes instreams_salmon) came out solid.gq_style()now surfaces a per-classdashesvector,gq_tmap_classes()returns it, andgq_mapgl_style()consumes the exact pattern. The raw QGIS value is stored so each backend renders it as it can (mapgl uses the exactline-dasharray; tmap maps tolty).Add groups, templates, and themes composition layer (#28). Three CSVs in
inst/registry/model how layers compose into QGIS projects — group membership with nesting and z-order, project templates, and visibility themes. Seven new functions:gq_groups(),gq_group_layers(),gq_templates(),gq_template_groups(),gq_template_layers(),gq_themes(),gq_theme_groups(). All 53 registry layer keys mapped to 12 canonical groups.Add
gq_style()— backend-agnostic style resolver with name-based registry lookup. Accepts layer names like"lake"or"Crossings - PSCIS assessment", normalizes to registry keys, returns plain lists of colors, widths, and classification info. No tmap/mapgl dependency.gq_tmap_style()andgq_tmap_classes()now accept name-based lookup:gq_tmap_style(reg, "lake")instead ofgq_tmap_style(reg$layers$lake). Classified layers return fulltm_scale_categorical()wiring — no manual color extraction needed. Backwards compatible with object-based calls.Add Neexdzii Kwa subbasin datasets: habitat, crossings, fish observations, falls.
Rename
gq_reg_read_csv()togq_reg_custom()— describes what it does, not the file format.gq_style(),gq_tmap_style(), andgq_tmap_classes()gain afieldparameter to override the classification field name. Useful when data comes from an alternative source with a different column name (e.g., bcfishpassbarrier_statusvs WHSEbarrier_result_code). No column renames needed in user code.gq_qgs_extract()now handles QGIS grouped categories (#25). Categorized renderers that group multiple values under one symbol (e.g., Highway = RH1 + RH2 + RRP) are expanded into individual class entries. Previously only single-value categories were extracted.Add
inst/registry/xref_layers.csv— cross-reference for layers with alternative data sources and different classification field names.Update composition vignette: all layer colors trace back to
gq_reg_main()viagq_style()andgq_tmap_style(). No hardcoded hex values. Field mismatches handled viafieldparameter — no column renames.
