| Title: | Adaptive Densification of Lines for Map Projections |
|---|---|
| Description: | Densify lines adaptively, as required by the curvature a map projection imposes on them. Edges are read as great circle arcs, and vertices are inserted along them only where the projected curve deviates from a straight chord by more than a tolerance measured on the map, following the adaptive resampling scheme of 'd3-geo' <https://github.com/d3/d3-geo>. Projections are computed with the 'PROJ' library, so any coordinate reference system it supports can be a target. Inputs can be coordinate matrices, segment meshes or vertex pool topology from 'wkpool', and edges shared by neighbouring polygons receive identical new vertices. |
| Authors: | Michael D. Sumner [aut, cre, cph], Simon Wotherspoon [ctb] (wrote SGAT::trackMdpts, the ancestor of the spherical midpoint used here), Mike Bostock [ctb, cph] (author of the 'd3-geo' adaptive resampling method ported here; see inst/COPYRIGHTS) |
| Maintainer: | Michael D. Sumner <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.6.0 |
| Built: | 2026-10-14 13:59:26 UTC |
| Source: | https://github.com/hypertidy/bigcurve |
Insert vertices along great circle arcs exactly where a target projection
bends them, so that straight chords drawn between output vertices stay
within tolerance of the true projected curve. This is the adaptive
resampling scheme of d3-geo (Bostock), generalized to paths and to
segment meshes.
densify( x, target, source = NULL, tolerance = NULL, pixels = 2048L, max_depth = 16L )densify( x, target, source = NULL, tolerance = NULL, pixels = 2048L, max_depth = 16L )
x |
a two-column matrix of lon,lat (a path), a list of such
matrices, a segment mesh (a list with |
target |
projection for which to densify (proj string, WKT, authority code - anything PROJ accepts) |
source |
coordinate system of the input. If |
tolerance |
maximum allowed deviation, in units of |
pixels |
notional output width/height used to derive the default
|
max_depth |
maximum bisection depth per input segment (each level at most doubles the vertex count of a segment) |
The error metric is evaluated in the plane of the target projection:
the great circle midpoint of each candidate segment is forward-projected
and compared to the segment's planar chord (perpendicular distance, plus
a parametric check that catches strong shear). Segments are split
recursively until they pass or max_depth is reached. No inverse
transforms are used and each point is projected exactly once.
Coordinates are returned in the coordinates of source (geographic by
default): project the result yourself for plotting, e.g. with
rproj_xy() for a matrix or, for wk geometry (such as
wkpool::segments_to_wkb() of a refined pool), with wk::wk_transform()
and a PROJ::proj_trans_create() transform.
Points that cannot be projected (for example beyond the horizon of an orthographic projection) do not error: segments touching them are left un-densified, and refinement stops at unprojectable midpoints. Clipping is out of scope here.
Inserted vertices are emitted in the longitude frame of their input
edge: an edge along longitude 180 refines at 180, its mirror at -180
refines at -180 (the two seams of a global layer remain distinct), and
an edge written as crossing the antimeridian yields a locally
continuous chain, possibly outside [-180, 180). Input vertices are
always returned with their written values, so a crossing edge's path
carries one numeric 360-degree step at its far endpoint (the same
point on the sphere; PROJ is indifferent). Longitudes are never
canonicalized; normalize afterwards if a canonical range is needed.
Edges that cross the target projection's own discontinuity (which
depends on lon_0) cannot form a valid planar chain and should be cut
beforehand; refinement of such edges is bounded, not exploded, because
arcs spanning less ground than tolerance are never split.
Every edge is a great circle arc on a sphere, the same reading of an edge that 's2' uses. Data whose edges are meant as straight lines in longitude and latitude describe a different shape, and densifying them here changes it.
Shared edges stay shared. An edge refines to the same vertices whichever
way it is written, bit for bit, so two neighbouring polygons get
identical new vertices along their common boundary (and
wkpool::merge_coincident() fuses them). An edge that appears twice with
the same vertices, as in a merged pool or a segment mesh, is refined
once and its new vertices are reused.
the same kind of object as x, with vertices added: a matrix
for a path, a list of matrices for a list, a mesh with appended
vertices and refined is for a mesh, a wkpool with appended
vertices (freshly minted .vx ids), refined directed segments, and
.feature provenance carried onto every refined segment
https://bost.ocks.org/mike/example/
s <- segment(c(140, -164), c(-89, 80)) d <- densify(s, "+proj=laea +lon_0=147 +lat_0=-42") nrow(d) plot(rproj_xy(d, "+proj=laea +lon_0=147 +lat_0=-42"), type = "l", asp = 1)s <- segment(c(140, -164), c(-89, 80)) d <- densify(s, "+proj=laea +lon_0=147 +lat_0=-42") nrow(d) plot(rproj_xy(d, "+proj=laea +lon_0=147 +lat_0=-42"), type = "l", asp = 1)
Lambert azimuthal equal area projection string, in old-style PROJ format.
laea(lon_0 = 0, lat_0 = 0, x_0 = 0, y_0 = 0)laea(lon_0 = 0, lat_0 = 0, x_0 = 0, y_0 = 0)
lon_0 |
centre longitude |
lat_0 |
centre latitude |
x_0 |
false easting (default 0 is fine) |
y_0 |
false northing (default 0 is fine) |
Note that easting/northing are poor names for false X and Y offsets, but that is what they are called in PROJ.
character string, projection
laea() laea(147, -42)laea() laea(147, -42)
Forward-transform a two-column matrix of coordinates with PROJ. Authority codes are axis-normalized, so input is always x,y (lon,lat) order and output is easting,northing - even for 'EPSG:4326'.
rproj_xy(x, target, ..., source = NULL)rproj_xy(x, target, ..., source = NULL)
x |
two-column matrix of coordinates in |
target |
output coordinate system (anything PROJ accepts) |
... |
ignored |
source |
input coordinate system, default 'EPSG:4326' (axis-normalized, so input is lon,lat regardless of authority order) |
Points outside the domain of target are returned as NA rather than
raising an error.
two-column matrix of transformed coordinates
rproj_xy(cbind(147, -42), "+proj=laea +lon_0=147 +lat_0=-42")rproj_xy(cbind(147, -42), "+proj=laea +lon_0=147 +lat_0=-42")
A two-point path as a two-column matrix, by default a random one.
segment(x = runif(2L, -180, 180), y = runif(2L, -90, 90))segment(x = runif(2L, -180, 180), y = runif(2L, -90, 90))
x |
longitude (2, start point, end point) |
y |
latitude (2, same as x) |
two-column matrix
segment() segment(c(0, 147), c(0, -42))segment() segment(c(0, 147), c(0, -42))