All three map functions drew the arrowhead on the southern end of the shaft, so the arrow under the “N” pointed down. It now points north – to true north, read from the map’s coordinate system at the sampling point, so it stays right in a projection whose grid is not aligned with the meridians. In UTM the difference from grid north is the meridian convergence, about 0.7 degrees at Maré and invisible at figure scale; in Web Mercator, used under Esri imagery, it is zero.
A hatch line crossing a concave polygon more than once kept only its first piece. Textures therefore covered only part of the polygons they belonged to – most visibly the grey hatching of unclassified ground, which is almost always concave – and every map emitted a run of “keeping first linestring only” warnings. Every piece is now drawn. This affected drawing only: areas, weights, counts and distances were never involved.
batch_composition() printed, for each file, the coverage
of its first site at the smallest radius – 20 m with
the default radii – while the maps, the summary sheet and the end-of-run
coverage check all use the largest. A file with three sites showed one.
Every site is now reported, at the primary radius:
[ok] SITE_1eSITE_2 @ 50 m : SITE_1 51.6% classified, 37 polygons,
21 tanks (1 unsealed) | SITE_2 36.1% classified, 27 polygons, ...
Coverage figures read from earlier logs were therefore 20 m figures for the first site of each file. The workbook was always correct.
plot_site_composition() reported “of 29 categories”
whatever dictionary was in use; it now counts the dictionary actually
used.citation("bufferscape") takes the version from
DESCRIPTION, so it can no longer drift from the
release.A surface traced a second time counted twice, so a buffer that should read near 100% classified could report 191%. Polygons with identical geometry are now collapsed to one on read. Genuine overlap – a tree crown over a roof – is different geometry and is untouched.
Which copy survives depends on what it carries: if only one has a class code that one is kept; if the codes conflict the first is kept and the conflicting pair is named, because silently choosing between two interpretations would hide a decision the interpreter should make.
A new coverage_flag column marks any site still above
120% after that, so duplication the geometric test cannot see is
reported rather than passed on quietly. Coverage above 100% remains
legitimate and is not flagged.
Several classes were too close to tell apart, most visibly main thoroughfare against formal parking. Every pair is now either at least 14 CIELAB units apart or separated by a texture; measured across all 378 pairs, none is ambiguous.
Where two classes genuinely look alike from above, the texture carries meaning: a grid for parking bays, vertical strokes for the deep shadow of an alley, horizontal strokes for the linear flow of a channel, a fine diagonal for a finished slab. Eight of the 28 classes now carry a texture, up from four.
Class labels were cut at 32 characters, losing the end of names such as “Fibre-cement or asbestos sheeting”. The column is wider and labels now run to 40 characters, which covers the longest name in the schema.
“unsealed tank (oviposition risk)” is now “apparently unsealed tank”, and “sealed tank” is “apparently sealed tank”. What aerial imagery supports is an appearance, not a determination: a lid is not always visible from above, and the figures should not claim more than the method can deliver.
Four failures reported from a 44-file field run, all fixed. Two of them could end a long run with nothing usable on disk.
TopologyException and the whole file was abandoned.
Geometry is now repaired on read and again after reprojection, and an
overlay that still fails is retried on repaired inputs, then at reduced
precision, then polygon by polygon. Only genuinely irreparable polygons
are skipped, and they are reported.pivot_wider()
returned list-columns, and writexl refused the lot –
discarding every table at the end of the run. Such sites are now kept
separately as ID__file, matching how their maps are already
named. As a backstop the writer flattens anything non-atomic, and if the
workbook still cannot be written the tables are saved as CSV.mare_categories previously held 29 classes in 12 groups.
The article and protocol define 28 classes in nine thematic
groups, with class 29 (PVC roofing) removed once the
nomenclature stabilised and class 24 revised. The dataset is now
identical to Table 1: same identifiers, same English labels, same
grouping. Identifiers stayed contiguous, so no surviving class was
renumbered.
This changes output column names. Columns are built
as id_category_description, and category now
carries the nine thematic groups (roofing,
vegetation, circulation_parking, …) rather
than the earlier twelve Portuguese labels. Analyses that referred to the
old column names must be updated. Areas, weights and counts are
unchanged.
Any data still coded with class 29 will be reported as an unrecognised code rather than silently dropped, which is the intended behaviour.
buffer_composition() and
batch_composition() now read GeoPackage as
well as KML. The format is detected from the file’s contents rather than
its extension, and a folder may mix the two. A file may contain one site
or several in either format; nothing needs to be configured.focal_points and
landcover layers, and optionally buffers,
water_containers, poi_lines and
class_dictionary. Anything else falls back to the flat
reader used for KML.NH_6 rather than NH_6_Buffer) was treated as
an unclassified land-cover polygon and dropped with a warning. It is now
recognised as reference geometry, so the buffer cross-check works on
these files.bufferscape::class_dictionary() failed with “object
‘mare_categories’ not found” unless the package had been attached with
library(). Lazy-loaded data is not on the namespace search
path; the lookup is now explicit.batch_composition()
were missing the _d_nearest_m proximity columns.buffer_composition(kml = ) is now path =,
and batch_composition(kml_dir = ) is now
dir =, because neither is KML-specific any more. The old
names still work and warn.batch_composition(): requesting a
basemap with make_maps = TRUE failed with “could not find
function basemap_ready”. The helper that checks whether the imagery
packages are installed had been lost in a refactor while its call site
remained.R CMD check no longer reports undefined globals.batch_composition() now goes
through message() rather than cat(), so a run
can be silenced with suppressMessages(). CRAN policy asks
that packages not write to the console in a way the user cannot
suppress.README.md,
CITATION.cff and inst/CITATION, so
citation("bufferscape") returns it.DESCRIPTION,
CITATION.cff and inst/CITATION now match the
release tag; 1.0.0 was tagged while the package still declared
0.1.0.CITATION.cff, so the repository exposes citation
metadata to GitHub and to archives such as Zenodo.First release.
buffer_composition() computes per-class surface area
within buffers around point locations, directly from vector polygons. It
returns the exact area, a distance-decay weighted area in which the
kernel is integrated over polygon geometry
(area_w), the centroid-weighted equivalent for comparison
(area_w_centroid), and the distance to the nearest patch of
each class (d_nearest_m).decay_kernel() provides bounded exponential and
Gaussian kernels.batch_composition() processes a folder, writing a
workbook and figures. A malformed file is logged and skipped rather than
stopping the run.class_dictionary()
accepts a data.frame or an
.xlsx/.csv path;
validate_dictionary() checks one before a long run. The
29-class Maré schema ships as mare_categories.parse_class_codes() keeps hyphenated codes such as
"8-6", which naive coercion would silently drop.centroid_bias() and
summarise_centroid_bias() quantify, polygon by polygon, how
far centroid weighting departs from the exact integral, with shape
descriptors and a centroid_inside flag for concave features
whose centroid falls outside the polygon.map_composition() draws the land-cover map;
map_basemap() and map_combined() add satellite
imagery.plot_site_composition() and
plot_group_points() give per-site composition bars and
per-group point-feature bars.class_palette() provides four schemes:
"aerial" (appearance-matched), "colorblind"
(one colour-vision-safe hue per group, members separated by lightness
and texture), "viridis" and "greyscale".
resolve_palette() also accepts a palette table or a named
colour vector.write_composition_report() writes the workbook, with
control over radii, which surface metrics to include, which sheets,
label style and rounding.