Technical notes
The shared pipeline
Every command implements one small trait (declare the output columns, compute
their values for one location) and shares the rest: extract longitude and
latitude (cast to float, nulls to NaN), round and de-duplicate into unique
locations with integer-scaled keys (so the join never compares floats), enrich
the unique set in parallel with rayon, expand the results back to one value per
input row, append the columns, and write. A NaN coordinate gets no key and
therefore null output.
Geometry: no PROJ, no GDAL
All geometry is hand-rolled in pure Rust, so downstream projects need no extra system libraries. Two projections do the work:
- A spherical LAEA (Lambert Azimuthal Equal-Area) centered on the region,
used for planar distances by
coast(and for the nearest-boundary fallbacks inseaandplace). Planar distance in that projection is accurate for the regional-scale nearest-feature query. The reference R workflow used EPSG:3035 LAEA for the same reason. A single sphere (authalic radius) is used rather than the GRS80 ellipsoid; the error is well under coastline resolution for regional work. Sub-meter accuracy, if ever needed, means an ellipsoidal LAEA. - The unit sphere, used by
nearest. Reference points become(x, y, z)vectors on the unit sphere; nearest-by-chord equals nearest-by-great-circle, and the squared chord converts back to an exact great-circle distance in meters. This is whynearestneeds no projection center and has none of a planar projection’s distortion far from a center.
The haversine_m great-circle distance is used for reference and to refine
index candidates.
Spatial indexes
Nearest-feature and point-in-polygon queries use rstar R-trees:
coastindexes projected shoreline segments; a query takes the nearest segment’s planar distance.seaandplaceindex feature bounding boxes for the containment test and boundary segments for the nearest fallback.nearestindexes reference points in 3D on the unit sphere.
Cropping keeps whole features: a feature is dropped if its bounding box misses the region-plus-margin box, but it is never clipped, so containment stays exact and cropping cannot invent geometry.
Memory and streaming
The input is read whole into memory because the join back touches every row; the enrichment set itself is only the unique locations, which stays small. For very large inputs a streamed two-pass version (collect unique locations, then append columns chunk by chunk) is the natural next step, noted in the source.
Parquet writes
Parquet is written single-threaded (set_parallel(false)), matching ctddump:
the parallel column encoder in the pinned Polars version leaks memory per call,
and the single-thread path is safe and deterministic.