Skip to contents

min(lon)/max(lon) is the wrong extent for anything that crosses the antimeridian. A North Pacific species with cells at 165 E and 155 W has a -180..180 span of ~360 deg – the whole globe – so fitBounds centres the camera on longitude 0 and the viewer lands off Iceland while the animal lives in the Bering Sea (apps#9). The span that describes it is 165..205.

Usage

lon_span(lon)

lon_span_agg(x0, x1, w0, w1)

Arguments

lon

numeric vector of longitudes in [-180, 180] (non-finite dropped)

x0, x1

min and max longitude in the -180..180 frame

w0, w1

min and max longitude in the 0..360 frame

Value

c(xmin, xmax); c(NA, NA) when nothing is finite

Details

The rule: measure the span in BOTH frames (-180..180 and 0..360) and keep the narrower one. Longitudes are periodic, so the two frames cut the circle at opposite points (0 deg and 180 deg) and a distribution can straddle at most one of them unless it genuinely encircles the globe – in which case both spans are wide and the -180..180 answer is returned unchanged.

The returned xmax may exceed 180. That is deliberate and is what MapLibre wants: map.fitBounds([160, 48, 210, 66]) centres at 185 -> -175 (verified in-browser). Wrapping it back into -180..180 would put west east of east and fit the COMPLEMENT of the intended box.

lon_span_agg() is the same rule applied to aggregates already computed elsewhere – the form the apps use, because the four min/max come back from one DuckDB query over millions of cells and must not be pulled into R just to be reduced. Keeping both forms on one implementation is what stops the SQL path and the vector path from drifting apart.

Examples

lon_span(c(-179, -160, 170, 178))   # 170 205  (Bering Sea, not the globe)
#> [1] 170 200
lon_span(c(-30, 0, 20))             # -30 20   (Atlantic; frame unchanged)
#> [1] -30  20