Generates a field form's two artifacts from its git-diffable schema CSV: an
empty typed GeoPackage and a QGIS .qml sidecar carrying the field aliases,
edit widgets, tabbed form layout and symbology. This is the maintainer-side
counterpart to rfp_form_create(), which copies the already-built GeoPackage
into a project.
Usage
rfp_form_build(
type,
path_out,
schema = NULL,
layer = NULL,
dir_photos = "ignore_mobile/photos",
crs = 3005,
symbol = NULL,
color = NULL,
label_expression = NULL,
on_records = c("stop", "migrate", "overwrite")
)Arguments
- type
Character. Form type, e.g.
"vri_qa". Must be registered inrfp_form_types.csvwith aschema_source. Seerfp_form_types().- path_out
Character. Output path for the form GPKG. The QML is written beside it with the same basename.
- schema
Character. Optional path to a schema CSV, overriding the registry's
schema_source. Use when drafting a form that is not registered yet.- layer
Character. Layer name inside the GPKG. Defaults to
form_<type>.- dir_photos
Character. Project-relative directory photo widgets store into. Default
"ignore_mobile/photos", matching the existing rfp and dff-2022 forms.- crs
Numeric. EPSG code for the form layer. Default
3005(BC Albers).- symbol
Character. Optional symbol template name, overriding the registry. See
inst/extdata/forms/symbols/.- color
Character. Optional hex colour (e.g.
"#E31AC8"), overriding the registry.- label_expression
Character. Optional QGIS label expression, overriding the registry's
label_expression.""labels nothing.- on_records
Character. What to do when
path_outalready holds records and the schema has changed - which replaces the whole file."stop"(the default) refuses, naming the row count."migrate"carries the records into the new schema, matching on column name, leaving added columns null and reporting anything dropped."overwrite"replaces the file and its records. Ignored when the schema is unchanged, since nothing is written.
Details
Everything a form needs is data, never code. Per-field configuration
(type, widget, tab, label, dropdown options, default) lives in the schema CSV;
per-form configuration (schema path, geometry, symbol, colour) lives in
the registry read by rfp_form_types(). Adding a form, or restyling one,
should require no change to this function.
The GeoPackage is only rewritten when its columns, geometry type or CRS actually differ from the schema. An unconditional write would churn a ~100 KB binary in git on every rebuild, because GeoPackage embeds a creation timestamp. The QML is always rewritten - it is text, and byte-stable for a given schema.
Rebuilding replaces the whole file rather than altering the layer in place,
so when the schema has changed and path_out holds anything else - another
layer, or the layer_styles table QGIS writes - the build stops rather than
discarding it.
The same replacement would destroy the form's own records. A deployed form is
not only a build artifact: once rfp_form_create() has put it in a project
and Mergin has synced it, it is the surface a field crew captures onto. So a
schema change over a form that holds records stops by default, and
on_records says what to do instead - carry the records into the new schema,
or discard them deliberately. A form with no records rebuilds silently either
way, which is every form rfp ships (#49).
See also
rfp_form_types() for the registry, rfp_form_create() to deploy a
built form into a project.
Other form:
rfp_form_create(),
rfp_form_types()
Examples
# Build the land cover transition QA form into a temporary directory
out <- file.path(tempdir(), "form_transition_qa.gpkg")
rfp_form_build("transition_qa", out)
#> ✔ Built transition_qa form: /tmp/RtmpfSNDuG/form_transition_qa.gpkg (20 fields, /tmp/RtmpfSNDuG/form_transition_qa.qml)
# The typed, empty layer...
sf::st_layers(out)
#> Driver: GPKG
#> Available layers:
#> layer_name geometry_type features fields crs_name
#> 1 form_transition_qa Point 0 20 NAD83 / BC Albers
# ...and its QML sidecar, carrying widgets, tabs and symbology
qml <- sub("\\.gpkg$", ".qml", out)
length(xml2::xml_find_all(xml2::read_xml(qml), "//attributeEditorContainer"))
#> [1] 3
unlink(c(out, qml))
