Breaking changes

  • intersect_mask_filter_area() — clipped now returns one row per kept x_id by default (dissolve = TRUE): fragments produced when x and y have misaligned boundaries (for example two administrative meshes from different vintages) are unioned into a single feature instead of being left as separate slivers. Pass dissolve = FALSE to recover the previous one-row-per-fragment behaviour. Also, y is now intersected as geometry only, so its attributes no longer leak into clipped and no longer collide with x’s attribute names (#8). Fixed a bug in this same change where a GEOMETRYCOLLECTION decomposing into more than one polygon during dissolve was assigned back into the clipped sfc by position, silently recycling and mixing fragments across unrelated x_ids (#10).

New helpers

  • clean_geo() — reads a .zip/.shp/.gpkg/.geojson, drops Z/M dimensions, replaces non-ASCII characters in attribute columns, reprojects to a target CRS (default EPSG:4326) and writes the result to a user-provided output path. The output format is determined by the extension of output and may differ from the input format. Replaces the standalone R/prepare_zip_shapefiles.R cleanup routine with a portable, testable function backed by zip::zip() instead of system("zip -j ...").
  • fix_invalid_geometries() — reports rows with invalid geometries (via sf::st_is_valid()) and repairs them with sf::st_make_valid(), preserving attributes and the geometry column name; valid layers are returned unchanged.
  • intersect_filter_touch() — returns the rows of x whose geometries touch any feature in the mask y, using sf::st_intersects() only. No reprojection, clipping, or area computation is performed: geometries and attributes of x are returned unchanged. The lightest-touch selection strategy, condensed from the common st_intersects() |> lengths() |> { . == 1 } idiom.
  • remove_geom_holes() — removes polygon holes, optionally keeping holes at or above a configurable max_area; adapted from nngeo under its MIT license.
  • serve_html() / serve_map() / serve_html_stop() — serve HTML widgets and rendered reports over HTTP so they can be opened in a browser on another device. Accepts mapview maps, any htmlwidget (leaflet, plotly, DT, gt), sf objects, and .html files already on disk; an on-disk report is mounted together with its sibling asset directory instead of being copied. One static server is reused per R session, so every URL handed out stays valid and pages can be compared in separate tabs. Hostname resolution is shared with serve_plots().
  • serve_plots() / serve_plots_url() — serve R plots over HTTP via httpgd so they can be viewed in a browser on another device (for example an iPad running a terminal over SSH or mosh). The advertised hostname is resolved from MISC_PLOTS_HOST, options(misc.plots_host), or the Tailscale MagicDNS name, replacing the local mDNS name that httpgd reports and that does not resolve over a VPN. Binds a free port by default to avoid clashing with other local servers.
  • view_qgis() — opens sf objects in QGIS on macOS, with GeoPackage or GeoJSON output and QGIS selection through MISC_QGIS_APP or options(misc.qgis_app).

Improvements

  • read_geo(), read_gdb(), read_kmz() and read_sf_zip() gain a by_bbox argument that reads only the features intersecting a bounding box (an sf, sfc, bbox or named numeric), filtered by GDAL at read time via wkt_filter. The bounding box is reprojected to each layer’s CRS; layers without a geometry column (e.g. a .dbf-only shapefile or a GeoPackage attribute table) are read in full with a warning, as are coordinates on layers whose CRS is missing or cannot be reprojected to (e.g. an undefined LOCAL_CS .prj). Requires sf >= 1.0-17.

Fixes

  • clean_geo() now normalizes the output path to an absolute path (via fs::path_abs()), matching the behavior already applied to path. Relative output paths (e.g. "data/clean/x.zip") previously failed when writing .zip output with zip::zip(); all output formats now resolve correctly against the current working directory.
  • clean_geo() now also accepts an in-memory sf object as path, so a layer read and pre-filtered with read_geo() can be written out directly (e.g. sf %>% clean_geo(output = "x.zip")) without round-tripping through a file.
  • quick_map() now defaults to type = "sf" when only region is given, instead of failing with an obscure object 'plot_map' not found error, and rejects unknown type values with a clear message. It also uses ggplot2::annotation_borders() when available (ggplot2 >= 4.0.0), avoiding the borders() deprecation warning.

View helpers (R/view_in.R)

  • view_vd() and view_vd_nonint() are explicitly macOS-only: on Windows or Linux they stop with a clear error. Documentation describes this platform restriction.
  • New terminal argument: "terminal" (Terminal.app, default), "iterm" (iTerm2), or "auto". For "auto", the choice comes from the MISC_VIEW_TERM environment variable and then from options(misc.view_term) ("terminal" or "iterm"); invalid values trigger a warning and fall back to Terminal.app.
  • AppleScript-based terminal launch is unified (new window or tab in iTerm2 as appropriate).
  • The vd (VisiData) executable must be on PATH before opening the viewer; the error message points to installation (for example pip install visidata).
  • If vdk is on PATH, the command used is vdk <project_basename> <file>; otherwise vd --default-width=500 <file>. In view_vd_nonint(), the project name uses basename(here::here()), consistent with view_vd().
  • view_mapview_from_path() performs the macOS check up front because the workflow relies on view_vd_nonint() for the attribute table; documentation updated.