<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>hypertidy</title>
<link>https://hypertidy.org/</link>
<atom:link href="https://hypertidy.org/index.xml" rel="self" type="application/rss+xml"/>
<description>Geospatial data, GDAL, and R — Michael Sumner</description>
<generator>quarto-1.10.18</generator>
<lastBuildDate>Thu, 01 Oct 2026 00:00:00 GMT</lastBuildDate>
<item>
  <title>What R does today with an ALTREP that has a dim and no class</title>
  <dc:creator>Michael Sumner</dc:creator>
  <link>https://hypertidy.org/posts/2026-10-01_altrep-dim-no-class/</link>
  <description><![CDATA[ 






<p>Base R can already read a remote, chunked, compressed array in planned batches, using nothing but its own indexing syntax. The object has no class and no methods: it is a double vector with a <code>dim</code> attribute. One of R’s indexing forms reaches it as a single planned request today; another reaches it one element at a time; and a small change to R would close that gap.</p>
<p>I wanted to know how far R’s old and very rich array indexing could go as a query language for remote encoded chunks, without patching R. So I built <a href="https://github.com/hypertidy/altarr"><code>altarr</code></a>, a small ALTREP prototype, read through <code>src/main/subset.c</code> to see what each indexing form actually does, and measured it. Then I pointed it at a real Zarr store written by zarr-python.</p>
<p>The short version: <code>x[cbind(i, j, k)]</code> is already first-class. <code>x[i, j, k]</code> works, but serially. And the information R would need to fix that is already sitting in a local variable inside <code>ArraySubset</code>.</p>
<section id="two-kinds-of-array-question" class="level2">
<h2 class="anchored" data-anchor-id="two-kinds-of-array-question">Two kinds of array question</h2>
<p>There are two different things you can ask of a 3-d array with three index vectors. The <em>orthogonal</em> question asks for every combination: a hyperslab, the outer product of the indices. The <em>pointwise</em> question pairs the indices up: the first i with the first j with the first k, giving one value per point. A chunked store cares a lot about which one you mean, because they touch completely different sets of chunks.</p>
<p>R and numpy chose opposite defaults:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th></th>
<th>R</th>
<th>numpy</th>
<th>zarr-python</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>orthogonal (hyperslab)</td>
<td><code>x[i, j, k]</code></td>
<td><code>x[np.ix_(i, j, k)]</code></td>
<td><code>z.oindex[i, j, k]</code></td>
</tr>
<tr class="even">
<td>pointwise (points)</td>
<td><code>x[cbind(i, j, k)]</code></td>
<td><code>x[i, j, k]</code> with integer arrays</td>
<td><code>z.vindex[i, j, k]</code></td>
</tr>
</tbody>
</table>
<p>Zarr grew two named accessors, <code>oindex</code> and <code>vindex</code>, to say what S and R have said in base syntax for decades. xarray meets the same split as orthogonal versus vectorized <code>isel</code>. I checked the table against a real store: on the Zarr array used later in this post, <code>z.vindex[[29, 14], [49, 24], [69, 34]]</code> and R’s <code>x[cbind(c(70, 35), c(50, 25), c(30, 15))]</code> both return <code>104999 50714</code>, and <code>z.oindex</code> returns the same 8 values as R’s <code>x[i, j, k]</code>. (The R indices are reversed and 1-based; more on why below.)</p>
<p>Matrix indexing is one of the least appreciated parts of R. It long predates lazy evaluation in most of the places people now care about it, and as it turns out, it is exactly the form that reaches a lazy array as a single, plannable request.</p>
</section>
<section id="what-r-does-with-it" class="level2">
<h2 class="anchored" data-anchor-id="what-r-does-with-it">What R does with it</h2>
<p>ALTREP (since R 3.5) lets a package supply a vector whose contents come from somewhere other than a block of memory. The package registers methods: <code>Length</code>, <code>Elt</code> for one element, <code>Extract_subset</code> for a set of indices, <code>Dataptr</code> for a pointer to all the data. R’s own code calls them instead of reading memory. To R, the result is just a vector.</p>
<p>So what happens when that vector has a <code>dim</code> attribute and you index it? Reading <code>src/main/subset.c</code> (R 4.3 and current trunk have the same structure), there are three paths:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>you write</th>
<th>R calls</th>
<th>the ALTREP object sees</th>
<th>can it plan?</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>x[i]</code></td>
<td><code>VectorSubset</code> then <code>ExtractSubset</code></td>
<td><code>Extract_subset(x, indx)</code>, every index at once</td>
<td>yes</td>
</tr>
<tr class="even">
<td><code>x[cbind(i, j, k)]</code></td>
<td><code>mat2indsub</code> then <code>ExtractSubset</code></td>
<td><code>Extract_subset(x, indx)</code>, every index at once</td>
<td>yes</td>
</tr>
<tr class="odd">
<td><code>x[i, j, k]</code></td>
<td><code>ArraySubset</code> or <code>MatrixSubset</code></td>
<td><code>Elt(x, offset)</code>, one element at a time</td>
<td>no</td>
</tr>
<tr class="even">
<td><code>x + 1</code>, <code>print(x)</code></td>
<td>anything needing the data pointer</td>
<td><code>Dataptr(x)</code></td>
<td>must materialize</td>
</tr>
</tbody>
</table>
<p>Matrix indexing is converted to linear indices before it reaches ALTREP, but the whole set arrives in one call. That is enough: the object can recover each index’s chunk, find the unique chunks, and fetch them together.</p>
<p>The rectangular form is the surprise. <code>ArraySubset</code> normalizes each subscript with <code>int_arraySubscript()</code> and holds the result in a local array, <code>subs[k]</code>: one integer vector per dimension. That is the hyperslab, exactly. But it never offers <code>subs</code> to ALTREP. It loops over the result in column-major order and calls <code>REAL_ELT()</code> once per element.</p>
</section>
<section id="altarr-the-experiment" class="level2">
<h2 class="anchored" data-anchor-id="altarr-the-experiment">altarr: the experiment</h2>
<p><code>altarr</code> is a small R package with a C core. <code>altarr(dim, chunk, fetch)</code> returns a lazy array: a double vector, a <code>dim</code> attribute, no class. Values arrive chunk by chunk from a fetch function you supply, so the store behind it can be object storage, Zarr, GDAL, or a generator in a test.</p>
<p>The whole contract with the store is one function:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">fetch</span>(chunks)</span>
<span id="cb1-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># chunks:  integer matrix, one row per chunk, one column per dimension,</span></span>
<span id="cb1-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#          0-based chunk coordinates</span></span>
<span id="cb1-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># returns: list of numeric vectors, one per row, column-major,</span></span>
<span id="cb1-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#          clipped at the array edge</span></span></code></pre></div></div>
<p>A call to <code>fetch</code> is one round trip, however many chunks it asks for. The ALTREP methods do the rest:</p>
<ul>
<li><code>Elt</code> finds the element’s chunk, fetches it alone if it is not cached, and returns one value.</li>
<li><code>Extract_subset</code> maps every index to its chunk, fetches all the uncached chunks in one call, then assembles.</li>
<li><code>Dataptr</code> materializes the whole array, and refuses above <code>getOption("altarr.max_materialize", 1e6)</code> values.</li>
</ul>
<p>Here is a lazy 1440 x 720 x 365 array (378 million values, never allocated) from a generator with 20 ms of latency per call:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(altarr)</span>
<span id="cb2-2">d  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1440</span>L, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">720</span>L, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">365</span>L)</span>
<span id="cb2-3">cs <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>L, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>L, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>L)</span>
<span id="cb2-4">remote <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(chunks) {</span>
<span id="cb2-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Sys.sleep</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.02</span>)                     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># one round trip</span></span>
<span id="cb2-6">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">lapply</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq_len</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nrow</span>(chunks)), <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(r) {</span>
<span id="cb2-7">    st <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> chunks[r, ] <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> cs <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L</span>
<span id="cb2-8">    en <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">pmin</span>(st <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> cs <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L, d)</span>
<span id="cb2-9">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.vector</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">outer</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">outer</span>(st[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span>en[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e4</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (st[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span>en[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+"</span>),</span>
<span id="cb2-10">                    <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e8</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (st[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span>en[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>]), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+"</span>))</span>
<span id="cb2-11">  })</span>
<span id="cb2-12">}</span>
<span id="cb2-13">x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">altarr</span>(d, cs, remote)</span>
<span id="cb2-14"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">class</span>(x)           <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># "array"</span></span>
<span id="cb2-15"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">is.object</span>(x)       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># FALSE</span></span>
<span id="cb2-16"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">altarr_stats</span>(x)    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># counts Elt calls, fetch calls, chunks</span></span></code></pre></div></div>
<p><code>altarr_stats()</code> counts what R actually asked for, which is how the numbers below were measured.</p>
</section>
<section id="measured" class="level2">
<h2 class="anchored" data-anchor-id="measured">Measured</h2>
<p>On the 378-million-value array, the planned paths read the same chunks as the element-at-a-time paths in a single round trip, and are 20 to 40 times faster.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://hypertidy.org/posts/2026-10-01_altrep-dim-no-class/round-trips.svg" class="img-fluid figure-img" alt="Paired bars. Region 401 x 301 x 31: x[i, j, k] element at a time 4.38 s and 192 fetch calls; altarr_extract planned 0.21 s and 1 fetch call. 2000 scattered points: loop of x[i, j, k] 6.01 s and 287 fetch calls; x[cbind(i, j, k)] planned 0.15 s and 1 fetch call."></p>
<figcaption>Seconds to read the same chunks, element at a time versus planned. Measured with <code>inst/examples/three-paths.R</code> in altarr, R 4.3.3, simulated 20 ms latency per fetch call.</figcaption>
</figure>
</div>
<p>The latency stands in for an object-store GET, with a batch of chunks assumed to be fetched concurrently. A linear slice, <code>x[1:1e5]</code>, also goes through <code>Extract_subset</code>: 48 chunks in 1 call. Point extraction through <code>x[cbind(...)]</code> is already first-class in unpatched R; the rectangular form is correct and lazy, but serial.</p>
</section>
<section id="a-real-zarr-store" class="level2">
<h2 class="anchored" data-anchor-id="a-real-zarr-store">A real Zarr store</h2>
<p>A generator proves the mechanics; a real store proves the contract is enough. zarr-python wrote a Zarr v2 array of shape (30, 50, 70), chunks (7, 16, 20), zlib-compressed, C order. The chunk shape is ragged at every edge, and one chunk is entirely fill value, so the store simply omits its file.</p>
<p><code>altarr_zarr_v2()</code> reads <code>.zarray</code> and implements <code>fetch</code> in about 85 lines of plain R: build the chunk key, read the file (or return <code>fill_value</code> if it is missing), <code>memDecompress()</code>, <code>readBin()</code>, and clip Zarr’s padded edge chunks.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1">z <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">altarr_zarr_v2</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"zdemo.zarr"</span>)</span>
<span id="cb3-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dim</span>(z)                                        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 70 50 30</span></span>
<span id="cb3-3">z[<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cbind</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">70</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">35</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">50</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">25</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">30</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">15</span>))]     <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 104999 50714, one fetch call</span></span>
<span id="cb3-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">altarr_extract</span>(z, , , )                       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># every value, one fetch call</span></span></code></pre></div></div>
<p>Every value matches zarr-python’s array exactly, including the omitted chunk and the clipped edges.</p>
<p>The dimensions come out reversed, and that is deliberate. A C-order array of shape (s1, …, sn) has bytes that are already column-major for shape (sn, …, s1). So <code>altarr</code> gives R <code>dim</code> <code>c(70, 50, 30)</code> and decodes each chunk straight into place, with no transposition at all. R’s <code>x[i, j, k]</code> on that array is zarr’s <code>z.oindex[k, j, i]</code>, shifted by one.</p>
</section>
<section id="a-real-dataset-with-gdal-doing-the-hard-parts" class="level2">
<h2 class="anchored" data-anchor-id="a-real-dataset-with-gdal-doing-the-hard-parts">A real dataset, with GDAL doing the hard parts</h2>
<p>The zarr-python store keeps the package self-contained. For real data, the classic xarray tutorial <code>air_temperature</code> dataset is published as Zarr over HTTP, compressed with Blosc. A Blosc decoder and an HTTP range-reader are exactly the things <code>altarr</code> should not grow, so GDAL does both in one command:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode sh code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">gdal</span> mdim convert <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb4-2">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ZARR:</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">/vsicurl/https://scottyhq.github.io/zarrdata/air_temperature.zarr/</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb4-3">  air_temperature.zarr <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--co</span> FORMAT=ZARR_V2 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--co</span> ARRAY:COMPRESS=ZLIB</span></code></pre></div></div>
<p>The result is a local, zlib-compressed Zarr v2 store, and the same reader opens it:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1">x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">altarr_zarr_v2</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"air_temperature.zarr/air"</span>)</span>
<span id="cb5-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dim</span>(x)                 <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 53 x 25 x 2920: lon, lat, time</span></span>
<span id="cb5-3">                       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># int16, chunks 27 x 13 x 730, 16 of them</span></span>
<span id="cb5-4"></span>
<span id="cb5-5">x[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, ]            <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># a time series at one cell, in kelvin</span></span>
<span id="cb5-6">                       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># orthogonal: Elt path, 4 chunks in 4 round trips</span></span>
<span id="cb5-7"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">altarr_extract</span>(x, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, )      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># the same 4 chunks in 1 round trip</span></span>
<span id="cb5-8"></span>
<span id="cb5-9">x[<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cbind</span>(lon_i, lat_i, time_i)]   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># a track, one time per position</span></span>
<span id="cb5-10">                                 <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># pointwise: one planned batch</span></span></code></pre></div></div>
<p>Those are the two questions from the start of this post, on real data. The time series sits under one cell, so it touches the 4 time chunks there: 4 round trips today, 1 with the hook. A 500-point track spread over the year touches all 16 chunks, and already arrives as a single request.</p>
<p><code>air</code> is stored as int16 with a scale factor of 0.01. <code>altarr_zarr_v2()</code> applies the CF packing attributes from <code>.zattrs</code> (<code>scale_factor</code>, <code>add_offset</code>, <code>_FillValue</code>) inside <code>fetch</code>, so values arrive in kelvin and fill values become <code>NA</code>. Unpacking is a concern of the fetch, not of the ALTREP layer.</p>
<p>This is also the point to resist building a Zarr engine. Remote I/O, Blosc, sharding, Zarr v3 codecs and their combinations are a never-ending road, and every step along it is a detour from the question here. The fetch contract exists so the engine can be someone else’s: GDAL as a converter in this example, and the natural next step is a <code>fetch</code> that calls GDAL’s multidimensional API, or object_store and the Rust codec crates, directly. The ALTREP layer only needs chunk coordinates in and values out.</p>
</section>
<section id="what-comes-for-free" class="level2">
<h2 class="anchored" data-anchor-id="what-comes-for-free">What comes for free</h2>
<p>Because the object is an ordinary vector to R, a lot of behaviour arrives without any code:</p>
<ul>
<li><strong>Shape is free.</strong> <code>dim()</code>, <code>length()</code> and <code>str()</code> read nothing.</li>
<li><strong>Copy-on-modify works.</strong> <code>y &lt;- x; y[1, 1, 1] &lt;- 0</code> materializes <code>y</code> only; <code>x</code> stays lazy and unchanged.</li>
<li><strong>R’s own wrapper keeps the planning.</strong> Changing an attribute on a shared object (<code>dimnames(y) &lt;- ...</code> after <code>y &lt;- x</code>) makes R wrap it in its internal <code>wrapper</code> ALTREP class. The wrapper forwards <code>Extract_subset</code>, so matrix indexing still arrives as one planned request.</li>
<li><strong>Serialization stores the recipe, not the payload.</strong> ALTREP has a <code>Serialized_state</code> method, so <code>saveRDS()</code> of the 378-million-value array writes 1250 bytes: dim, chunk shape and fetch function. For the Zarr array it is 3.7 KB, and <code>readRDS()</code> in a fresh R session loads the package and returns a lazy array that reads from the store on first use.</li>
<li><strong>The plan is a table.</strong> <code>altarr_plan(x, i, j, k)</code> returns the chunks a subscript would touch, one row per chunk, with 0-based chunk coordinates, starts and extents. The plan is a table, and the array is what assembling it gives you.</li>
</ul>
<p>That last point connects to an older thread. A chunk manifest (Icechunk’s, kerchunk’s, a COG’s tile index) is already a table of chunk keys to byte ranges. Chunk extents are min/max statistics for free, so filtering that table on coordinates is chunk pruning without anyone computing statistics.</p>
</section>
<section id="the-hook-r-could-add" class="level2">
<h2 class="anchored" data-anchor-id="the-hook-r-could-add">The hook R could add</h2>
<p><code>ExtractSubset</code> already does this for vectors: if the object is ALTREP, it offers the indices to the class’s <code>Extract_subset</code> method first, and falls back to the element loop only if the method returns <code>NULL</code>. The same pattern would fit in <code>ArraySubset</code> and <code>MatrixSubset</code>, just after the subscripts are normalized and bounds-checked:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode c code-with-copy"><code class="sourceCode c"><span id="cb6-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>ALTREP<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">))</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-2">    SEXP ans <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ALTVEC_EXTRACT_ARRAY_SUBSET<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>x<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> s<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> call<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">);</span>  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* s: per-dim subs */</span></span>
<span id="cb6-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">(</span>ans <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> NULL<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-4">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* attach dim and dimnames exactly as the existing code does */</span></span>
<span id="cb6-5">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb6-6"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb6-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/* otherwise the element loop runs, as today */</span></span></code></pre></div></div>
<p>The new method receives one normalized integer vector per dimension. Returning <code>NULL</code> keeps today’s behaviour, so nothing changes for any class that does not implement it.</p>
<p><code>altarr_extract(x, i, j, k)</code> emulates this from package code: it normalizes each subscript with R’s own rules (by indexing a <code>seq_len()</code> of each dimension), plans the cartesian product of touched chunks, fetches once, and assembles. Its results are <code>identical()</code> to <code>x[i, j, k]</code> across negative, logical, character, <code>NA</code>, missing and zero-length subscripts, with and without <code>drop</code>.</p>
<p>The argument for the hook is that it is general. It adds no data model and no new object type, and every lazy array in R benefits: arrow, gdalraster, Zarr readers, anything ALTREP-backed with a shape.</p>
</section>
<section id="prior-art-and-what-is-different" class="level2">
<h2 class="anchored" data-anchor-id="prior-art-and-what-is-different">Prior art, and what is different</h2>
<p>Out-of-memory arrays in R are not new, and some of the older work was remarkably capable:</p>
<ul>
<li><strong>ff</strong> gave R memory-mapped, chunked vectors and arrays long before most languages had anything comparable, with its own <code>[</code> methods doing the paging. (I used ff heavily in my own <a href="https://figshare.utas.edu.au/ndownloader/files/40906604">thesis, chapter 6</a> to map 4D ocean model to diving elephant seal movement estimates).</li>
<li><strong>bigmemory</strong> offered shared and file-backed matrices for parallel work.</li>
<li><strong>DelayedArray</strong> and <strong>HDF5Array</strong> in Bioconductor provide lazy, chunk-aware arrays with block processing and delayed operations, and are the most complete answer to lazy compute on arrays in R.</li>
<li><strong>arrow</strong> and <strong>vroom</strong> use ALTREP to hand R vectors whose contents are not yet in R memory.</li>
</ul>
<p>The difference here is the class. Every one of the array packages above is an S3 or S4 object with methods: code has to know about the class, or the object gets converted. An ALTREP array with a <code>dim</code> and no class needs no method dispatch at all. Any function that indexes a numeric array, written in 1998 or yesterday, reads it lazily, and matrix indexing reads it in one planned batch.</p>
<p>That is also the limit. Without a class there is nowhere to hang delayed arithmetic, which is where DelayedArray’s design earns its complexity. The two approaches fit together: lazy reads through base indexing underneath, lazy compute in a plan above.</p>
</section>
<section id="limits-and-what-next" class="level2">
<h2 class="anchored" data-anchor-id="limits-and-what-next">Limits, and what next</h2>
<p>This is a prototype, and the limits are real:</p>
<ul>
<li><strong>Lazy reads, not lazy compute.</strong> Anything that needs the data pointer (arithmetic, printing, most of <code>apply</code>) materializes the whole array. <code>altarr</code> refuses that above a size option rather than fetching 3 GB because someone typed <code>x + 1</code>.</li>
<li><strong>The cache is unbounded.</strong> It holds one slot per chunk in the grid; it needs an LRU and a byte budget.</li>
<li><strong>Fetch runs on R’s main thread.</strong> Concurrency belongs inside the fetch implementation (object_store in Rust, GDAL), never touching the R API off-thread. The fetch contract is where a Rust backend plugs in, without touching the ALTREP layer.</li>
<li><strong>Coordinates are still dimnames, which must be character.</strong> ALTREP does not touch that; numeric coordinates are a separate question for base R.</li>
</ul>
<p>Next steps:</p>
<ol type="1">
<li>Build R-devel with the <code>ArraySubset</code> hook and show <code>x[i, j, k]</code> going from 192 round trips to 1, with no package-side method.</li>
<li>A Rust fetch backend over object storage, so the demo reads a remote Zarr store rather than a local one.</li>
<li>Ask the people who know ALTREP and R’s subscript code best whether the hook is sound.</li>
</ol>
<p>The code, tests and the measurement script are in the <code>altarr</code> package <!-- TODO: link the altarr repository -->. The tests check every path against base R with <code>identical()</code>.</p>
</section>
<section id="links" class="level2">
<h2 class="anchored" data-anchor-id="links">Links</h2>
<ul>
<li>R’s subsetting code: <a href="https://github.com/wch/r-source/blob/trunk/src/main/subset.c"><code>src/main/subset.c</code></a> (GitHub mirror of the R sources)</li>
<li>numpy indexing, including <code>np.ix_</code>: <a href="https://numpy.org/doc/stable/user/basics.indexing.html" class="uri">https://numpy.org/doc/stable/user/basics.indexing.html</a></li>
<li>zarr-python: <a href="https://zarr.readthedocs.io/" class="uri">https://zarr.readthedocs.io/</a></li>
<li>The <code>air_temperature</code> Zarr store: <a href="https://scottyhq.github.io/zarrdata/air_temperature.zarr/" class="uri">https://scottyhq.github.io/zarrdata/air_temperature.zarr/</a></li>
<li>ff: <a href="https://cran.r-project.org/package=ff" class="uri">https://cran.r-project.org/package=ff</a></li>
<li>DelayedArray: <a href="https://bioconductor.org/packages/DelayedArray/" class="uri">https://bioconductor.org/packages/DelayedArray/</a></li>
</ul>


</section>

 ]]></description>
  <category>code</category>
  <category>news</category>
  <category>zarr</category>
  <category>altarr</category>
  <category>r-packages</category>
  <guid>https://hypertidy.org/posts/2026-10-01_altrep-dim-no-class/</guid>
  <pubDate>Thu, 01 Oct 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>A mapview for modern R: Arrow in, any CRS out</title>
  <dc:creator>Michael Sumner</dc:creator>
  <link>https://hypertidy.org/posts/2026-09-30_modern-mapview-arrow-lonboard/</link>
  <description><![CDATA[ 






<p>R can have a fast, CRS-honest interactive map viewer, and GeoArrow is what makes it cheap to build now. The prompt is <a href="https://github.com/r-spatial/deckglgeoarrow">deckglgeoarrow</a>, which landed on CRAN this year: it ships sf and wk data to the browser as GeoArrow and lets deck.gl draw it on the GPU, on a maplibre map built with mapgl.</p>
<p>That package is a good answer to “show me a lot of features fast on a web map”. This post asks a wider question. What would a mapview for modern R look like if it were not tied to one JavaScript library, and not tied to Web Mercator? And how should it be split between a lean core and the convenience layer that makes mapview so loved?</p>
<p>The short version:</p>
<ul>
<li>Arrow does not give R and the browser shared memory, but it removes the parse. That is the real win.</li>
<li>Mercator is a basemap problem, not a GPU problem.</li>
<li>The core should be a protocol (data plane, scene spec, transport), not a widget.</li>
<li>Start on deck.gl, probe how far arbitrary CRS goes, and keep a clean seam so a capable team could own the renderer later.</li>
<li>Start on deck.gl, probe how far arbitrary CRS goes, and keep a clean seam so a capable team could own the renderer later.</li>
</ul>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-1-contents" aria-controls="callout-1" aria-expanded="true" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>This is now a real, early-stage project
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-1" class="callout-1-contents callout-collapse collapse show">
<div class="callout-body-container callout-body">
<p>The design below became <strong>allonboard</strong>: <a href="https://allboa.github.io/aobview/">aobview</a> is the mapview-style <code>view(x)</code> front door, <a href="https://github.com/allboa/aobcore">aobcore</a> is the core package and renderer, and <a href="https://github.com/allboa/scenespec">scenespec</a> is the renderer-neutral scene spec described below. Decisions live in <a href="https://github.com/allboa/design">design</a> and experiments in <a href="https://github.com/allboa/spikes">spikes</a>, all under the <a href="https://github.com/allboa">allboa</a> GitHub organisation.</p>
<p>It is early and in the open: names are placeholders, nothing is on CRAN, and the scene spec is pre-1.0. <code>view()</code> already works for sf/terra vectors and rasters, draws in any projected CRS (lon/lat data south of 40°S goes to EPSG:3031, north of 60°N to EPSG:3413), fetches remote COGs in tiles, and supports colour-by-attribute, legends and popups. The open problems are the ones this post anticipated: seams and the antimeridian in some projections, tile budgets for large rasters, and whether the renderer ends up sharing lonboard’s JavaScript stack. Try it and <a href="https://github.com/allboa/aobview/issues">open an issue</a> — feedback is the most useful thing right now.</p>
</div>
</div>
</div>
<section id="what-arrow-actually-changes" class="level2">
<h2 class="anchored" data-anchor-id="what-arrow-actually-changes">What Arrow actually changes</h2>
<p>Arrow does not share memory between R and the page; it ships bytes the GPU can use without parsing them. R and the browser are separate processes, so data always crosses a boundary. What matters is how much work happens on the far side.</p>
<p>The classic mapview path was htmlwidgets. sf became GeoJSON text, the text was embedded in the page, and the browser parsed it into JS objects and then into Leaflet layers. Most of the cost was in that parse and in building millions of small objects. leafgl (WebGL drawing) and flatgeobuf attachments (binary, streamable) already moved away from it. GeoArrow is where that path was heading.</p>
<p>With GeoArrow, an Arrow IPC buffer becomes JS typed arrays with no parse. deck.gl-geoarrow binds those coordinate buffers straight into GPU attributes. The format is still serialized, but the cost is close to a memory copy, not a parse-and-build.</p>
<p>Two consequences follow:</p>
<ul>
<li><strong>True shared memory exists in webR.</strong> When R runs as wasm in the same page, Arrow buffers can be handed across without a copy. Designing around Arrow now keeps that door open.</li>
<li><strong>The browser can fetch the data itself.</strong> deckglgeoarrow can already render remote GeoParquet and GeoArrow without reading it into R; only the styling is prepared in R (<a href="https://stat.ethz.ch/CRAN/web/packages/deckglgeoarrow/refman/deckglgeoarrow.html">CRAN reference</a>). R sends a recipe, the browser pulls the bytes.</li>
</ul>
<p>One constraint shapes the core: only native GeoArrow geometry encoding renders, not WKB (<a href="https://stat.ethz.ch/CRAN/web/packages/deckglgeoarrow/readme/README.html">README</a>). The data plane must emit native coordinate buffers. A wk handler writing geoarrow does that, and so does GDAL’s OGR Arrow stream when asked for GeoArrow encoding, which keeps a gdalraster-to-browser path free of conversions.</p>
</section>
<section id="mercator-is-a-basemap-problem" class="level2">
<h2 class="anchored" data-anchor-id="mercator-is-a-basemap-problem">Mercator is a basemap problem</h2>
<p>The GPU does not care about Web Mercator; the tiled basemap does. deck.gl has orthographic and cartesian views, so projected metres can go straight in and render in any CRS. deckglgeoarrow is Mercator-bound only because it sits on mapgl and maplibre.</p>
<p>There is one numeric trap. GPUs work in float32, and projected coordinates in the millions of metres lose precision. The fix is to subtract a local origin before the data leaves R, and draw relative to it. deck.gl has a coordinate-origin mechanism for exactly this.</p>
<p>The hard parts of “any CRS in the browser” are elsewhere:</p>
<ol type="1">
<li><strong>Basemaps.</strong> XYZ tiles are Mercator. Showing them in a polar or equal-area view means warping each tile on a reprojected mesh.</li>
<li><strong>Rasters into a non-Mercator view.</strong> <a href="https://developmentseed.org/deck.gl-raster/docs/intro/">deck.gl-raster</a> already reprojects COG and Zarr on the GPU with an adaptive mesh, from most source projections into the web map, and has prototype globe support. What remains is drawing into a polar or equal-area view CRS, and the polar and antimeridian cases its docs say are not yet tested.</li>
</ol>
<p>So the gap is narrower than it looks. Source reprojection is solved upstream. The view CRS and polar robustness are the open work, and that is where Antarctic use cases can contribute rather than rebuild.</p>
</section>
<section id="the-design-a-protocol-not-a-widget" class="level2">
<h2 class="anchored" data-anchor-id="the-design-a-protocol-not-a-widget">The design: a protocol, not a widget</h2>
<p>The core package should describe a scene and move data; drawing it is someone else’s job. It has three parts:</p>
<ol type="1">
<li><strong>Data plane.</strong> Vectors travel as nanoarrow streams with native GeoArrow geometry. Rasters are not Arrow: they travel as a grid descriptor (extent, dimension, CRS) plus a typed buffer, or simply as a URL the browser reads itself. Styling is data too, so colours computed in R become an RGBA column.</li>
<li><strong>Scene spec.</strong> A small declarative JSON document: the view CRS, the view type (projected, orthographic, globe), layers that reference data by id, and legends. Being plain data, it is testable and can have more than one renderer.</li>
<li><strong>Transport.</strong> Pluggable: embedded in a standalone page, served from a local httpuv server for large or streaming data, updated over a websocket (with selections coming back to R as row ids), or handed over in-process under webR.</li>
</ol>
<p><em>[Diagram placeholder: architecture - producers, core, transport, renderers]</em></p>
<p>The scene spec is the seam. Producers only need to emit standard inputs, and renderers only need to read the spec, so either side can change without touching the other.</p>
</section>
<section id="two-packages-a-lean-core-and-a-friendly-front-door" class="level2">
<h2 class="anchored" data-anchor-id="two-packages-a-lean-core-and-a-friendly-front-door">Two packages: a lean core and a friendly front door</h2>
<p>The core should accept standard inputs rather than import spatial classes; the convenience package is where the opinions live.</p>
<p><strong>The core</strong> depends on nanoarrow, geoarrow, wk and htmltools, and little else. Its contract is: give me an ArrowArrayStream with native GeoArrow geometry, a grid descriptor with a buffer, or a URL. gdalraster sits in Suggests as the best-supported producer, because GDAL can already hand over Arrow streams and read everything else. Anything that can emit those inputs works, with no class-specific code in the core.</p>
<p><strong>The convenience package</strong> is the mapview role: <code>view(x)</code> for any spatial object, sensible colours, legends, popups, layer toggles. It carries the methods for sf and terra, and it is allowed to be heavy. Keeping it separate means the core stays small enough for other packages to depend on.</p>
<p>The convenience layer should shrink over time. sf already reaches geoarrow via wk. Once terra can emit a GeoArrow stream for a SpatVector, and a grid descriptor plus source path for a SpatRaster, the adapters become thin and the package is mostly about the user experience. That is a good outcome, not a failure.</p>
</section>
<section id="the-renderer-is-where-the-work-is" class="level2">
<h2 class="anchored" data-anchor-id="the-renderer-is-where-the-work-is">The renderer is where the work is</h2>
<p>Rasters are the easy half of a hand-built renderer; vectors are the long tail. A reprojected raster is one textured mesh per tile with a simple shader. Vectors need much more:</p>
<ul>
<li><strong>Polygons:</strong> triangulation with holes (earcut).</li>
<li><strong>Lines:</strong> extrusion to screen-space width, joins, caps, antialiasing.</li>
<li><strong>Points:</strong> instanced sprites with sizing.</li>
<li><strong>Picking:</strong> an id framebuffer so a click resolves to a feature.</li>
<li><strong>Labels:</strong> glyph atlases, placement, collision. This is the biggest time sink, and it is why maplibre is large.</li>
<li><strong>Styling:</strong> attribute buffers and a data-driven style path.</li>
</ul>
<p>The same list faces any standalone COG viewer that wants vector overlays. So the R viewer and the web viewer should share one renderer, not build two.</p>
<p><strong>Curvature is one problem in two places.</strong> For rasters, mesh density absorbs the reprojection curvature. For vectors, a straight segment in the source CRS is not straight in the view CRS, so edges must be densified before projecting. It is the same sampling decision, applied to edges instead of a grid.</p>
<p><strong>Most of the work can move out of the browser.</strong> Triangulate, densify and project on the R or Rust side, then ship vertex, index and segment buffers as Arrow columns. The renderer shrinks to three draw calls: indexed triangles, extruded segments, instanced points. The trade-off is that the payload is tied to one view CRS. For globe or spinning views, project the densified vertices on the client instead, in a vertex shader or with PROJ compiled to wasm.</p>
</section>
<section id="strategy-deck.gl-first-with-a-seam-for-going-large" class="level2">
<h2 class="anchored" data-anchor-id="strategy-deck.gl-first-with-a-seam-for-going-large">Strategy: deck.gl first, with a seam for going large</h2>
<p>Start on deck.gl in an orthographic view and find out how far the CRS game goes. “Hand the renderer to a capable team” is close behind, and the scene spec is what makes both possible without a rewrite.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Option</th>
<th>What it buys</th>
<th>Move on when</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>deck.gl first</td>
<td>Vectors, picking and GeoArrow ingest from deck.gl-geoarrow; COG and Zarr reprojection from deck.gl-raster</td>
<td>A polar or equal-area view CRS fights deck.gl’s view model</td>
</tr>
<tr class="even">
<td>Hybrid</td>
<td>Own mesh pipeline for non-Mercator views and basemaps; deck.gl for the rest, camera synced</td>
<td>Keeping two render loops in step costs more than it saves</td>
</tr>
<tr class="odd">
<td>Go large</td>
<td>Share lonboard’s JavaScript stack; R owns data, scene spec and transport</td>
<td>The scene spec is stable and has more than one consumer</td>
</tr>
</tbody>
</table>
<p>The go-large option is realistic because the team largely exists. Development Seed’s lonboard puts deck.gl and GeoArrow behind a Python notebook widget, and it is the product that deck.gl-geoarrow and deck.gl-raster move with: lonboard upgrades the deck.gl-raster family in lockstep with deck.gl (<a href="https://github.com/developmentseed/lonboard/pull/1272">lonboard #1272</a>). If the scene spec is a neutral contract, an R front end can sit on the same JavaScript stack, and that renderer work becomes R’s renderer too.</p>
<p>The discipline that keeps the door open is simple: nothing deck.gl-specific in the scene spec. deck.gl is an implementation of the spec, not the spec.</p>
</section>
<section id="probe-a-polar-view-on-stock-deck.gl" class="level2">
<h2 class="anchored" data-anchor-id="probe-a-polar-view-on-stock-deck.gl">Probe: a polar view on stock deck.gl</h2>
<p>A polar view works on stock deck.gl today, as long as rasters arrive as pre-projected meshes rather than as COGs. The <a href="https://claude.ai/artifact/UxP8NaiUE8Hc4c6R7YXCnj">Polar view probe</a> draws EPSG:3031 metres in an orthographic view from 885 KiB of Arrow, decoded in about 20 ms.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Layer</th>
<th>Shipped as</th>
<th>Drawn with</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Land (50m)</td>
<td>geoarrow.polygon, exterior rings</td>
<td>SolidPolygonLayer, binary, cartesian</td>
<td>Works; the pole closure is harmless in a fill</td>
</tr>
<tr class="even">
<td>Coastline (50m)</td>
<td>geoarrow.linestring</td>
<td>PathLayer, binary, offsets bound directly</td>
<td>Works; must come from lines, not polygon rings</td>
</tr>
<tr class="odd">
<td>Graticule</td>
<td>geoarrow.linestring plus an RGBA column</td>
<td>PathLayer, colour read from the column</td>
<td>Works; styling as data</td>
</tr>
<tr class="even">
<td>Raster</td>
<td>grid descriptor, values, projected mesh with uv</td>
<td>SimpleMeshLayer with a canvas texture</td>
<td>Works; pole and antimeridian need nothing special</td>
</tr>
</tbody>
</table>
<p>Three things came out of it:</p>
<ul>
<li><strong>Source-space meshes make the pole easy.</strong> Each vertex of a lon/lat grid is projected once upstream. The pole row collapses to a point and the dateline is just a shared column edge.</li>
<li><strong>Topology depends on purpose.</strong> Natural Earth land closes through the pole, which is fine for a fill but draws a false seam if its rings are stroked. The outline has to come from a line dataset.</li>
<li><strong>EPSG:3031 is kind to float32.</strong> Its origin is the pole, so offsets stay under about 5000 km. Other projected CRSs still need a <code>coordinateOrigin</code> near the data.</li>
</ul>
<section id="where-lonboard-and-deck.gl-raster-sit" class="level3">
<h3 class="anchored" data-anchor-id="where-lonboard-and-deck.gl-raster-sit">Where lonboard and deck.gl-raster sit</h3>
<p>Neither can draw a tiled COG into a polar projected view yet. This is a review of the repos at their late-September 2026 state.</p>
<ul>
<li><strong>deck.gl-raster</strong> targets Web Mercator, plus a preliminary globe view since v0.8.0. Its tiled path (COGLayer, ZarrLayer) traverses tiles in Web Mercator and clamps the mesh at 85.05 degrees, so it cannot target EPSG:3031 in an orthographic view. The lower-level RasterLayer takes arbitrary reprojection functions, so a single untiled image might work, but that is untested. Its docs still list polar projections and the antimeridian as untested (<a href="https://github.com/developmentseed/deck.gl-raster/blob/main/docs/guides/intro.md">intro</a>, <a href="https://github.com/developmentseed/deck.gl-raster/pull/563">globe PR</a>).</li>
<li><strong>lonboard</strong> exposes orthographic, globe and other deck.gl views in <code>lonboard.experimental.view</code>, but it has no custom CRS: layers are reprojected to EPSG:4326. Its transport is Arrow written as Parquet over the anywidget comm (<a href="https://developmentseed.org/lonboard/latest/api/view/">view API</a>).</li>
<li><strong>deck.gl-geoarrow</strong> layers pass non-accessor props through to stock deck.gl layers, so cartesian coordinates should work. That is inferred from the source, not documented.</li>
</ul>
<p>The opening for R is clear. A polar view CRS, tiling that is not Web Mercator, and pole-covering rasters are unclaimed, and they are exactly the Antarctic cases. Upstream contributions to deck.gl-raster’s tile traversal would serve both languages.</p>
</section>
</section>
<section id="open-questions-and-next-steps" class="level2">
<h2 class="anchored" data-anchor-id="open-questions-and-next-steps">Open questions and next steps</h2>
<p>The first experiment is small: a polar-stereographic scene with a GeoArrow coastline and one COG, drawn by deck.gl in an orthographic view.</p>
<ul class="task-list">
<li><label><input type="checkbox">Draft the scene spec as a JSON schema: view CRS, view type, layers, data references, legends. The probe’s spec is a starting point.</label></li>
<li><label><input type="checkbox" checked="">Prototype the polar scene above and note where deck.gl pushes back.</label></li>
<li><label><input type="checkbox">Try deck.gl-raster’s untiled RasterLayer with a 3031 reprojection function; scope a non-Mercator tile traversal upstream.</label></li>
<li><label><input type="checkbox">Test a basemap in a non-Mercator view: warp XYZ tiles on a mesh, or skip tiles and draw vector land in the view CRS.</label></li>
<li><label><input type="checkbox">Measure the float32 origin-offset approach on a projected CRS whose origin is far from the data.</label></li>
<li><label><input type="checkbox">Decide where triangulation and densification live: R, Rust, or the client.</label></li>
<li><label><input type="checkbox">Replace the probe’s Python prep with R: gdalraster Arrow streams, wk, and a mesh builder.</label></li>
</ul>
<p>Open questions:</p>
<ul>
<li>Should styling be data only (RGBA columns computed in R), or also allow accessor expressions evaluated in the browser?</li>
<li>What is the minimum grid descriptor for rasters: extent, dimension and CRS, or does it need overviews and nodata too?</li>
<li>Would the lonboard maintainers welcome a shared, language-neutral scene spec, so R and Python front ends drive one renderer?</li>
</ul>


</section>

 ]]></description>
  <category>mapview</category>
  <category>arrow</category>
  <category>deck.gl</category>
  <category>spatial</category>
  <category>R</category>
  <guid>https://hypertidy.org/posts/2026-09-30_modern-mapview-arrow-lonboard/</guid>
  <pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Distance, perimeter and area in sf and terra: which number do you get?</title>
  <dc:creator>Michael Sumner</dc:creator>
  <link>https://hypertidy.org/posts/2026-09-23_measures-sf-terra/</link>
  <description><![CDATA[ 






<p>In May 2025 a question on R-sig-Geo asked why <code>sf::st_perimeter()</code> gave a different answer from <code>sf::st_length(sf::st_boundary())</code> for the same small land parcel in France, with spherical geometry switched off (<a href="https://stat.ethz.ch/pipermail/r-sig-geo/2025-May/029541.html">thread</a>). There were three different numbers for the perimeter of that parcel, and each of them is correct for a particular model of the Earth and a particular meaning of “edge”. I posted a <a href="https://gist.github.com/mdsumner/5daedfcbee851ca31a7aa9006dbf74cf">gist</a> at the time as a start at unpicking them. (In my message on that thread, “sf+longlat-s2 uses Karney” should read “with s2 switched off”.)</p>
<p>The specific inconsistency in <code>st_perimeter()</code> has since been fixed in sf (<a href="https://github.com/r-spatial/sf/issues/2541">#2541</a>). What remains is the more general question this post is about: for a given geometry, how does each package decide which model of the Earth to measure on, and can a user predict the answer from the data in front of them?</p>
<p>The short version:</p>
<ul>
<li><strong>terra</strong> decides from the geometry’s coordinate reference system. Longitude and latitude input is always measured on the ellipsoid. Projected input is measured on the ellipsoid for area and cell size (by default), and in the plane for perimeter and distance.</li>
<li><strong>sf</strong> decides from the coordinate reference system <em>and</em> a session option, <code>sf_use_s2()</code>. Longitude and latitude input is measured on a sphere when s2 is on (the default) and on the ellipsoid when it is off. Projected input is always measured in the plane. The same option also selects the engine used for predicates and overlay operations.</li>
</ul>
<p>The rest of the post establishes this with code, and then looks at how large the differences are and where they come from.</p>
<section id="versions" class="level2">
<h2 class="anchored" data-anchor-id="versions">Versions</h2>
<p>Everything below depends on these versions, and the results should be re-run when they change.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1">pkgs <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sf"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"terra"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"s2"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lwgeom"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"geosphere"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"geodist"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"wk"</span>)</span>
<span id="cb1-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vapply</span>(pkgs, <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(p) <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.character</span>(utils<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">packageVersion</span>(p)), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>       sf     terra        s2    lwgeom geosphere   geodist        wk 
  "1.1.3"  "1.9.50"  "1.1.12"  "0.2.17"   "1.6.8"   "0.1.1"   "0.9.5" </code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1">sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sf_extSoftVersion</span>()</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>          GEOS           GDAL         proj.4 GDAL_with_GEOS     USE_PROJ_H 
      "3.12.1"       "3.13.3"        "9.9.0"         "true"         "true" 
          PROJ 
       "9.9.0" </code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1">terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">gdal</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lib =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"all"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>    gdal  proj   geos   TBB
1 3.13.3 9.9.0 3.12.1 FALSE</code></pre>
</div>
</div>
</section>
<section id="the-parcel" class="level2">
<h2 class="anchored" data-anchor-id="the-parcel">The parcel</h2>
<p>This is the polygon from the thread, as a plain coordinate matrix in Lambert-93 (EPSG:2154, RGF93 on the GRS 1980 ellipsoid). It is about 40 m across. We keep three versions of it: projected, transformed to longitude and latitude (EPSG:4326), and with no CRS at all.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb7-1">xy <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">matrix</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(</span>
<span id="cb7-2">  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">320102.67</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6845954.56</span>,</span>
<span id="cb7-3">  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">320111.85</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6845962.15</span>,</span>
<span id="cb7-4">  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">320131.82</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6845971.55</span>,</span>
<span id="cb7-5">  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">320134.33</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6845971.90</span>,</span>
<span id="cb7-6">  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">320138.50</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6845953.21</span>,</span>
<span id="cb7-7">  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">320141.93</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6845936.12</span>,</span>
<span id="cb7-8">  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">320135.33</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6845935.07</span>,</span>
<span id="cb7-9">  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">320118.58</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6845931.54</span>,</span>
<span id="cb7-10">  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">320113.45</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6845929.91</span>,</span>
<span id="cb7-11">  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">320105.18</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6845950.57</span>,</span>
<span id="cb7-12">  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">320102.67</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">6845954.56</span></span>
<span id="cb7-13">), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ncol =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">byrow =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb7-14"></span>
<span id="cb7-15">p   <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_sfc</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_polygon</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(xy)), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:2154"</span>)  <span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## projected</span></span>
<span id="cb7-16">pll <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_transform</span>(p, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>)                          <span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## longlat</span></span>
<span id="cb7-17">p0  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_set_crs</span>(p, <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NA</span>)                                     <span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## no crs</span></span>
<span id="cb7-18">ll  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_coordinates</span>(pll)[, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span>
<span id="cb7-19">seg_i <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq_len</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nrow</span>(xy) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span></code></pre></div></div>
</div>
<p>A small helper sets <code>sf_use_s2()</code> for one expression and restores it afterwards.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb8-1">with_s2 <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(use, code) {</span>
<span id="cb8-2">  old <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">suppressMessages</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sf_use_s2</span>(use))</span>
<span id="cb8-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">on.exit</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">suppressMessages</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sf_use_s2</span>(old)))</span>
<span id="cb8-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">force</span>(code)</span>
<span id="cb8-5">}</span></code></pre></div></div>
</div>
</section>
<section id="three-reference-numbers" class="level2">
<h2 class="anchored" data-anchor-id="three-reference-numbers">Three reference numbers</h2>
<p>Before calling any sf or terra function, we compute the perimeter and area directly under each of the three models, so that every later result can be labelled by which model it corresponds to.</p>
<ul>
<li><strong>Planar</strong>: each edge is a straight line in the projected coordinates, and lengths and areas are Euclidean in those coordinates.</li>
<li><strong>Ellipsoid</strong>: each edge is a geodesic on the WGS84 ellipsoid, computed with Karney’s algorithms (GeographicLib), here via geosphere.</li>
<li><strong>Sphere</strong>: each edge is a great circle on a sphere of radius 6371010 m, which is the radius s2 uses.</li>
</ul>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb9-1">planar_perim <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sum</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sqrt</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rowSums</span>((xy[seg_i <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, ] <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> xy[seg_i, ])<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">^</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)))</span>
<span id="cb9-2">planar_area  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">abs</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sum</span>(xy[seg_i, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> xy[seg_i <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>] <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span></span>
<span id="cb9-3">                        xy[seg_i <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> xy[seg_i, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>])) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb9-4"></span>
<span id="cb9-5">karney_perim <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sum</span>(geosphere<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">distGeo</span>(ll[seg_i, ], ll[seg_i <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, ]))</span>
<span id="cb9-6">karney_area  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> geosphere<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">areaPolygon</span>(ll)</span>
<span id="cb9-7"></span>
<span id="cb9-8">R_s2 <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6371010</span></span>
<span id="cb9-9">sphere_perim <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_perimeter</span>(s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_s2_geography</span>(pll), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">radius =</span> R_s2)</span>
<span id="cb9-10">sphere_area  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_area</span>(s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_s2_geography</span>(pll), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">radius =</span> R_s2)</span>
<span id="cb9-11"></span>
<span id="cb9-12">refs_perim <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">planar =</span> planar_perim, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ellipsoid =</span> karney_perim, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">sphere =</span> sphere_perim)</span>
<span id="cb9-13">refs_area  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">planar =</span> planar_area,  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ellipsoid =</span> karney_area,  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">sphere =</span> sphere_area)</span>
<span id="cb9-14">refs_perim</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>    planar  ellipsoid     sphere 
129.248935 129.285384 129.098150 </code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb11-1">refs_area</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>    planar  ellipsoid     sphere 
1049.65051 1050.24260 1047.02612 </code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb13-1">which_number <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(x, refs) {</span>
<span id="cb13-2">  hit <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">names</span>(refs)[<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">abs</span>(refs <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> x) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> refs <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e-6</span>]</span>
<span id="cb13-3">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">length</span>(hit) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"OTHER"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">paste</span>(hit, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">collapse =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/"</span>)</span>
<span id="cb13-4">}</span></code></pre></div></div>
</div>
<p>The perimeter is 129.249 m planar, 129.285 m on the ellipsoid and 129.098 m on the sphere. The area is 1049.65, 1050.24 and 1047.03 square metres respectively.</p>
<section id="what-each-number-is" class="level3">
<h3 class="anchored" data-anchor-id="what-each-number-is">What each number is</h3>
<p>The ellipsoidal length of an edge is the same as its planar length in an azimuthal equidistant projection centred on one of its endpoints, because that projection preserves distance from its centre.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb14-1">aeqd_edge <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(a, b) {</span>
<span id="cb14-2">  crs <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sprintf</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=aeqd +lon_0=%.12f +lat_0=%.12f +ellps=WGS84"</span>, a[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], a[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>])</span>
<span id="cb14-3">  q <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">project</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rbind</span>(a, b), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">from =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">to =</span> crs)</span>
<span id="cb14-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sqrt</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sum</span>((q[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, ] <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> q[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, ])<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">^</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>))</span>
<span id="cb14-5">}</span>
<span id="cb14-6"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">aeqd =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sum</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vapply</span>(seg_i, <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(i) <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">aeqd_edge</span>(ll[i, ], ll[i <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, ]), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)),</span>
<span id="cb14-7">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">karney =</span> karney_perim)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>      aeqd     karney 
129.285384 129.285384 </code></pre>
</div>
</div>
<p>The spherical number is the same geodesic calculation with zero flattening and s2’s radius.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb16-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sum</span>(geosphere<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">distGeo</span>(ll[seg_i, ], ll[seg_i <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, ], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">a =</span> R_s2, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">f =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>))</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] 129.09815</code></pre>
</div>
</div>
<p>The planar number is the ellipsoidal number scaled by the projection. Lambert-93 is conformal, so at a point it has a single scale factor k in every direction. Lengths scale by k and areas by k squared. Measuring k numerically at the parcel reproduces both ratios.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb18-1">k_perim <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> planar_perim <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> karney_perim</span>
<span id="cb18-2">k_area  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> planar_area <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> karney_area</span>
<span id="cb18-3"></span>
<span id="cb18-4">cen <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">colMeans</span>(ll[seg_i, ])</span>
<span id="cb18-5">nb  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> geosphere<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">destPoint</span>(cen, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">b =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">45</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">d =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>)</span>
<span id="cb18-6">cq  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">project</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rbind</span>(cen, nb), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">from =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">to =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:2154"</span>)</span>
<span id="cb18-7">k_point <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sqrt</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sum</span>((cq[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, ] <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> cq[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, ])<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">^</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span></span>
<span id="cb18-8"></span>
<span id="cb18-9"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">k_point =</span> k_point, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">k_perim =</span> k_perim, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">k_perim_sq =</span> k_perim<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">^</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">k_area =</span> k_area)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>    k_point     k_perim  k_perim_sq      k_area 
0.999718096 0.999718077 0.999436234 0.999436238 </code></pre>
</div>
</div>
<p>At this location k is 0.999718, so the planar perimeter is 0.028% shorter than the ellipsoidal one and the planar area is 0.056% smaller. An equal-area projection centred on the parcel gives the ellipsoidal area to within about one part in 10^8.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb20-1">laea <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sprintf</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=laea +lon_0=%.12f +lat_0=%.12f +ellps=WGS84"</span>, cen[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], cen[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>])</span>
<span id="cb20-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">laea =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.numeric</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_area</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_set_crs</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_transform</span>(pll, laea), laea))),</span>
<span id="cb20-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">karney =</span> karney_area)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>      laea     karney 
1050.24259 1050.24260 </code></pre>
</div>
</div>
</section>
</section>
<section id="how-each-package-decides" class="level2">
<h2 class="anchored" data-anchor-id="how-each-package-decides">How each package decides</h2>
<section id="sf" class="level3">
<h3 class="anchored" data-anchor-id="sf">sf</h3>
<p>The decision in sf is visible in the source of <code>st_perimeter()</code>. The first branch is taken when <code>sf_use_s2()</code> is <code>TRUE</code> and the geometry is longitude and latitude.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb22" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb22-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">body</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>st_perimeter)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>{
    x = st_geometry(x)
    if (sf_use_s2() &amp;&amp; isTRUE(st_is_longlat(x))) {
        if (!requireNamespace("s2", quietly = TRUE)) 
            stop("package s2 required to calculate the perimeter of spherical geometries")
        units::set_units(s2::s2_perimeter(x, ...), "m", mode = "standard")
    }
    else {
        if (isTRUE(st_is_longlat(x))) 
            units::set_units(st_length(st_boundary(x)), "m", 
                mode = "standard")
        else {
            if (!requireNamespace("lwgeom", quietly = TRUE)) 
                stop("package lwgeom required, please install it first")
            lwgeom::st_perimeter_lwgeom(x)
        }
    }
}</code></pre>
</div>
</div>
<p>The sf documentation for <code>st_area()</code> describes the same rule: with longitude and latitude coordinates, area is computed on the ellipsoid with <code>st_geod_area()</code> if <code>sf_use_s2()</code> is <code>FALSE</code>, and on the sphere with <code>s2_area()</code> if it is <code>TRUE</code>; with projected coordinates, area is computed in the plane. <code>st_length()</code> and <code>st_distance()</code> follow the same pattern.</p>
<p>When s2 is off, the longitude and latitude branch of <code>st_perimeter()</code> goes via <code>st_length(st_boundary(x))</code>. It does not call lwgeom’s perimeter, which declines longitude and latitude input:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb24" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb24-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tryCatch</span>(lwgeom<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_perimeter</span>(pll), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">error =</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(e) <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">conditionMessage</span>(e))</span></code></pre></div></div>
<div class="cell-output cell-output-stderr">
<pre><code>Warning in lwgeom::st_perimeter(pll): 'lwgeom::st_perimeter' is deprecated.
Use 'sf::st_perimeter or lwgeom::st_perimeter_lwgeom' instead.
See help("Deprecated")</code></pre>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] "for perimeter of longlat geometry, cast to LINESTRING and use st_length"</code></pre>
</div>
</div>
</section>
<section id="terra" class="level3">
<h3 class="anchored" data-anchor-id="terra">terra</h3>
<p>terra has no global option for this. For longitude and latitude input, its documentation states that distances, lengths and areas are computed on the WGS84 ellipsoid with Karney’s algorithms. For projected input, the behaviour differs between functions. <code>expanse()</code> and <code>cellSize()</code> have an argument <code>transform</code>, which defaults to <code>TRUE</code> and computes the result on the ellipsoid. <code>perim()</code> and <code>distance()</code> have no such argument and work in the plane.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb27" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb27-1">vp  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vect</span>(p)</span>
<span id="cb27-2">pts <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vect</span>(xy[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, ], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:2154"</span>)</span>
<span id="cb27-3">r   <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rast</span>(terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ext</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">320100</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">320150</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6845925</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6845975</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">res =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:2154"</span>)</span>
<span id="cb27-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">unlist</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(</span>
<span id="cb27-5">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">perim          =</span> terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">perim</span>(vp),</span>
<span id="cb27-6">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">expanse        =</span> terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">expanse</span>(vp),</span>
<span id="cb27-7">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">expanse_plane  =</span> terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">expanse</span>(vp, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">transform =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>),</span>
<span id="cb27-8">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">distance       =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.numeric</span>(terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">distance</span>(pts)),</span>
<span id="cb27-9">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">cellSize       =</span> terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">global</span>(terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cellSize</span>(r, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">unit =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"m"</span>), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mean"</span>)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>],</span>
<span id="cb27-10">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">cellSize_plane =</span> terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">global</span>(terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cellSize</span>(r, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">unit =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"m"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">transform =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mean"</span>)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb27-11">))</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>         perim        expanse  expanse_plane       distance       cellSize 
   129.2489354   1050.2425993   1049.6505127     11.9113601    100.0564081 
cellSize_plane 
   100.0000000 </code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb29" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb29-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## references for the distance row</span></span>
<span id="cb29-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">planar =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sqrt</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sum</span>((xy[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, ] <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> xy[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, ])<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">^</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">karney =</span> geosphere<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">distGeo</span>(ll[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, ], ll[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, ]))</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>    planar     karney 
11.9113601 11.9147186 </code></pre>
</div>
</div>
<p>The area of a nominal 10 m by 10 m cell is 100.056 square metres on the ellipsoid, which is 100 divided by k squared. The distance between the first two vertices is the planar value, 11.911 m, rather than the ellipsoidal 11.915 m.</p>
</section>
</section>
<section id="the-grid" class="level2">
<h2 class="anchored" data-anchor-id="the-grid">The grid</h2>
<p>Now every combination: four ways of asking for a perimeter, three ways of asking for an area, three versions of the polygon, and <code>sf_use_s2()</code> on and off. Each result is labelled by the reference number it matches.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb31" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb31-1">inputs <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">projected =</span> p, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">longlat =</span> pll, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">none =</span> p0)</span>
<span id="cb31-2"></span>
<span id="cb31-3">perim_calls <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(</span>
<span id="cb31-4">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sf::st_perimeter"</span>        <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(x) sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_perimeter</span>(x),</span>
<span id="cb31-5">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sf::st_length(boundary)"</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(x) sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_length</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_boundary</span>(x)),</span>
<span id="cb31-6">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sf::st_length(cast MLS)"</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(x) sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_length</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_cast</span>(x, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"MULTILINESTRING"</span>)),</span>
<span id="cb31-7">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"terra::perim"</span>            <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(x) terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">perim</span>(terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vect</span>(x))</span>
<span id="cb31-8">)</span>
<span id="cb31-9">area_calls <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(</span>
<span id="cb31-10">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sf::st_area"</span>                     <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(x) sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_area</span>(x),</span>
<span id="cb31-11">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"terra::expanse"</span>                  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(x) terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">expanse</span>(terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vect</span>(x)),</span>
<span id="cb31-12">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"terra::expanse(transform=FALSE)"</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(x) terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">expanse</span>(terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vect</span>(x), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">transform =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>)</span>
<span id="cb31-13">)</span>
<span id="cb31-14"></span>
<span id="cb31-15">run_grid <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(calls, refs) {</span>
<span id="cb31-16">  out <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">expand.grid</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">call =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">names</span>(calls), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">input =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">names</span>(inputs), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">s2 =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>, <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>),</span>
<span id="cb31-17">                     <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">stringsAsFactors =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>)</span>
<span id="cb31-18">  out<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>value <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NA_real_</span></span>
<span id="cb31-19">  out<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>msg <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span></span>
<span id="cb31-20">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> (i <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq_len</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nrow</span>(out))) {</span>
<span id="cb31-21">    f <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> calls[[out<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>call[i]]]</span>
<span id="cb31-22">    x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> inputs[[out<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>input[i]]]</span>
<span id="cb31-23">    r <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tryCatch</span>(</span>
<span id="cb31-24">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">withCallingHandlers</span>(</span>
<span id="cb31-25">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">with_s2</span>(out<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>s2[i], <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.numeric</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">f</span>(x))),</span>
<span id="cb31-26">        <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">warning =</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(w) {</span>
<span id="cb31-27">          out<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>msg[i] <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">conditionMessage</span>(w)</span>
<span id="cb31-28">          <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">invokeRestart</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"muffleWarning"</span>)</span>
<span id="cb31-29">        }),</span>
<span id="cb31-30">      <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">error =</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(e) {</span>
<span id="cb31-31">        out<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>msg[i] <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">paste</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ERROR:"</span>, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">conditionMessage</span>(e))</span>
<span id="cb31-32">        <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NA_real_</span></span>
<span id="cb31-33">      })</span>
<span id="cb31-34">    out<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>value[i] <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> r</span>
<span id="cb31-35">  }</span>
<span id="cb31-36">  out<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>matches <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vapply</span>(out<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>value, <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(v) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">is.na</span>(v)) <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">which_number</span>(v, refs), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>)</span>
<span id="cb31-37">  out</span>
<span id="cb31-38">}</span>
<span id="cb31-39"></span>
<span id="cb31-40">wide <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(g) {</span>
<span id="cb31-41">  w <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">reshape</span>(g[, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"call"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"input"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"s2"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"matches"</span>)],</span>
<span id="cb31-42">               <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">idvar =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"call"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"s2"</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">timevar =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"input"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">direction =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"wide"</span>)</span>
<span id="cb31-43">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">names</span>(w) <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sub</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"^matches</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\\</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">."</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">names</span>(w))</span>
<span id="cb31-44">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rownames</span>(w) <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span></span>
<span id="cb31-45">  w</span>
<span id="cb31-46">}</span></code></pre></div></div>
</div>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb32" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb32-1">perim_grid <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">run_grid</span>(perim_calls, refs_perim)</span></code></pre></div></div>
<div class="cell-output cell-output-stderr">
<pre><code>Linking to GEOS 3.12.1, GDAL 3.13.3, PROJ 9.9.0; sf_use_s2() is TRUE</code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb34" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb34-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wide</span>(perim_grid)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>                     call    s2 projected   longlat   none
1        sf::st_perimeter  TRUE    planar    sphere planar
2 sf::st_length(boundary)  TRUE    planar    sphere planar
3 sf::st_length(cast MLS)  TRUE    planar    sphere planar
4            terra::perim  TRUE    planar ellipsoid planar
5        sf::st_perimeter FALSE    planar ellipsoid planar
6 sf::st_length(boundary) FALSE    planar ellipsoid planar
7 sf::st_length(cast MLS) FALSE    planar ellipsoid planar
8            terra::perim FALSE    planar ellipsoid planar</code></pre>
</div>
</div>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb36" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb36-1">area_grid <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">run_grid</span>(area_calls, refs_area)</span>
<span id="cb36-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wide</span>(area_grid)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>                             call    s2 projected   longlat   none
1                     sf::st_area  TRUE    planar    sphere planar
2                  terra::expanse  TRUE ellipsoid ellipsoid planar
3 terra::expanse(transform=FALSE)  TRUE    planar ellipsoid planar
4                     sf::st_area FALSE    planar ellipsoid planar
5                  terra::expanse FALSE ellipsoid ellipsoid planar
6 terra::expanse(transform=FALSE) FALSE    planar ellipsoid planar</code></pre>
</div>
</div>
<p>The three sf routes to a perimeter now agree with each other in every case, which was not true at the time of the original thread. With projected input, or no CRS, everything in sf is planar. With longitude and latitude input, sf gives the spherical number when s2 is on and the ellipsoidal number when it is off.</p>
<p>terra does not read <code>sf_use_s2()</code>, so its rows are the same in both halves of each table. With longitude and latitude input it gives the ellipsoidal number for both perimeter and area. With projected input, <code>perim()</code> is planar and <code>expanse()</code> is ellipsoidal. With no CRS, terra computes in the plane and warns that the results may be wrong.</p>
<p>For longitude and latitude data, which is the common case for data arriving from files and web services, the two packages with their default settings give different answers: sf gives 129.098 m and 1047.03 square metres, terra gives 129.285 m and 1050.24 square metres.</p>
</section>
<section id="one-option-two-roles" class="level2">
<h2 class="anchored" data-anchor-id="one-option-two-roles">One option, two roles</h2>
<p>In sf, <code>sf_use_s2()</code> selects the model for measures and also the engine for predicates and overlays. With s2 off, longitude and latitude geometries go to GEOS, which treats the coordinates as planar. Measures, however, go to GeographicLib on the ellipsoid, where each edge is a geodesic. So within one session with s2 off, the same polygon has ellipsoidal geodesic edges for the purpose of its area and straight edges in longitude and latitude for the purpose of point-in-polygon.</p>
<p>This matters for wide polygons at high latitude. A geodesic between two points at the same latitude does not follow the parallel; it bends toward the pole. Here is a box 120 degrees wide between 70 and 60 degrees south, and a point just inside its northern edge.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb38" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb38-1">bx <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_sfc</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_polygon</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rbind</span>(</span>
<span id="cb38-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">70</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">70</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">70</span>)))),</span>
<span id="cb38-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>)</span>
<span id="cb38-4">pt <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_sfc</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_point</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">62</span>)), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>)</span>
<span id="cb38-5"></span>
<span id="cb38-6"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">with_s2</span>(<span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>,  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">area =</span> sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_area</span>(bx),</span>
<span id="cb38-7">                    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">intersects =</span> sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_intersects</span>(bx, pt, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">sparse =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]))</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>$area
3008488091024 [m^2]

$intersects
[1] FALSE</code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb40" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb40-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">with_s2</span>(<span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">area =</span> sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_area</span>(bx),</span>
<span id="cb40-2">                    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">intersects =</span> sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_intersects</span>(bx, pt, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">sparse =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>)[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]))</span></code></pre></div></div>
<div class="cell-output cell-output-stderr">
<pre><code>although coordinates are longitude/latitude, st_intersects assumes that they
are planar</code></pre>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>$area
3.027757e+12 [m^2]

$intersects
[1] TRUE</code></pre>
</div>
</div>
<p>With s2 on, the area is spherical and the point is outside the polygon, which is consistent: on the sphere the northern edge passes south of 62 degrees south at longitude 0. With s2 off, the area is ellipsoidal, which is the more accurate model, but the point is now inside the polygon, because GEOS treats the northern edge as the straight line at 60 degrees south. A user who switches s2 off to obtain ellipsoidal measures therefore also changes the answers to spatial predicates, and the measures and predicates no longer describe the same polygon.</p>
</section>
<section id="point-distance-and-two-spheres" class="level2">
<h2 class="anchored" data-anchor-id="point-distance-and-two-spheres">Point distance and two spheres</h2>
<p>For a single pair of points (the first two vertices of the parcel) there is a fourth number.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb43" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb43-1">a <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> ll[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, , drop <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>]</span>
<span id="cb43-2">b <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> ll[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, , drop <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>]</span>
<span id="cb43-3">pa <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_sfc</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_point</span>(a), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>)</span>
<span id="cb43-4">pb <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_sfc</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_point</span>(b), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>)</span>
<span id="cb43-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(</span>
<span id="cb43-6">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">sf_s2     =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">with_s2</span>(<span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>,  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.numeric</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_distance</span>(pa, pb))),</span>
<span id="cb43-7">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">sf_nos2   =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">with_s2</span>(<span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.numeric</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_distance</span>(pa, pb))),</span>
<span id="cb43-8">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">terra_geo =</span> terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">distance</span>(a, b, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lonlat =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>),</span>
<span id="cb43-9">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">terra_hav =</span> terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">distance</span>(a, b, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lonlat =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">method =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"haversine"</span>),</span>
<span id="cb43-10">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">geodist   =</span> geodist<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geodist</span>(a, b, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">measure =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"geodesic"</span>),</span>
<span id="cb43-11">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">karney    =</span> geosphere<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">distGeo</span>(a, b),</span>
<span id="cb43-12">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">planar    =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sqrt</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sum</span>((xy[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, ] <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> xy[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, ])<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">^</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>))</span>
<span id="cb43-13">)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>     sf_s2    sf_nos2  terra_geo  terra_hav    geodist     karney     planar 
11.8954624 11.9147186 11.9147186 11.9087694 11.9147186 11.9147186 11.9113601 </code></pre>
</div>
</div>
<p>sf with s2 off, terra’s default method, geodist and geosphere all agree on the ellipsoidal distance. sf with s2 on gives the spherical distance. terra’s <code>method = "haversine"</code> gives a third value, because it uses a sphere with the equatorial radius of WGS84 rather than s2’s mean radius:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb45" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb45-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">terra_hav      =</span> terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">distance</span>(a, b, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lonlat =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">method =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"haversine"</span>),</span>
<span id="cb45-2">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">hav_equatorial =</span> geosphere<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">distHaversine</span>(a, b, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">r =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6378137</span>),</span>
<span id="cb45-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">hav_mean_s2    =</span> geosphere<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">distHaversine</span>(a, b, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">r =</span> R_s2))</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>     terra_hav hav_equatorial    hav_mean_s2 
    11.9087694     11.9087694     11.8954624 </code></pre>
</div>
</div>
<p>The two spheres differ in radius by a factor of 1.0011, so “spherical distance” is itself not one number.</p>
<!-- TODO check at terra tip: the SpatRaster distance() method appeared to
default to method = "haversine" in the CRAN source I read. Confirm with
  getMethod("distance", signature(x = "SpatRaster", y = "missing"))
and mention here if it still does. -->
</section>
<section id="how-large-is-the-difference-and-where" class="level2">
<h2 class="anchored" data-anchor-id="how-large-is-the-difference-and-where">How large is the difference, and where?</h2>
<p>For a 40 m parcel the differences are centimetres. It is tempting to conclude that the choice of model only matters for large polygons. For the sphere against the ellipsoid, that is not the case: the relative error in area is set by latitude and is essentially the same for a box one metre across and a box one degree across.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb47" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb47-1">box <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(lon0, lat0, size) {</span>
<span id="cb47-2">  m <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rbind</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(lon0, lat0), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(lon0 <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> size, lat0), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(lon0 <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> size, lat0 <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> size),</span>
<span id="cb47-3">             <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(lon0, lat0 <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> size), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(lon0, lat0))</span>
<span id="cb47-4">  sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_sfc</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_polygon</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(m)), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>)</span>
<span id="cb47-5">}</span>
<span id="cb47-6">sweep_lat <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(size, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lats =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">89.5</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">89.5</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> size, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">by =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)) {</span>
<span id="cb47-7">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">do.call</span>(rbind, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">lapply</span>(lats, <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(lat0) {</span>
<span id="cb47-8">    g <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">box</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">147</span>, lat0, size)</span>
<span id="cb47-9">    k <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> geosphere<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">areaPolygon</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_coordinates</span>(g)[, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>])</span>
<span id="cb47-10">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">data.frame</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lat =</span> lat0 <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> size <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> size,</span>
<span id="cb47-11">               <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">s2 =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">with_s2</span>(<span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.numeric</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_area</span>(g))) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> k <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,</span>
<span id="cb47-12">               <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">terra =</span> terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">expanse</span>(terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vect</span>(g)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> k <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb47-13">  }))</span>
<span id="cb47-14">}</span>
<span id="cb47-15">sw <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rbind</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sweep_lat</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.001</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sweep_lat</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sweep_lat</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb47-16"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">range</span>(sw<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>terra)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] 0 0</code></pre>
</div>
</div>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb49" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb49-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(sw<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>lat, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> sw<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>s2, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">type =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"n"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">xlab =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"latitude"</span>,</span>
<span id="cb49-2">     <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ylab =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"area error vs ellipsoid (%)"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">main =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"s2 sphere vs Karney ellipsoid"</span>)</span>
<span id="cb49-3">sizes <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">unique</span>(sw<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>size)</span>
<span id="cb49-4"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> (s <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> sizes) {</span>
<span id="cb49-5">  d <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> sw[sw<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>size <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> s, ]</span>
<span id="cb49-6">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">lines</span>(d<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>lat, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> d<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>s2, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lty =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">match</span>(s, sizes))</span>
<span id="cb49-7">}</span>
<span id="cb49-8"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">abline</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">h =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">col =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"grey"</span>)</span>
<span id="cb49-9"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">legend</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"topright"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">legend =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">paste</span>(sizes, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"deg box"</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lty =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq_along</span>(sizes))</span></code></pre></div></div>
<div class="cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://hypertidy.org/posts/2026-09-23_measures-sf-terra/index_files/figure-html/sweep-plot-1.png" class="img-fluid figure-img" width="672"></p>
<figcaption>Relative error of s2 spherical area against Karney ellipsoidal area, by latitude, for square boxes of three sizes. The three curves coincide.</figcaption>
</figure>
</div>
</div>
</div>
<p>The spherical area is 0.45% too large at the equator, correct near 35 degrees north and south, and 0.89% too small near the poles. At the latitudes of the Southern Ocean and Antarctica the spherical area is consistently between 0.5% and 0.9% smaller than the ellipsoidal area, regardless of the size of the polygon. terra agrees with Karney to within floating point precision throughout, since it uses the same library.</p>
<p>Planar error behaves differently: it depends on the projection’s scale factor over the polygon, which for a well-chosen local projection is small for small polygons and grows with distance from the projection’s centre or standard lines.</p>
</section>
<section id="what-is-an-edge" class="level2">
<h2 class="anchored" data-anchor-id="what-is-an-edge">What is an edge?</h2>
<p>All of the above assumes that everyone agrees on what the polygon is. The coordinates alone do not determine that. Between two vertices, an edge could be a geodesic on the ellipsoid, a great circle on the sphere, a straight line in some projection, or a line of constant latitude. For small polygons these coincide closely. For large ones they do not.</p>
<p>Here is a box 10 degrees on each side at 60 degrees south, first with only its four corners, then densified at 0.1 degree spacing along its edges in longitude and latitude so that the northern and southern edges follow the parallels.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb50" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb50-1">b1  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">box</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">147</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>)</span>
<span id="cb50-2">b1d <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_set_crs</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_segmentize</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_set_crs</span>(b1, <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NA</span>), <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.1</span>), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>)</span>
<span id="cb50-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(</span>
<span id="cb50-4">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">corners_karney   =</span> geosphere<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">areaPolygon</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_coordinates</span>(b1)[, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]),</span>
<span id="cb50-5">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">densified_karney =</span> geosphere<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">areaPolygon</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_coordinates</span>(b1d)[, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]),</span>
<span id="cb50-6">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">corners_terra    =</span> terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">expanse</span>(terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vect</span>(b1)),</span>
<span id="cb50-7">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">densified_terra  =</span> terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">expanse</span>(terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vect</span>(b1d))</span>
<span id="cb50-8">)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>  corners_karney densified_karney    corners_terra  densified_terra 
    709651946744     711460602143     709651946744     711460602143 </code></pre>
</div>
</div>
<p>The two versions differ in area by 0.25%, which is of the same order as the difference between the sphere and the ellipsoid at this latitude. terra and Karney agree on each version, so both treat the edges as geodesics and neither densifies implicitly.</p>
<p>The same question applies to terra’s <code>transform = TRUE</code> for projected polygons. The vertices are transformed to longitude and latitude and the edges are then treated as geodesics. An edge that is straight in the projection is not a geodesic, so for large polygons the result is the ellipsoidal area of a slightly different polygon. Densifying in the projected coordinates before measuring removes this.</p>
</section>
<section id="summary" class="level2">
<h2 class="anchored" data-anchor-id="summary">Summary</h2>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>input</th>
<th>sf, s2 on (default)</th>
<th>sf, s2 off</th>
<th>terra</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>longitude/latitude, length and perimeter</td>
<td>sphere</td>
<td>ellipsoid</td>
<td>ellipsoid</td>
</tr>
<tr class="even">
<td>longitude/latitude, area</td>
<td>sphere</td>
<td>ellipsoid</td>
<td>ellipsoid</td>
</tr>
<tr class="odd">
<td>projected, length and perimeter</td>
<td>planar</td>
<td>planar</td>
<td>planar</td>
</tr>
<tr class="even">
<td>projected, area</td>
<td>planar</td>
<td>planar</td>
<td>ellipsoid (<code>transform = FALSE</code> for planar)</td>
</tr>
<tr class="odd">
<td>no CRS</td>
<td>planar</td>
<td>planar</td>
<td>planar, with a warning</td>
</tr>
</tbody>
</table>
<p>For longitude and latitude data, terra’s rule can be stated in one line: it always measures on the ellipsoid. sf’s answer depends on an option that is set per session, is on by default, and also determines how predicates and overlays are computed. The consequence is that an sf user who wants ellipsoidal measures has to change the behaviour of their spatial predicates as well, and a user who leaves the default gets a spherical measure whose error varies from +0.45% to -0.89% with latitude, independent of the size of the feature.</p>
<p>Two changes would make each package’s rule simpler to state. In sf, a way to select the ellipsoid for measures independently of the engine used for predicates, for example an argument to the measure functions or a separate option. In terra, extending the <code>transform</code> argument to <code>perim()</code> and <code>distance()</code>, so that projected input is measured on the ellipsoid for every quantity whenever the CRS is known, as it already is for area.</p>
<p>In the meantime, the grid above can be run against any installed versions, and is a quick way to check which number a given function returns.</p>


</section>

 ]]></description>
  <guid>https://hypertidy.org/posts/2026-09-23_measures-sf-terra/</guid>
  <pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>The road to +proj=spilhaus</title>
  <dc:creator>Michael Sumner</dc:creator>
  <link>https://hypertidy.org/posts/2026-09-20_road-to-spilhaus/</link>
  <description><![CDATA[ 






<p>Spilhaus is the map where the oceans are one continuous body and the continents are the ragged edge. It went viral in late 2018, it has been in ArcGIS Pro since 2020, and since March 2025 it is in PROJ, which means it is in GDAL, sf, terra, pyproj, QGIS, PostGIS and everything else that sits on that stack. Almost nobody noticed the last step. I think the whole arc is a tidy illustration of how our software landscape actually works.</p>
<section id="it-was-not-a-new-projection" class="level2">
<h2 class="anchored" data-anchor-id="it-was-not-a-new-projection">It was not a new projection</h2>
<p>Athelstan Spilhaus published ocean-centred maps from 1942, using existing conformal and equal-area projections. The square version people recognise appeared in 1979 and was republished in 1983 and 1991. Mathematically it is an oblique aspect of the Adams World in a Square II projection (Adams, 1925/1929), rotated 45 degrees so the square sits on a corner. Spilhaus chose a centre and an azimuth; he did not invent the maths.</p>
<p>That matters for the story, because “implement Spilhaus” decomposes into two jobs: implement Adams WS2 (elliptic-function conformal maths), and then support an oblique, rotated aspect with the right parameters.</p>
</section>
<section id="viral-then-reverse-engineered" class="level2">
<h2 class="anchored" data-anchor-id="viral-then-reverse-engineered">2018-2020: viral, then reverse-engineered</h2>
<p>The map spread on social media in autumn 2018 with no usable coordinate system behind it. Bojan Savric, David Burrows and Melita Kennedy at Esri did the detective work, published it as a <a href="https://storymaps.arcgis.com/stories/756bcae18d304a1eac140f19f4d5cb3d">storymap</a>, and identified the parameters:</p>
<pre><code>Projection: Adams Square II
Azimuth: 40.17823482
Longitude of centre: 66.94970198 E
Latitude of centre: 49.56371678 S
XY plane rotation: 45</code></pre>
<p>It shipped as a projected coordinate system in ArcGIS 10.8 and ArcGIS Pro 2.5 in <a href="https://www.maproomblog.com/2020/02/the-spilhaus-projection-comes-to-arcgis-pro/">February 2020</a>, and the definitions became ESRI:54098 and ESRI:54099 in Esri’s openly published projection-engine database. So to be fair to that company: the parameters were public, the WKT was public, and the storymap is still the best explainer on the web. What did not happen was any code landing in the open library everyone else depends on.</p>
</section>
<section id="proj-gets-halfway-there-in-three-months" class="level2">
<h2 class="anchored" data-anchor-id="proj-gets-halfway-there-in-three-months">2020: PROJ gets halfway there in three months</h2>
<p>Asger Petersen opened <a href="https://github.com/OSGeo/PROJ/issues/1851">OSGeo/PROJ#1851</a> in January 2020, pointing at the Esri storymap and the parameters. By April, Kristian Evers had added <code>adams_hemi</code>, <code>adams_ws1</code>, <code>adams_ws2</code>, <code>guyou</code> and <code>peirce_q</code> to PROJ (<a href="https://github.com/OSGeo/PROJ/pull/2148">PR #2148</a>, “a first step towards implementing the Spilhaus projection as requested in #1851”), and Even Rouault had mapped Esri’s <code>Adams_Square_II</code> WKT to <code>+proj=adams_ws2</code> and written an iterative inverse so <code>gdalwarp</code> could use it (<a href="https://github.com/OSGeo/PROJ/pull/2157">PR #2157</a>). Both shipped in PROJ 7.1.0.</p>
<p>The maths was there, the oblique/rotated aspect was not, and the issue sat for five years wearing the <code>pinned</code> label, whose stated purpose in the PROJ repo is to “prevent stale-bot from closing an issue”.</p>
<!-- TODO: a comment or two from the 2020-2024 gap in #1851 would make this concrete (ob_tran workarounds? who kept it alive?) -->
</section>
<section id="everyone-else-routes-around" class="level2">
<h2 class="anchored" data-anchor-id="everyone-else-routes-around">2019-2024: everyone else routes around</h2>
<p>While the base layer was stalled, the demand did not go away, and the community did what it always does: reimplemented the forward transform wherever it was standing.</p>
<ul>
<li><a href="https://github.com/SciTools/cartopy/issues/1376">Cartopy issue #1376</a> (October 2019) asked for Spilhaus and asked whether the rotation could live in cartopy or had to come from PROJ. It stayed open until 2025.</li>
<li>Torben Jansen published Observable notebooks for <a href="https://observablehq.com/@toja/adams-world-in-a-square-i-ii">Adams World in a Square I and II</a>, and for <a href="https://observablehq.com/@toja/spilhaus-world-ocean-map-in-a-square">Spilhaus</a>, on d3-geo. <a href="https://observablehq.com/@joewdavies/spilhaus-map">Joe Davies</a> and others followed. This is where the “viral map” crowd already lived, so this is where most Spilhaus maps were actually being made.</li>
<li>Ricardo Lemos published <a href="https://github.com/rtlemos/spilhaus">rtlemos/spilhaus</a>, parallel R and Python scripts with <code>from_lonlat_to_spilhaus_xy</code> and its inverse, with a README that says the quiet part out loud: “Currently, the Spilhaus projection is missing in PROJ, making it unavailable for R and Python enthusiasts, among others: OSGeo/PROJ#1851.”</li>
<li><a href="https://www.nature.com/articles/s41597-023-02309-6">Chen et al.&nbsp;(Scientific Data, 2023)</a> published an oceanographic case for the projection. Their maps were made in ArcGIS Pro 2.5; their open supplement was a “quasi-Spilhaus” d3 notebook based on Jansen’s work, which they note carries “slight error”, and they record that GMT and QGIS could not do it.</li>
</ul>
<p>Each of these gives you one thing: points forward, sometimes points inverse, in one language. None gives you a CRS. You cannot warp a raster with an Observable notebook, <code>st_transform()</code> a polygon layer with a Python script, or hand ESRI:54099 to a colleague and have their tools agree with yours. That is the difference between a projection formula and a projection in PROJ.</p>
</section>
<section id="three-days-in-february" class="level2">
<h2 class="anchored" data-anchor-id="three-days-in-february">2025: three days in February</h2>
<p>Javier Jimenez Shaw opened <a href="https://github.com/OSGeo/PROJ/pull/4401">PR #4401</a> on 16 February 2025 and it was merged on 19 February. It implements <code>+proj=spilhaus</code> for both the sphere and the ellipsoid, using conformal latitudes with an additional correction that, in his words, “was not present in other implementations”. The defaults are Spilhaus’s parameters, so the bare string is the map:</p>
<pre><code>+proj=spilhaus
## equivalent to
+proj=spilhaus +lon_0=66.94970198 +lat_0=-49.56371678 +azi=40.17823482 +rot=45</code></pre>
<p>The same day, Even Rouault’s <a href="https://github.com/OSGeo/PROJ/pull/4402">PR #4402</a> wired ESRI:54099 and ESRI:54098 to <code>+proj=spilhaus</code>, which surfaced the last puzzle: Esri’s coordinates differ from the raw Adams maths by a factor of sqrt(2). PROJ carries that as <code>+k_0=1.4142135...</code> on the Esri definitions, and Keith Jenkins’s reference points from ArcGIS agreed to well under a metre. Interoperability is exactly this kind of unglamorous detail, done in public, with the test points in the PR.</p>
<p><a href="https://github.com/OSGeo/PROJ/releases#release-9.6.0">PROJ 9.6.0</a> was released on 15 March 2025 with “Add Spilhaus projection (#4401)” as one line in the release notes. The <a href="https://proj.org/en/stable/operations/projections/spilhaus.html">documentation page</a> is a parameter table.</p>
</section>
<section id="after-a-very-quiet-arrival" class="level2">
<h2 class="anchored" data-anchor-id="after-a-very-quiet-arrival">After: a very quiet arrival</h2>
<p>What followed:</p>
<ul>
<li><a href="https://github.com/SciTools/cartopy/pull/2529">Cartopy PR #2529</a> (May 2025) added a <code>Spilhaus</code> class, gated on PROJ 9.6 and pyproj 3.7.1. But pyproj 3.7.1 wheels bundle PROJ 9.5.1, so a <code>pip install</code> user got nothing until 3.7.2 in August 2025. Conda users got it sooner. <!-- TODO check bundled PROJ in pyproj 3.7.2 wheels --></li>
<li>A NetBSD user <a href="https://www.mail-archive.com/proj@lists.osgeo.org/msg01414.html">reported</a> 56 of 77 Spilhaus regression tests failing with round-trip errors of tens to hundreds of kilometres, everything else in PROJ passing, almost certainly a libm quirk. Conformal maths at the edges of a square is a stress test for platform floating point.</li>
<li>The <a href="https://hub.qgis.org/map-gallery/tags/spilhaus/">QGIS map gallery</a> got one tagged post: “The beautiful Spilhaus projection (‘oceans in a square’) is new in PROJ 9.6.0.”</li>
<li>The five-year issue closed with the PR and, as far as I can find, no announcement anywhere that ocean people read.</li>
</ul>
<p>Meanwhile, all of this just works, if and only if your PROJ is 9.6 or newer:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(sf)</span>
<span id="cb3-2">x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_transform</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_as_sf</span>(rnaturalearth<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ne_countries</span>()), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=spilhaus"</span>)</span>
<span id="cb3-3"></span>
<span id="cb3-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(terra)</span>
<span id="cb3-5">r <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">project</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rast</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsicurl/https://example.org/global.tif"</span>), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=spilhaus"</span>)</span></code></pre></div></div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode sh code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">gdalwarp</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-t_srs</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=spilhaus"</span> global.tif spilhaus.tif</span></code></pre></div></div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pyproj <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Transformer</span>
<span id="cb5-2">t <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Transformer.from_crs(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4326</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=spilhaus"</span>, always_xy<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span>
<span id="cb5-3">t.transform(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">147.3</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">42.9</span>)</span></code></pre></div></div>
</section>
<section id="what-this-says-about-the-landscape" class="level2">
<h2 class="anchored" data-anchor-id="what-this-says-about-the-landscape">What this says about the landscape</h2>
<p><strong>The base layer is where the leverage is, and where the least attention goes.</strong> Five-plus years of demand produced maybe half a dozen independent reimplementations. One contribution to PROJ made the projection available for raster, vector and raw coordinates, with an inverse, on the ellipsoid, with a database identity, in every downstream stack at once. Nobody had to change a line in GDAL, sf, terra, QGIS or PostGIS.</p>
<p><strong>“It’s in PROJ” is not “it’s in your hands.”</strong> The path from a merged PR to a working function on a user’s machine runs through release cadence, wheel and CRAN binary builds, conda-forge, OSGeo4W, Linux distro freezes and QGIS LTR policy. Six to eighteen months of skew is normal, and it is invisible unless you know to look:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode sh code-with-copy"><code class="sourceCode bash"><span id="cb6-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">projinfo</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--version</span></span></code></pre></div></div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb7-1">sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sf_extSoftVersion</span>()[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PROJ"</span>]</span>
<span id="cb7-2">terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">gdal</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lib =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"proj"</span>)</span></code></pre></div></div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pyproj<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> pyproj.proj_version_str</span>
<span id="cb8-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> osgeo <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> osr<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">;</span> osr.GetPROJVersionMajor()</span></code></pre></div></div>
<p>If folks tried Spilhaus in mid-2025 and got an “unknown projection” error probably concluded it was not supported, not that they were on 9.5 or earlier.</p>
<p><strong>Provenance is lost by default.</strong> Search for the projection and you get the Esri storymap and Observable notebooks; the PROJ documentation page is a parameter table. The people who did the hard, general work are one line in a changelog. This is not a complaint about anyone; it is how the attention economy of open infrastructure works, and it is why I think projects like PROJ and GDAL need people whose job is partly to narrate what they have done.</p>
<p><strong>The vendor question is less interesting than it looks.</strong> Esri published the parameters and the WKT, and that is what the open implementation was built from and tested against. The gap is not secrecy; it was just not done properly until folded into PROJ.</p>
</section>
<section id="next" class="level2">
<h2 class="anchored" data-anchor-id="next">Next</h2>
<p>I am putting together notebooks that walk the chain, PROJ version to pyproj to GDAL to a global bathymetry raster, vectors and city points on one Spilhaus map, partly as a demo and partly to make the case that following upstream is a skill worth teaching.</p>
<p><a href="https://mdsumner.github.io/spilhaus-tracker/">Spilhaus tracker: example python code</a></p>
</section>
<section id="links" class="level2">
<h2 class="anchored" data-anchor-id="links">Links</h2>
<ul>
<li><a href="https://github.com/OSGeo/PROJ/issues/1851">OSGeo/PROJ#1851 Spilhaus projection</a></li>
<li><a href="https://github.com/OSGeo/PROJ/pull/4401">PR #4401 Add Spilhaus projection</a></li>
<li><a href="https://github.com/OSGeo/PROJ/pull/4402">PR #4402 Export ESRI:54099 as PROJ string</a></li>
<li><a href="https://github.com/OSGeo/PROJ/pull/2148">PR #2148 Adams, Guyou, Peirce projections</a></li>
<li><a href="https://github.com/OSGeo/PROJ/pull/2157">PR #2157 Adams Square II ESRI WKT and inverse</a></li>
<li><a href="https://proj.org/en/stable/operations/projections/spilhaus.html">PROJ Spilhaus documentation</a></li>
<li><a href="https://community.esri.com/t5/mapping-blog/the-spilhaus-map-of-the-oceans-will-be-supported/ba-p/883199">Esri: The Spilhaus map of the oceans will be supported in the next release of ArcGIS</a></li>
<li><a href="https://www.nature.com/articles/s41597-023-02309-6">Chen et al.&nbsp;2023, Ocean Sciences with the Spilhaus Projection</a></li>
<li><a href="https://github.com/SciTools/cartopy/issues/1376">Cartopy issue #1376</a> and <a href="https://github.com/SciTools/cartopy/pull/2529">PR #2529</a></li>
<li><a href="https://github.com/rtlemos/spilhaus">rtlemos/spilhaus</a></li>
<li><a href="https://pyproj4.github.io/pyproj/stable/history.html">pyproj changelog</a></li>
</ul>


</section>

 ]]></description>
  <category>projections</category>
  <category>PROJ</category>
  <category>GDAL</category>
  <category>news</category>
  <guid>https://hypertidy.org/posts/2026-09-20_road-to-spilhaus/</guid>
  <pubDate>Sun, 20 Sep 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>R speaks Zax SQL: dbplyr and the tensor-as-table model</title>
  <dc:creator>Michael Sumner</dc:creator>
  <link>https://hypertidy.org/posts/2026-09-17_zax-sql-xrdbi/</link>
  <description><![CDATA[ 






<p>Earthmover announced <a href="https://www.earthmover.io/blog/compute-roadmap">Zax and Zax-SQL</a> this week: a new query engine for chunked multidimensional arrays, fronted by SQL over the PostgreSQL wire protocol and Arrow Flight SQL. The relational mapping they describe is worth quoting:</p>
<blockquote class="blockquote">
<p>It maps the NetCDF data model onto the relational model and lets you query tensors as if they were rows and columns. The mapping is one-way and equivalent to flattening the dataset: coordinates and data variables all appear as columns of the same length, variables of differing dimensionality are lazily broadcast against each other, and the row count is the outer product of the dimensions.</p>
</blockquote>
<p>I want to flag something from the R side: <strong>this virtual schema already has a mature query <em>generator</em> pointed at it</strong>. It’s dbplyr, the tidyverse’s SQL compiler, and we have been building against exactly this flattened tensor-as-table model in <a href="https://github.com/mdsumner/xrdbi">xrdbi</a>, a DBI/dbplyr backend for xarray via reticulate. Same mapping, arrived at independently: coordinates and variables as equal-length columns, row count as the outer product of the dimensions, filters on coordinate columns standing in for array slicing.</p>
<p>The difference is which side of the wire does the work. xrdbi intercepts the dbplyr pipeline <em>before</em> SQL and translates it into xarray selection and reduction calls (filter pushdown to coordinate selection, summarise to dimension reduction). Zax puts the same smarts server-side: the planner turns a WHERE clause into a coordinate selection in multidimensional space, works out which chunks intersect, and streams only those out of object storage.</p>
<p>Which means pointing dplyr at Zax is not a new client. It’s dbplyr doing the one thing it was always built for: generating SQL and sending it down a wire.</p>
<section id="the-climatology-example-from-dplyr" class="level2">
<h2 class="anchored" data-anchor-id="the-climatology-example-from-dplyr">The climatology example, from dplyr</h2>
<p>Earthmover’s headline query is a monthly climatology at a grid point, running against a 125 TB ERA5 Icechunk dataset:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">SELECT</span> DATE_PART(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'month'</span>, valid_time) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">as</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">month</span>,</span>
<span id="cb1-2">       <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">AVG</span>(t2m) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">as</span> t2m_mean,</span>
<span id="cb1-3">       <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">STDDEV</span>(t2m) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">as</span> t2m_std</span>
<span id="cb1-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">FROM</span> era5.<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'single/temporal'</span></span>
<span id="cb1-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">WHERE</span> longitude <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">45.0</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">AND</span> latitude <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">60.0</span></span>
<span id="cb1-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">GROUP</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">BY</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">month</span></span>
<span id="cb1-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">ORDER</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">BY</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">month</span></span></code></pre></div></div>
<p>Here is that query as an ordinary dplyr pipeline, and the SQL dbplyr 2.4.0 generates for it. This is reproducible with no server at all, using dbplyr’s simulated Postgres connection:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(dplyr)</span></code></pre></div></div>
<div class="cell-output cell-output-stderr">
<pre><code>
Attaching package: 'dplyr'</code></pre>
</div>
<div class="cell-output cell-output-stderr">
<pre><code>The following objects are masked from 'package:stats':

    filter, lag</code></pre>
</div>
<div class="cell-output cell-output-stderr">
<pre><code>The following objects are masked from 'package:base':

    intersect, setdiff, setequal, union</code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb6-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(dbplyr)</span></code></pre></div></div>
<div class="cell-output cell-output-stderr">
<pre><code>
Attaching package: 'dbplyr'</code></pre>
</div>
<div class="cell-output cell-output-stderr">
<pre><code>The following objects are masked from 'package:dplyr':

    ident, sql, sql_escape_ident, sql_escape_string</code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb9-1">era5 <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">lazy_frame</span>(</span>
<span id="cb9-2">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">valid_time =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Sys.time</span>(), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">longitude =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">latitude =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">t2m =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,</span>
<span id="cb9-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">con =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">simulate_postgres</span>(), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">.name =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ident_q</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"era5.'single/temporal'"</span>)</span>
<span id="cb9-4">)</span>
<span id="cb9-5"></span>
<span id="cb9-6">era5 <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb9-7">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filter</span>(longitude <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">45</span>, latitude <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb9-8">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">group_by</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">month =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">date_part</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"month"</span>, valid_time)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb9-9">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">summarise</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">t2m_mean =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mean</span>(t2m, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">na.rm =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>),</span>
<span id="cb9-10">            <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">t2m_std  =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sd</span>(t2m, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">na.rm =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb9-11">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">arrange</span>(month) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb9-12">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">show_query</span>()</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>&lt;SQL&gt;
SELECT "month", AVG("t2m") AS "t2m_mean", STDDEV_SAMP("t2m") AS "t2m_std"
FROM (
  SELECT *, date_part('month', "valid_time") AS "month"
  FROM era5.'single/temporal'
  WHERE ("longitude" = 45.0) AND ("latitude" = 60.0)
) AS "q01"
GROUP BY "month"
ORDER BY "month"</code></pre>
</div>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb11-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">SELECT</span> `month`, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">AVG</span>(`t2m`) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">AS</span> `t2m_mean`, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">STDDEV_SAMP</span>(`t2m`) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">AS</span> `t2m_std`</span>
<span id="cb11-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">FROM</span> (</span>
<span id="cb11-3">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">SELECT</span> `q01`.<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>, date_part(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'month'</span>, `valid_time`) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">AS</span> `month`</span>
<span id="cb11-4">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">FROM</span> era5.<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'single/temporal'</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">AS</span> `q01`</span>
<span id="cb11-5">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">WHERE</span> (`longitude` <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">45.0</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">AND</span> (`latitude` <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">60.0</span>)</span>
<span id="cb11-6">) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">AS</span> `q01`</span>
<span id="cb11-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">GROUP</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">BY</span> `month`</span>
<span id="cb11-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">ORDER</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">BY</span> `month`</span></code></pre></div></div>
<p>(Simulated connections print backticks; a live RPostgres connection quotes identifiers with double quotes. On a real connection the table reference is <code>tbl(con, I("era5.'single/temporal'"))</code> rather than <code>ident_q()</code>.)</p>
<p>That is, up to a subquery and spelling, the query from their post. Two details I like: dbplyr writes <code>STDDEV_SAMP</code>, which is what Postgres’s (and DataFusion’s) <code>STDDEV</code> is an alias for, so it is being <em>more</em> explicit, not less compatible; and <code>date_part</code> isn’t a special case - dbplyr passes unknown functions through verbatim, which happens to be exactly the escape hatch a young dialect needs.</p>
<p>The pattern extends naturally. A regional aggregation - the thing that actually exercises chunk-level pushdown - is just a bounding box in the WHERE clause:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb12-1">era5 <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb12-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">filter</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">between</span>(longitude, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">160</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">between</span>(latitude, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">70</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">40</span>),</span>
<span id="cb12-3">         valid_time <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2020-01-01"</span>, valid_time <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2021-01-01"</span>) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb12-4">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">group_by</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">month =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">date_part</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"month"</span>, valid_time)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb12-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">summarise</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">t2m_mean =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mean</span>(t2m, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">na.rm =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">n =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">n</span>()) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb12-6">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">arrange</span>(month)</span></code></pre></div></div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode sql code-with-copy"><code class="sourceCode sql"><span id="cb13-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">SELECT</span> `month`, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">AVG</span>(`t2m`) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">AS</span> `t2m_mean`, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">COUNT</span>(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">AS</span> `n`</span>
<span id="cb13-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">FROM</span> (</span>
<span id="cb13-3">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">SELECT</span> `q01`.<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>, date_part(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'month'</span>, `valid_time`) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">AS</span> `month`</span>
<span id="cb13-4">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">FROM</span> era5.<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'single/temporal'</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">AS</span> `q01`</span>
<span id="cb13-5">  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">WHERE</span></span>
<span id="cb13-6">    (`longitude` <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">BETWEEN</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">100.0</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">AND</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">160.0</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">AND</span></span>
<span id="cb13-7">    (`latitude` <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">BETWEEN</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">70.0</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">AND</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">40.0</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">AND</span></span>
<span id="cb13-8">    (`valid_time` <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'2020-01-01'</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">AND</span></span>
<span id="cb13-9">    (`valid_time` <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'2021-01-01'</span>)</span>
<span id="cb13-10">) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">AS</span> `q01`</span>
<span id="cb13-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">GROUP</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">BY</span> `month`</span>
<span id="cb13-12"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">ORDER</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">BY</span> `month`</span></code></pre></div></div>
<p>A <code>BETWEEN</code> on a coordinate column is a hyperslab. The R user wrote a filter; the Zax planner decides what that means in terms of I/O. That division of labour is precisely the one dbplyr assumes about every database it talks to.</p>
</section>
<section id="wiring-it-up" class="level2">
<h2 class="anchored" data-anchor-id="wiring-it-up">Wiring it up</h2>
<p>Two routes, both using infrastructure R already has:</p>
<ul>
<li><strong>Postgres wire</strong>: <code>RPostgres::dbConnect()</code> to a Zax-SQL endpoint, then <code>tbl()</code> and ordinary dplyr verbs. dbplyr’s Postgres dialect should carry most pipelines, since Zax-SQL uses Apache DataFusion for the relational processing and DataFusion’s SQL is Postgres-flavoured.</li>
<li><strong>Arrow Flight SQL</strong>: the better wire for bulk results - Arrow record batches straight into R with no text protocol in the middle. The ADBC Flight SQL driver via <code>adbcdrivermanager</code>, with DBI compatibility through <code>adbi</code>, gives dbplyr the same interface.</li>
</ul>
<p>Where it will misfire is dialect edges: DataFusion is not Postgres, so some translations (string and date functions, window frames) will emit SQL that Zax rejects. The remedy is the classic thin-backend move - a connection subclass with a <code>sql_translation()</code> method mapping to DataFusion names. That is a couple of hundred lines, the duckplyr-shaped package, and only worth writing once the friction is demonstrated rather than assumed.</p>
</section>
<section id="why-this-matters-beyond-a-demo" class="level2">
<h2 class="anchored" data-anchor-id="why-this-matters-beyond-a-demo">Why this matters beyond a demo</h2>
<p><strong>A correctness oracle, both ways.</strong> xrdbi and Zax now implement the same tensor-to-relation flattening on either side of a wire. Running identical pipelines against xrdbi-local (xarray on your laptop) and Zax-remote (125 TB of Icechunk) cross-checks the fiddly semantics: broadcast order, coordinate column behaviour, aggregation over flattened dimensions. Where they agree, R users get a strong story - same dplyr code, laptop or petabyte. Where they disagree, one of us has found a bug or an underspecified corner of the model, and either outcome is useful feedback while the dialect is young.</p>
<p><strong>Convergent evolution.</strong> Zax-SQL, xarray-sql, Beacon, and SedonaDB’s raster support are all landing on SQL-over-flattened-tensors from different directions (Earthmover’s post generously points at the other three). When the same interface emerges independently from a Rust query engine, a serverless Python project, a NetCDF client-server system, and a spatial SQL engine - and, from the R side, from a dbplyr backend - that is probably not a fad.</p>
<p><strong>The relational layer is the contract.</strong> In work factoring xrdbi’s core into an engine-agnostic translation layer (working name arrplyr), the plan had been a JSON-ish intermediate representation - variables, coordinate windows, strides, axis reductions - handed to engines like xarray, GDAL multidim, or pizzarr. The arrival of SQL-speaking array engines simplifies one whole branch of that picture: for Zax and its cousins, no IR is needed, because dbplyr’s native SQL generation <em>is</em> the contract, and the flattened relational model is the interchange format. The IR earns its keep only for engines with no SQL front door. And Earthmover’s roadmap - queries as saved, versioned, shareable objects; views as unmaterialized derived datasets - is the recipe-not-payload position: a query is a description of data, and it is the description you should store and exchange, not the bytes.</p>
<p><strong>Agents, incidentally.</strong> Earthmover’s argument for SQL-first is partly that LLMs write competent SQL but poor memory-aware chunked-array code. The same argument holds one level up: agents and humans alike write competent <em>dplyr</em>, and dbplyr removes even the SQL from view. The grammar R users have applied to data frames for a decade now reaches petabyte tensor stores with no new API at all.</p>
</section>
<section id="trying-it" class="level2">
<h2 class="anchored" data-anchor-id="trying-it">Trying it</h2>
<p>Zax-SQL is live on every Arraylake plan including the free Community Tier: create an organization, subscribe to a Marketplace dataset (ERA5 is there), spin up a Zax-SQL service, and connect with RPostgres exactly as you would to any Postgres. The dplyr snippets above should be a working starting point, and <code>show_query()</code> shows you what is crossing the wire.</p>
<p>I plan to write up a fuller “same table, two engines” comparison - identical pipelines against xrdbi and Zax with the results diffed. If you are interested in the R side of this (or the arrplyr factoring), issues and discussion at <a href="https://github.com/mdsumner/xrdbi">xrdbi</a> are welcome.</p>
</section>
<section id="links" class="level2">
<h2 class="anchored" data-anchor-id="links">Links</h2>
<ul>
<li>Earthmover, “Our compute roadmap: introducing Zax SQL”: https://www.earthmover.io/blog/compute-roadmap</li>
<li>Zax-SQL docs: https://docs.earthmover.io/compute/sql</li>
<li>xrdbi: https://github.com/mdsumner/xrdbi</li>
<li>dbplyr: https://dbplyr.tidyverse.org</li>
<li>Apache DataFusion: https://datafusion.apache.org</li>
<li>xarray-sql: https://xql.systems/xarray-sql/latest/</li>
<li>Beacon: https://maris-development.github.io/beacon/</li>
<li>SedonaDB: https://sedona.apache.org/sedonadb/latest/</li>
<li>Earthmover, “Tensors vs.&nbsp;tables”: https://www.earthmover.io/blog/tensors-vs-tables/</li>
</ul>


</section>

 ]]></description>
  <category>code</category>
  <category>news</category>
  <category>zarr</category>
  <category>xrdbi</category>
  <category>dbplyr</category>
  <guid>https://hypertidy.org/posts/2026-09-17_zax-sql-xrdbi/</guid>
  <pubDate>Thu, 17 Sep 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Writing controlledburn output in parallel</title>
  <dc:creator>Michael Sumner</dc:creator>
  <link>https://hypertidy.org/posts/2026-09-03_controlled-burn/</link>
  <description><![CDATA[ 






<p>In <a href="../2026-08-19_antarctica-icefree-rasterizing/">Rasterizing ice-free Antarctica</a> the rasterization itself was fast, and the slow part was the file. The write loop visited about 8000 super-tiles, materializing each one from the sparse burn and pushing it into a COG through a single dataset handle. That handle is the whole problem: the COG driver’s create path writes a temporary GeoTIFF and then rewrites it on close, and neither step tolerates a second writer.</p>
<p>Every iteration of that loop is independent - crop, materialize, write - and the burn object it reads from is 27 MB. The only thing shared is the sink. So change the sink.</p>
<section id="why-zarr" class="level2">
<h2 class="anchored" data-anchor-id="why-zarr">Why Zarr</h2>
<p>A Zarr array is a small metadata file plus one file per chunk. Two processes writing different chunks never touch the same bytes, so there is nothing to coordinate. GDAL’s Zarr driver also declines to write a chunk that is entirely fill value (it will even unlink an existing one), which means the store on disk <em>is</em> the sparse representation: chunks over open ocean and interior ice sheet do not exist.</p>
<p>The plan is</p>
<ol type="1">
<li>create the empty store once, in the parent, with GDAL</li>
<li>open it in update mode from each worker, with its own handle, and write whole chunks</li>
<li>if a COG is still wanted, make it with one <code>translate()</code> at the end</li>
</ol>
</section>
<section id="instantiate" class="level2">
<h2 class="anchored" data-anchor-id="instantiate">Instantiate</h2>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(controlledburn)</span>
<span id="cb1-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(gdalraster)</span>
<span id="cb1-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(mirai)</span>
<span id="cb1-4"></span>
<span id="cb1-5">ext <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> ba<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>extent</span>
<span id="cb1-6">dm  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> ba<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>dimension</span>
<span id="cb1-7">chunk_size <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2048</span>L</span>
<span id="cb1-8">zarr_path  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">file.path</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tempdir</span>(), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rema_approx.zarr"</span>)</span>
<span id="cb1-9"></span>
<span id="cb1-10">ds <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">create</span>(</span>
<span id="cb1-11">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">format =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Zarr"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">dst_filename =</span> zarr_path,</span>
<span id="cb1-12">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">xsize =</span> dm[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ysize =</span> dm[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">nbands =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">dataType =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Int32"</span>,</span>
<span id="cb1-13">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">options =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">paste0</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"BLOCKSIZE="</span>, chunk_size, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">","</span>, chunk_size),</span>
<span id="cb1-14">              <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"COMPRESS=ZSTD"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"FORMAT=ZARR_V2"</span>),</span>
<span id="cb1-15">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">return_obj =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb1-16">ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">setGeoTransform</span>(vaster<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">extent_dim_to_gt</span>(ext, dm))</span>
<span id="cb1-17">ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">setProjection</span>(raster_info<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>projection<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>Wkt)</span>
<span id="cb1-18">ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">setNoDataValue</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">band =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># fill_value; all-zero chunks are never written</span></span>
<span id="cb1-19">ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">close</span>()</span></code></pre></div></div>
</div>
<p>The 4x4 “super-tile” from the last post is gone. A chunk write is one file write, so the chunk is the natural unit of parallel work; 2048 keeps each dense allocation at 16 MB for Int32. (If 512-pixel chunks are wanted for cloud reads, set <code>BLOCKSIZE=512,512</code> and keep iterating over 2048 windows; GDAL splits each write into 16 chunk files that are still disjoint per worker.)</p>
</section>
<section id="populate" class="level2">
<h2 class="anchored" data-anchor-id="populate">Populate</h2>
<p>The tile index and the pre-filter to rows that carry data are unchanged. The worker opens the store in update mode, writes a batch of chunks, and closes. It calls nothing that dirties metadata, so <code>.zarray</code> and <code>.zmetadata</code> are not rewritten on close.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1">write_tiles <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(tile_batch, zarr_path) {</span>
<span id="cb2-2">  ds <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> methods<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">new</span>(gdalraster<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span>GDALRaster, zarr_path, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">read_only =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>)</span>
<span id="cb2-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">on.exit</span>(ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">close</span>(), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">add =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb2-4">  n <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>L</span>
<span id="cb2-5">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> (i <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq_len</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nrow</span>(tile_batch))) {</span>
<span id="cb2-6">    t <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> tile_batch[i, ]</span>
<span id="cb2-7">    sub <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> controlledburn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">crop_burn</span>(ba, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(t<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>xmin, t<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>xmax, t<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>ymin, t<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>ymax))</span>
<span id="cb2-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nrow</span>(sub<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>runs) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;&amp;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nrow</span>(sub<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>edges) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">next</span></span>
<span id="cb2-9">    mat <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> controlledburn<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">materialize_chunk</span>(sub, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fun =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"id"</span>)</span>
<span id="cb2-10">    mat[<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">is.na</span>(mat)] <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>L</span>
<span id="cb2-11">    ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">write</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">band =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">xoff =</span> t<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>offset_x, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">yoff =</span> t<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>offset_y,</span>
<span id="cb2-12">             <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">xsize =</span> t<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>ncol, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ysize =</span> t<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>nrow, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">rasterData =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.integer</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">t</span>(mat)))</span>
<span id="cb2-13">    n <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> n <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L</span>
<span id="cb2-14">  }</span>
<span id="cb2-15">  n</span>
<span id="cb2-16">}</span>
<span id="cb2-17"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">environment</span>(write_tiles) <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">globalenv</span>()</span>
<span id="cb2-18"></span>
<span id="cb2-19"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">daemons</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">24</span>)</span>
<span id="cb2-20"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">everywhere</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(controlledburn), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ba =</span> ba)   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ships ba once per daemon</span></span>
<span id="cb2-21"></span>
<span id="cb2-22">batches <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">split</span>(tiles, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cut</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq_len</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nrow</span>(tiles)), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">24</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>L, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">labels =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>))</span>
<span id="cb2-23"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">system.time</span>({</span>
<span id="cb2-24">  res <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mirai_map</span>(batches, write_tiles, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">.args =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">zarr_path =</span> zarr_path))[.progress]</span>
<span id="cb2-25">})</span>
<span id="cb2-26"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">daemons</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span></code></pre></div></div>
</div>
<p>Two <code>mirai</code> details that cost me a round trip. Objects passed through <code>...</code> to <code>everywhere()</code> are assigned into each daemon’s global environment, which is exactly the “send the 27 MB once” behaviour wanted here; a plain <code>&lt;-</code> inside the expression is evaluated in a clean environment and lost (use <code>&lt;&lt;-</code> if you assign there). And reparenting the worker function to <code>globalenv()</code> stops it dragging the Quarto chunk environment along with every task.</p>
</section>
<section id="result" class="level2">
<h2 class="anchored" data-anchor-id="result">Result</h2>
<p>On a 32-cpu, 64 GB machine with 24 daemons:</p>
<pre><code>   user  system elapsed
  ...     ...    28.0</code></pre>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(terra)</span>
<span id="cb4-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rast</span>(zarr_path)</span>
<span id="cb4-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; class       : SpatRaster</span></span>
<span id="cb4-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; size        : 182568, 170318, 1  (nrow, ncol, nlyr)</span></span>
<span id="cb4-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; resolution  : 32, 32  (x, y)</span></span>
<span id="cb4-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; extent      : -2700096, 2750080, -2500096, 3342080  (xmin, xmax, ymin, ymax)</span></span>
<span id="cb4-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; source      : rema_approx.zarr</span></span>
<span id="cb4-8"></span>
<span id="cb4-9">f0 <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> fs<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dir_ls</span>(zarr_path, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">recurse =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb4-10"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">length</span>(f0)</span>
<span id="cb4-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1] 722</span></span>
<span id="cb4-12"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sum</span>(fs<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">file_size</span>(f0))</span>
<span id="cb4-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; 9.48M</span></span></code></pre></div></div>
</div>
<p>The grid could hold ceiling(170318 / 2048) x ceiling(182568 / 2048) = 84 x 90 = 7560 chunks. About 718 of them exist. That is 9.5% of the continent’s bounding box with any rock in it at the 2048-pixel scale, a number obtained by listing a directory, and the whole store is 9.5 MB - alongside the 27 MB burn object, the 5.6 MB DEFLATE COG, and the 613 MB uncompressed AADC product from last time, it is another point on the same continuum.</p>
</section>
<section id="coda-the-cog-is-now-cheap" class="level2">
<h2 class="anchored" data-anchor-id="coda-the-cog-is-now-cheap">Coda: the COG is now cheap</h2>
<p>If the COG is still the deliverable, the expensive part - materializing every tile - has already happened in parallel. What remains is one sequential translate, I/O-bound, with the COG driver’s own threads handling compression and overviews:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">set_config_option</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"GDAL_NUM_THREADS"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ALL_CPUS"</span>)</span>
<span id="cb5-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">translate</span>(zarr_path, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">file.path</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tempdir</span>(), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rema_approx.tif"</span>),</span>
<span id="cb5-3">          <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">cl_arg =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-of"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"COG"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-co"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"BLOCKSIZE=512"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-co"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"SPARSE_OK=YES"</span>,</span>
<span id="cb5-4">                     <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-co"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"COMPRESS=DEFLATE"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-co"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"OVERVIEW_RESAMPLING=NEAREST"</span>,</span>
<span id="cb5-5">                     <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-co"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"NUM_THREADS=ALL_CPUS"</span>))</span></code></pre></div></div>
</div>
<p>Things I have not done yet, and want to:</p>
<ul>
<li>Time this step, so there is an honest end-to-end “how long to get a COG” number against the original serial loop.</li>
<li>Check whether reading a sparse Zarr into the COG driver preserves sparsity as well as writing blocks directly did (<code>SPARSE_OK=YES</code> should skip the absent chunks, which come back as nodata).</li>
<li>The Zarr driver has no classic-API overviews; for a Zarr-native product the pyramid would be additional arrays (the multiscales convention in GDAL 3.13 for Zarr V3), which is a separate post.</li>
</ul>
</section>
<section id="appendix-populating-without-gdal-in-the-workers" class="level2">
<h2 class="anchored" data-anchor-id="appendix-populating-without-gdal-in-the-workers">Appendix: populating without GDAL in the workers</h2>
<p>The other question I set out with was whether GDAL could instantiate the store and something more generic could fill it. It can. A Zarr V2 chunk is: the full chunk shape (edge chunks padded with <code>fill_value</code>), C order, the declared dtype (<code>&lt;i4</code> here), passed through the declared compressor, saved as <code>row.col</code> in the array directory, 0-based. If the store is created with <code>COMPRESS=ZLIB</code>, base R already has the encoder, because <code>memCompress(type = "gzip")</code> emits a zlib stream, which is what the numcodecs <code>zlib</code> codec expects.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb6-1">write_chunk_raw <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(mat, row_chunk, col_chunk, array_dir, chunk_size) {</span>
<span id="cb6-2">  full <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">matrix</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>L, chunk_size, chunk_size)</span>
<span id="cb6-3">  full[<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq_len</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nrow</span>(mat)), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq_len</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ncol</span>(mat))] <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> mat</span>
<span id="cb6-4">  bytes <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">writeBin</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.integer</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">t</span>(full)), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">raw</span>(), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>L, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">endian =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"little"</span>)</span>
<span id="cb6-5">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">writeBin</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">memCompress</span>(bytes, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">type =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gzip"</span>),</span>
<span id="cb6-6">           <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">file.path</span>(array_dir, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sprintf</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"%d.%d"</span>, row_chunk, col_chunk)))</span>
<span id="cb6-7">}</span></code></pre></div></div>
</div>
<p>This has no shared writes of any kind, not even the metadata files, and it is the mirror image of a Zarr <em>reader</em>, which is where I am headed with <code>zaro</code>. Open questions to explore:</p>
<ul>
<li>ZSTD or Blosc from R without pulling in a heavy dependency, since ZLIB is the slow codec.</li>
<li>Whether the worker should write chunks at all, or emit <code>(chunk key, bytes)</code> pairs so the sink can be a local directory, an object store, or something that builds a kerchunk/Icechunk reference set instead.</li>
<li>Doing the same for the coverage-mode burn (<code>Float32</code>, <code>&lt;f4</code>), where the chunks are no longer mostly a single value and the compressor choice matters more.</li>
</ul>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>A note on the surrounding tools
</div>
</div>
<div class="callout-body-container callout-body">
<p>As before, this leans on tools in active development - <code>controlledburn</code>, <code>grout</code>, <code>vaster</code> - and on <code>gdalraster</code> and <code>mirai</code>, which are not. The Zarr driver’s behaviour under concurrent update (metadata rewritten only when dirtied, empty chunks skipped) is what I observed here and what a reading of the driver suggests, but it is not a documented guarantee; check the <code>.zmetadata</code> mtime and the chunk-file count on a small grid before trusting it on a large one.</p>
</div>
</div>


</section>

 ]]></description>
  <category>controlledburn</category>
  <category>rasterization</category>
  <category>Antarctica</category>
  <category>GDAL</category>
  <category>Zarr</category>
  <category>mirai</category>
  <guid>https://hypertidy.org/posts/2026-09-03_controlled-burn/</guid>
  <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Rasterizing ice-free Antarctica</title>
  <dc:creator>Michael Sumner</dc:creator>
  <link>https://hypertidy.org/posts/2026-08-19_antarctica-icefree-rasterizing/</link>
  <description><![CDATA[ 






<p>Antarctica’s exposed rock — the ice-free ground — has been mapped as 25,954 polygons covering nunataks, coastal outcrops, and mountain ranges across the continent. The Reference Elevation Model of Antarctica (REMA) provides a 2m DEM mosaic of the entire ice sheet, tiled at multiple resolutions.</p>
<p>What does it take to rasterize those polygons onto the REMA grid? At 32m resolution, the grid is 170318 x 182568 pixels. A raster at that size would require enormous memory, but controlledburn does it in under a second, producing 30-150 MB of sparse output, then serves up any individual tile on demand.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center" data-bs-toggle="collapse" data-bs-target=".callout-1-contents" aria-controls="callout-1" aria-expanded="true" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>A note on the surrounding tools
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-1" class="callout-1-contents callout-collapse collapse show">
<div class="callout-body-container callout-body">
<p>This post reaches for <code>vapour</code>, <code>terra</code>, <code>gdalraster</code>, <code>grout</code>, and a few others alongside <code>controlledburn</code> itself, without much ceremony about why one is used here and another there. That’s a fair reflection of where things stand: <code>controlledburn</code> is under heavy, active development, and the tooling around it — how you get a grid spec, read geometry, or write a COG — has mattered less than getting the core right. Some of this will likely consolidate as the package settles. Treat the package mix here as a snapshot of current convenience, not a considered API.</p>
</div>
</div>
</div>
<section id="setup" class="level2">
<h2 class="anchored" data-anchor-id="setup">Setup</h2>
<p>Three cloud-hosted datasets, read directly via GDAL virtual filesystem paths.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># REMA 32m DEM mosaic — we read only the grid spec, no pixel values</span></span>
<span id="cb1-2">dsn <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">paste0</span>(</span>
<span id="cb1-3">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsicurl/https://raw.githubusercontent.com/mdsumner/rema-ovr/"</span>,</span>
<span id="cb1-4">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"main/rema-vrt/32m_dem_tiles.vrt"</span>)</span>
<span id="cb1-5"></span>
<span id="cb1-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Antarctic rock outcrop polygons (v7.3, Burton-Johnson et al.)</span></span>
<span id="cb1-7">shp <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">paste0</span>(</span>
<span id="cb1-8">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsizip/{/vsicurl/https://github.com/AustralianAntarcticDivision/"</span>,</span>
<span id="cb1-9">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rema.proc/raw/refs/heads/master/01_rock_classification/"</span>,</span>
<span id="cb1-10">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Medium_resolution_vector_polygons_of_Antarctic_rock_outcrop_-_"</span>,</span>
<span id="cb1-11">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"VERSION_7.3.zip}/Medium resolution vector polygons of Antarctic "</span>,</span>
<span id="cb1-12">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rock outcrop - VERSION 7.3/"</span>,</span>
<span id="cb1-13">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"add_rock_outcrop_medium_res_polygon_v7.3.gpkg"</span>)</span>
<span id="cb1-14"></span>
<span id="cb1-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># REMA tile index — for choosing a view window</span></span>
<span id="cb1-16">rema_tiles <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">paste0</span>(</span>
<span id="cb1-17">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsizip/{/vsicurl/https://data.pgc.umn.edu/elev/dem/setsm/REMA/"</span>,</span>
<span id="cb1-18">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"indexes/REMA_Mosaic_Index_latest_shp.zip}/REMA_Mosaic_Index_v2_shp"</span>)</span></code></pre></div></div>
</div>
</section>
<section id="read" class="level2">
<h2 class="anchored" data-anchor-id="read">Read</h2>
<p>The raster is never opened — <code>vapour_raster_info()</code> reads only the metadata (extent, dimensions, CRS). The polygons are read as raw WKB bytes.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(terra)</span></code></pre></div></div>
<div class="cell-output cell-output-stderr">
<pre><code>terra 1.9.44</code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1">tiles <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vect</span>(rema_tiles)</span></code></pre></div></div>
<div class="cell-output cell-output-stderr">
<pre><code>Warning: [vect] Reading layer: REMA_Mosaic_Index_v2_2m
Other layers: REMA_Mosaic_Index_v2_10m, REMA_Mosaic_Index_v2_32m</code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb6-1">raster_info <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> vapour<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vapour_raster_info</span>(dsn)</span>
<span id="cb6-2">rock_info <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> vapour<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vapour_layer_info</span>(shp)</span></code></pre></div></div>
<div class="cell-output cell-output-stderr">
<pre><code>Warning: Unhandled projection method Polar_Stereographic (GDAL error 6)</code></pre>
</div>
<div class="cell-output cell-output-stderr">
<pre><code>Warning: unable export SRS to XML</code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb9-1">rock <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wkb</span>(vapour<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vapour_read_geometry</span>(shp),</span>
<span id="cb9-2">                 <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> rock_info<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>projection<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>Wkt)</span>
<span id="cb9-3"></span>
<span id="cb9-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cat</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sprintf</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Grid: %s × %s pixels (%.0f billion cells)</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>,</span>
<span id="cb9-5">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">format</span>(raster_info<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>dimension[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">big.mark =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">","</span>),</span>
<span id="cb9-6">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">format</span>(raster_info<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>dimension[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">big.mark =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">","</span>),</span>
<span id="cb9-7">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">prod</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.numeric</span>(raster_info<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>dimension)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e9</span>))</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>Grid: 170,318 × 182,568 pixels (31 billion cells)</code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb11-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cat</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sprintf</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Polygons: %s geometries</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>,</span>
<span id="cb11-2">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">format</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">length</span>(rock), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">big.mark =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">","</span>)))</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>Polygons: 25,954 geometries</code></pre>
</div>
</div>
</section>
<section id="rasterize-the-continent" class="level2">
<h2 class="anchored" data-anchor-id="rasterize-the-continent">Rasterize the continent</h2>
<p>One call rasterizes every polygon onto the full continental grid. The output is a sparse table of run-length encoded interior cells — no dense matrix is allocated, no pixel values are touched.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb13-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(controlledburn)</span>
<span id="cb13-2"></span>
<span id="cb13-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">system.time</span>({</span>
<span id="cb13-4">  ba <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">burn</span>(rock, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">extent =</span> raster_info<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>extent,</span>
<span id="cb13-5">             <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">dimension =</span> raster_info<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>dimension, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">mode =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"approx"</span>)</span>
<span id="cb13-6">})</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>   user  system elapsed 
  0.294   0.056   0.350 </code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb15-1">ba</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>&lt;controlledburn&gt; 170318 x 182568 grid, 25954 geometries
  runs:   1704432 (70650819 interior cells)
  edges:  0 polygon boundary cells
  sparsity: 99.8% empty</code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb17-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cat</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sprintf</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">Object size: %.0f MB</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.numeric</span>(lobstr<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">obj_size</span>(ba)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1e6</span>))</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>
Object size: 27 MB</code></pre>
</div>
</div>
<p>Coverage mode computes exact boundary fractions — slower, but the total burned area equals the true polygon area to floating-point precision:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb19-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">system.time</span>({</span>
<span id="cb19-2">  be <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">burn</span>(rock, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">extent =</span> raster_info<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>extent,</span>
<span id="cb19-3">             <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">dimension =</span> raster_info<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>dimension, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">mode =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"coverage"</span>)</span>
<span id="cb19-4">})</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>   user  system elapsed 
  4.484   0.287   4.771 </code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb21-1">be</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>&lt;controlledburn&gt; 170318 x 182568 grid, 25954 geometries
  runs:   1662668 (66525907 interior cells)
  edges:  6548354 polygon boundary cells
  sparsity: 99.8% empty</code></pre>
</div>
</div>
</section>
<section id="look-at-a-tile" class="level2">
<h2 class="anchored" data-anchor-id="look-at-a-tile">Look at a tile</h2>
<p>The sparse output covers the whole continent but we can extract any sub-window instantly with <code>crop_burn()</code>. Here: a 50 km REMA tile of the Windmill Islands, near Casey station.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb23" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb23-1">ll <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cbind</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lon =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">110.3</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lat =</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">66.3</span>)</span>
<span id="cb23-2">xy <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> reproj<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">reproj_xy</span>(ll, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">crs</span>(tiles), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">source =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>)</span>
<span id="cb23-3">idx <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">which</span>(terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">is.related</span>(tiles, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vect</span>(xy), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"intersects"</span>))</span>
<span id="cb23-4">tile_ex <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.vector</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ext</span>(tiles[idx, ]))</span></code></pre></div></div>
</div>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb24" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb24-1">ma <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">materialize_chunk</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">crop_burn</span>(ba, tile_ex))</span>
<span id="cb24-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cat</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sprintf</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Tile: %d × %d pixels</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ncol</span>(ma), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nrow</span>(ma)))</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>Tile: 1570 × 1570 pixels</code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb26" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb26-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Rock polygons that overlap this tile</span></span>
<span id="cb26-2">rock_idx <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">unlist</span>(</span>
<span id="cb26-3">  geos<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_strtree_query</span>(</span>
<span id="cb26-4">    geos<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_strtree</span>(geos<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_geos_geometry</span>(rock)),</span>
<span id="cb26-5">    tiles[idx, ]))</span>
<span id="cb26-6"></span>
<span id="cb26-7"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">par</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">bg =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#1a1a2e"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">mar =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb26-8"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(tiles[idx, ], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">col =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NA</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">border =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NA</span>,</span>
<span id="cb26-9">     <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">main =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Windmill Islands rock outcrop — 32m"</span>,</span>
<span id="cb26-10">     <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">col.main =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"white"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">col.axis =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"grey60"</span>)</span>
<span id="cb26-11">ximage<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ximage</span>(ma, tile_ex, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">add =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>,</span>
<span id="cb26-12">               <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">col =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#1a1a2e"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#e8d5b7"</span>))</span>
<span id="cb26-13"></span>
<span id="cb26-14"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(rock[rock_idx], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">add =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">border =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#e05780"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lwd =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span>)</span></code></pre></div></div>
<div class="cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://hypertidy.org/posts/2026-08-19_antarctica-icefree-rasterizing/index_files/figure-html/tile-approx-1.png" class="img-fluid figure-img" width="768"></p>
<figcaption>Rock outcrop near Windmill Islands, approx mode (cell-centre rule). White = exposed rock, dark = ice or ocean. Pink outlines are the source polygon boundaries.</figcaption>
</figure>
</div>
</div>
</div>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb27" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb27-1">mc <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">materialize_chunk</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">crop_burn</span>(be, tile_ex))</span>
<span id="cb27-2"></span>
<span id="cb27-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">par</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">bg =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#1a1a2e"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">mar =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb27-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(tiles[idx, ], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">col =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NA</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">border =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NA</span>,</span>
<span id="cb27-5">     <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">main =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Windmill Islands rock outcrop — 32m, exact coverage"</span>,</span>
<span id="cb27-6">     <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">col.main =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"white"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">col.axis =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"grey60"</span>)</span>
<span id="cb27-7">ximage<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ximage</span>(mc, tile_ex, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">add =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>,</span>
<span id="cb27-8">               <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">col =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">hcl.colors</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">64</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Lajolla"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">rev =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>))</span>
<span id="cb27-9"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(rock[rock_idx], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">add =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">border =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#e05780"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lwd =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.6</span>)</span></code></pre></div></div>
<div class="cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://hypertidy.org/posts/2026-08-19_antarctica-icefree-rasterizing/index_files/figure-html/tile-coverage-1.png" class="img-fluid figure-img" width="768"></p>
<figcaption>Same view, coverage mode. Boundary cells carry exact fractional weights — visible as soft edges where the polygon boundary crosses pixel boundaries.</figcaption>
</figure>
</div>
</div>
</div>
<p>At 32m resolution the two modes are nearly identical. The difference is sub-pixel boundary treatment:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb28" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb28-1">n_diff <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sum</span>(ma <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!=</span> mc)</span>
<span id="cb28-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cat</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sprintf</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Cells that differ: %s out of %s (%.3f%%)</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>,</span>
<span id="cb28-3">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">format</span>(n_diff, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">big.mark =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">","</span>),</span>
<span id="cb28-4">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">format</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">length</span>(ma), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">big.mark =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">","</span>),</span>
<span id="cb28-5">    <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> n_diff <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">length</span>(ma)))</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>Cells that differ: 7,360 out of 2,464,900 (0.299%)</code></pre>
</div>
</div>
</section>
<section id="summary-of-the-lazy-rasterization-process" class="level2">
<h2 class="anchored" data-anchor-id="summary-of-the-lazy-rasterization-process">Summary of the lazy rasterization process</h2>
<p>controlledburn burned 25,954 polygons onto a 31-billion-cell grid in under a second, producing a sparse run-length table small enough to hold in memory. Any tile can be extracted and materialized on demand. The raster was never read — only its grid specification. The polygons were never validated or reprojected — just raw WKB bytes from a cloud-hosted GeoPackage.</p>
<p>This is rasterization as an O(perimeter) operation: the cost scales with the complexity of the polygon boundaries, not the number of pixels in the grid. The sparse output is a reusable intermediate — downstream processing (zonal statistics, masking, tiling) operates on the same table without re-burning.</p>
</section>
<section id="creating-a-raster-file" class="level2">
<h2 class="anchored" data-anchor-id="creating-a-raster-file">Creating a raster file</h2>
<p>Now we have a very dense version of our rock layer as a latent raster, we can materialize arbitrary windows of it and write them to an actual raster file. The strategy: create a SPARSE_OK Cloud-Optimized GeoTIFF (COG), iterate over spatial tiles, crop the sparse burn result to each tile, materialize the dense matrix, and write it via <code>gdalraster</code>. Tiles with no burned pixels are skipped entirely — SPARSE_OK means unwritten tiles cost nothing on disk.</p>
<section id="why-crop_burn" class="level3">
<h3 class="anchored" data-anchor-id="why-crop_burn">Why <code>crop_burn()</code>?</h3>
<p><code>crop_burn()</code> is the spatial subsetting workhorse. Given a <code>controlledburn</code> object covering an arbitrarily large grid, it filters the sparse tables (runs, edges, lines, points) to a target extent and re-bases row/col indices to 1. No dense matrix is ever allocated — it is pure data-frame filtering, so the cost depends only on how many run-length entries fall inside the window, not on how many pixels the full grid contains. This is what makes it feasible to iterate over thousands of tiles without blowing up memory: each iteration crops, materializes a small dense block, writes it, and discards it.</p>
</section>
<section id="why-super-tiles-instead-of-native-512512-blocks" class="level3">
<h3 class="anchored" data-anchor-id="why-super-tiles-instead-of-native-512512-blocks">Why super-tiles instead of native 512×512 blocks?</h3>
<p>The COG internal tiling is 512×512, but writing one 512×512 block at a time means ~120,000 write calls for the full grid. GDAL’s <code>RasterIO()</code> accepts arbitrary windows — they don’t have to align with the internal block grid — but it works best when the window is a multiple of the native block size. Grouping into “super-tiles” of 2048×2048 (4×4 native blocks) brings the tile count down to ~8,000 while keeping each dense allocation under 16 MB. The dense output from <code>materialize_chunk()</code> is <em>not</em> itself tiled; it is a plain row-major matrix. The tiling structure exists only in the GeoTIFF container and is handled entirely by GDAL on write.</p>
</section>
<section id="the-write-loop" class="level3">
<h3 class="anchored" data-anchor-id="the-write-loop">The write loop</h3>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb30" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb30-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(controlledburn)</span>
<span id="cb30-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(gdalraster)</span>
<span id="cb30-3"></span>
<span id="cb30-4">outfile <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">file.path</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tempdir</span>(), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rema_approx.tif"</span>)</span>
<span id="cb30-5">block_size <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">512</span>L</span>
<span id="cb30-6"></span>
<span id="cb30-7">ext <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> ba<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>extent</span>
<span id="cb30-8">dm  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> ba<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>dimension</span>
<span id="cb30-9">gdalraster<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">set_config_option</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"GDAL_NUM_THREADS"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ALL_CPUS"</span>)</span>
<span id="cb30-10"></span>
<span id="cb30-11">ds <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">create</span>(</span>
<span id="cb30-12">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">format       =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"COG"</span>,</span>
<span id="cb30-13">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">dst_filename =</span> outfile,</span>
<span id="cb30-14">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">xsize =</span> dm[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ysize =</span> dm[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>],</span>
<span id="cb30-15">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">nbands   =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,</span>
<span id="cb30-16">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">dataType =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Int32"</span>,</span>
<span id="cb30-17">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">options  =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">paste0</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"BLOCKSIZE="</span>, block_size),</span>
<span id="cb30-18">               <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"SPARSE_OK=YES"</span>,</span>
<span id="cb30-19">               <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"COMPRESS=DEFLATE"</span>,</span>
<span id="cb30-20">               <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"OVERVIEW_RESAMPLING=NEAREST"</span>,</span>
<span id="cb30-21">               <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"NUM_THREADS=ALL_CPUS"</span>),</span>
<span id="cb30-22">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">return_obj =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span></span>
<span id="cb30-23">)</span>
<span id="cb30-24"></span>
<span id="cb30-25">ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">setGeoTransform</span>(vaster<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">extent_dim_to_gt</span>(ext, dm))</span>
<span id="cb30-26">ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">setNoDataValue</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">band =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span></code></pre></div></div>
</div>
<p>Build a tile index with <code>grout</code>, then pre-filter to rows that contain any burned data. This avoids even <em>visiting</em> tiles over open ocean or interior ice sheet:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb31" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb31-1">superblock <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> block_size <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span></span>
<span id="cb31-2">gg    <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> grout<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">grout</span>(dm, ext, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">blocksize =</span> superblock)</span>
<span id="cb31-3">tiles <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> grout<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tile_index</span>(gg)</span>
<span id="cb31-4"></span>
<span id="cb31-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Keep only tile-rows that intersect at least one data row</span></span>
<span id="cb31-6">data_rows <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">unique</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(ba<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>runs<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>row, ba<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>edges<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>row,</span>
<span id="cb31-7">                      ba<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>lines<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>row, ba<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>points<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>row))</span>
<span id="cb31-8">tiles <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> tiles[tiles<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>tile_row <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">%in%</span></span>
<span id="cb31-9">                 <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">unique</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ceiling</span>(data_rows <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> superblock)), ]</span></code></pre></div></div>
</div>
<p>The loop itself is straightforward: crop → materialize → write.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb32" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb32-1">tiles_written <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>L</span>
<span id="cb32-2"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> (i <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq_len</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nrow</span>(tiles))) {</span>
<span id="cb32-3">  sub <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">crop_burn</span>(ba, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(tiles<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>xmin[i], tiles<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>xmax[i],</span>
<span id="cb32-4">                         tiles<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>ymin[i], tiles<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>ymax[i]))</span>
<span id="cb32-5"></span>
<span id="cb32-6">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nrow</span>(sub<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>runs) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;&amp;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nrow</span>(sub<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>edges) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">next</span></span>
<span id="cb32-7"></span>
<span id="cb32-8">  mat <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">materialize_chunk</span>(sub, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fun =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"id"</span>)</span>
<span id="cb32-9">  mat[<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">is.na</span>(mat)] <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb32-10"></span>
<span id="cb32-11">  ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">write</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">band =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,</span>
<span id="cb32-12">           <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">xoff =</span> tiles<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>offset_x[i], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">yoff =</span> tiles<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>offset_y[i],</span>
<span id="cb32-13">           <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">xsize =</span> tiles<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>ncol[i], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ysize =</span> tiles<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>nrow[i],</span>
<span id="cb32-14">           <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">rasterData =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.integer</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">t</span>(mat)))</span>
<span id="cb32-15"></span>
<span id="cb32-16">  tiles_written <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> tiles_written <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L</span>
<span id="cb32-17">}</span>
<span id="cb32-18"></span>
<span id="cb32-19">ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">flushCache</span>()</span>
<span id="cb32-20">ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">close</span>()</span></code></pre></div></div>
</div>
<pre><code>## ~445 seconds elapsed
## tiles written: 3,482 of 7,511
## file size: 26,782 KB
## output: 170318 × 182568 Int32, nodata=0</code></pre>
<p>The result is a valid COG — internally tiled, compressed, with overviews — that weighs in at around 26 MB on disk for a 31-billion-cell grid. Only the tiles that actually contain rock outcrop carry any data.</p>
<p>The compressed file is informationally equivalent to the sparse burn object itself — both encode exactly the same set of non-empty cells. The burn object in memory is <code>lobstr::obj_size(ba)</code> → 27.27 MB; the COG on disk is 26 MB. The sparse representation <em>is</em> the information content of the raster; the file is just a different container for the same signal.</p>
</section>
</section>
<section id="extracting-point-values" class="level2">
<h2 class="anchored" data-anchor-id="extracting-point-values">Extracting point values</h2>
<p>Not every downstream operation needs a dense raster. <code>extract_burn()</code> queries the sparse tables directly at arbitrary point locations, with no materialisation step at all. It matches interior runs by row equality and column interval containment, and edges/lines/points by exact (row, col) match. Cost is O(points + hits), independent of grid dimension.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb34" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb34-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Some coordinates in the burn's CRS (Antarctic Polar Stereographic)</span></span>
<span id="cb34-2">pts <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">structure</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2412867</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2426358</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2424194</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2437557</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2437175</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2441121</span>, </span>
<span id="cb34-3"><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2443539</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2443284</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">911354</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">909445</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">915172</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">913263</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">913136</span>, </span>
<span id="cb34-4"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">912245</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">914281</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">920645</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">dim =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>L, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>L))</span>
<span id="cb34-5"></span>
<span id="cb34-6"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(pts, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">asp =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb34-7">ximage<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ximage</span>(vapour<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">gdal_raster_nara</span>(sds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wms_arcgis_mapserver_ESRI.WorldImagery_tms</span>(), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">target_ext =</span> tile_ex, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">target_crs =</span> raster_info<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>projection, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">target_dim =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">512</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">512</span>)),<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">add =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb34-8"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">points</span>(pts, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">col =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"green"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">pch =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">cex =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb34-9"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(rock[rock_idx], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">add =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">border =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"#e05780"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lwd =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.6</span>)</span></code></pre></div></div>
<div class="cell-output-display">
<div>
<figure class="figure">
<p><img src="https://hypertidy.org/posts/2026-08-19_antarctica-icefree-rasterizing/index_files/figure-html/extract-points-1.png" class="img-fluid figure-img" width="768"></p>
</figure>
</div>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb35" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb35-1">e_approx <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">extract_burn</span>(ba, pts, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fun =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"id"</span>)</span>
<span id="cb35-2"></span>
<span id="cb35-3">e_cover <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">extract_burn</span>(be, pts, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fun =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sum"</span>)</span>
<span id="cb35-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cbind</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">approx =</span> e_approx, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">cover =</span> e_cover)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>     approx     cover
[1,]  19038 1.0000000
[2,]  19024 1.0000000
[3,]     NA 0.0000000
[4,]  19048 1.0000000
[5,]  19048 1.0000000
[6,]  19051 0.5053515
[7,]  19059 1.0000000
[8,]     NA 0.0000000</code></pre>
</div>
</div>
<p>The function returns <code>NA</code> for background cells (no geometry touched that pixel) and the geometry ID for cells inside a polygon. With <code>fun = "sum"</code>, coverage-mode burns return exact fractional coverage at each point.</p>
<p>This pattern generalizes. Because the sparse representation is a reusable intermediate, other raster operations — zonal statistics, reclassification, distance transforms — can be built on top of the same sparse tables without re-burning or allocating a full dense grid. The burn is computed once; everything else is derived.</p>
<p>terra for example can tell us those exact same polygon identities, but it can’t easily tell us what coverage that polygon has in the related pixel (that’s a different question, we’d need to rasterize first …), we have different opportunities with this alternative representation.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb37" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb37-1">terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">extract</span>(terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vect</span>(geos<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_geos_geometry</span>(rock)), pts)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>     id.y  id.x
[1,]    1 19038
[2,]    2 19024
[3,]    3   NaN
[4,]    4 19048
[5,]    5 19048
[6,]    6 19051
[7,]    7 19059
[8,]    8   NaN</code></pre>
</div>
</div>
</section>
<section id="packing-compression-and-sparsity" class="level2">
<h2 class="anchored" data-anchor-id="packing-compression-and-sparsity">Packing, compression, and sparsity</h2>
<p>A useful comparison: the Australian Antarctic Data Centre publishes <a href="https://doi.org/10.26179/7mnh-j215">Ice-free Antarctica</a> (Toth &amp; Terauds 2023) — a union of rock outcrop layers from imagery spanning 1960–2020, encoded as a single-band GeoTIFF at 100 m resolution (52765 × 48132 pixels). Values are 0 (ice/ocean), 1, 2, or 3 (rock, by source layer), stored at 2 bits per sample (NBITS=2) in 128 × 128 tiles. The file is 613 MB.</p>
<p>That 2-bit packing is already a 4× saving over a byte raster — but it is a fixed per-pixel economy. Every tile is materialized: ocean, ice sheet, and rock alike each cost their full 4096 bytes. The encoding is indifferent to the fact that the vast majority of pixels say the same thing: zero. Recompressing the same tiles with DEFLATE collapses the file to about 5.6 MB — a ~100× reduction with zero information loss, because constant regions become long runs of identical bytes, and long runs are the best possible input to any general-purpose codec.</p>
<p>Our controlledburn COG encodes different (but related) data at 32 m resolution — a grid 3x denser in each dimension for 26 MB on disk. The sparse burn object in memory is 27 MB. These numbers are not directly comparable (different polygons, different resolution, different pixel semantics), but the <em>relationship</em> between them is the point: the information content of “where is the rock?” is small relative to the grid that contains it, regardless of how you choose to encode it.</p>
<p>The three approaches — bit-packing, codec compression, and run-length sparse representation — are related lenses on the same underlying reality. Bit-packing reduces the cost per pixel but remains grid-dense. Compression recovers structure after the fact, collapsing constant regions that the format didn’t know to avoid. Sparse rasterization exploits the same structure <em>before</em> pixels exist: polygon interiors are runs by construction, so the compact representation is produced directly rather than recovered by a codec.</p>
<p>None of these is categorically superior. The AADC file is a finished product, designed for broad interoperability and direct random-access reads — the choice not to compress may well reflect downstream tooling constraints or the simplicity of a uniform tile stride. controlledburn’s sparse format is an intermediate: it is meant to sit between the vector source and whatever raster product or analysis comes next. The value is in seeing them as points on the same continuum — rasterization, compression, and sparse encoding are all ways of negotiating between the geometry of the data and the geometry of the grid.</p>


</section>

 ]]></description>
  <category>controlledburn</category>
  <category>rasterization</category>
  <category>Antarctica</category>
  <category>GDAL</category>
  <guid>https://hypertidy.org/posts/2026-08-19_antarctica-icefree-rasterizing/</guid>
  <pubDate>Wed, 19 Aug 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>s2 guide</title>
  <dc:creator>Michael Sumner</dc:creator>
  <link>https://hypertidy.org/posts/2026-08-10_s2-guide/</link>
  <description><![CDATA[ 






<p>The s2 package is excellent and I find it genuinely hard to use. Not because the functions are bad, but because the package contains three parallel type families that perhaps aren’t obvious, and the <code>s2_</code> prefix only weakly signals which family a function belongs to. This is a real transcript of me trying to get from a column of cell IDs in a parquet file to a plot of longitude/latitude centre points:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1">res <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> arrow<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">read_parquet</span>(f[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb1-2"></span>
<span id="cb1-3">s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_lnglat</span>(res<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>cell[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb1-4">s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_x</span>(res<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>cell[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb1-5">s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_point</span>(res<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>cell[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb1-6">s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_cell</span>(res<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>cell[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb1-7">s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_s2_geography</span>(s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_cell</span>(res<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>cell[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]))</span>
<span id="cb1-8">s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_x</span>(s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_cell</span>(res<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>cell[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]))</span>
<span id="cb1-9">s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_cell_center</span>(s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_cell</span>(res<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>cell[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]))</span>
<span id="cb1-10">cc <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_cell_center</span>(s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_cell</span>(res<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>cell))</span>
<span id="cb1-11">s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_cell_to_lnglat</span>(cc)</span>
<span id="cb1-12"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_wkb</span>(cc))                            <span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## FAIL (geodesic!)</span></span>
<span id="cb1-13"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_set_geodesic</span>(wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_wkb</span>(cc), <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>))</span></code></pre></div></div>
<p>Eleven calls, one error, and the answer was one function cell:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_cell_to_lnglat</span>(s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_s2_cell</span>(res<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>cell)))</span></code></pre></div></div>
<p>This post is a guide I wanted. It’s aimed at people who use s2 directly. If you want the sf-flavoured story, the canonical reference is the <a href="https://r-spatial.github.io/sf/articles/sf7.html">sf spherical geometry article</a>, and other r-spatial posts <a href="https://r-spatial.org/r/2020/06/17/s2.html">In r-spatial, the Earth is no longer flat</a>, and <a href="https://r-spatial.org/r/2024/10/11/polygonfull.html">Introducing: the POLYGON FULL</a>.</p>
<section id="prior-art-deweys-posts" class="level2">
<h2 class="anchored" data-anchor-id="prior-art-deweys-posts">Prior art: Dewey’s posts</h2>
<p>Most of what follows was worked out first, and better, by Dewey Dunnington across a series of posts that double as the s2 package’s missing narrative documentation. If this guide is the map, these are the survey expeditions:</p>
<ul>
<li><p><a href="https://dewey.dunnington.ca/post/2021/wk-version-0.6.0/">wk version 0.6.0</a> (Dec 2021) is where the geodesic attribute enters WKT/WKB – added specifically so that wk could serve as an interchange format for s2, where vertices are joined by great circles rather than planar segments. If you have ever wondered why <code>plot(wk::as_wkb(some_s2_thing))</code> refuses to cooperate, the reasoning starts here.</p></li>
<li><p><a href="https://dewey.dunnington.ca/post/2022/profiling-point-in-polygon-joins-in-r/">Profiling point-in-polygon joins in R</a> (Mar 2022) benchmarks geos against s2 for a 9-million-point join, is honest about where s2’s point handling was slow at the time, and foreshadows exposing the cell machinery: approximate a polygon with a covering of cells and test membership as cell arithmetic. That idea is the core of the polygon recipe below.</p></li>
<li><p><a href="https://dewey.dunnington.ca/post/2022/s2-version-1.1.0/">s2 version 1.1.0</a> (Jul 2022) is the densest single source: <code>tessellate_tol_m</code> for importing planar-edged data honestly (adding vertices so GIS-rule edges survive the trip to the sphere), the cell polygon export problem (geodesic by definition, planar by expectation), the <code>s2_union_agg()</code> speedup, and the refactor of the internals into a standalone s2geography library – the lineage that later surfaces in DuckDB and Sedona.</p></li>
<li><p><a href="https://dewey.dunnington.ca/post/2024/partitioning-strategies-for-bigger-than-memory-spatial-data/">Partitioning strategies for bigger-than-memory spatial data</a> and <a href="https://dewey.dunnington.ca/post/2024/wrangling-and-joining-130m-points-with-duckdb--the-open-source-spatial-stack/">Wrangling and joining 130M points with DuckDB + the open source spatial stack</a> (both Dec 2024) take the cell idea to its logical end: s2 cells as partition keys and join keys for larger-than-memory data, via the DuckDB Geography extension. This is the “cells are just a column” worldview that motivates keeping cell IDs in parquet in the first place.</p></li>
<li><p>The <a href="https://dewey.dunnington.ca/slides/geopython2025">GeoPython 2025 slides</a> on DuckDB Geography give the compact cross-language overview of the Geography and S2 Cell/Center/Union types.</p></li>
</ul>
<p>The through-line in all of these: s2’s cell types are not an implementation detail, they are the product – a spatial index you can store, ship, and group by. This post is about using them directly from R without the database in between.</p>
</section>
<section id="the-three-type-families" class="level2">
<h2 class="anchored" data-anchor-id="the-three-type-families">The three type families</h2>
<p>Everything in s2 is one of these:</p>
<ol type="1">
<li><p><strong><code>s2_cell</code></strong> – a vctrs vector of doubles whose bits are reinterpreted as uint64 S2 cell IDs. This is the discrete global grid side of s2: a hierarchical quadtree on the six faces of a cube projected to the sphere, levels 0 (face) to 30 (leaf, roughly centimetre scale). Every function named <code>s2_cell_*</code> operates here. Cells are cheap: they sort, hash, group, and round-trip through <code>character</code> and <code>bit64::integer64</code> losslessly. This is the type you get from BigQuery, from parquet exports, from anything that indexes points spatially.</p></li>
<li><p><strong><code>s2_geography</code></strong> – external pointers to C++ S2 geography objects (points, polylines, polygons on the sphere). Every function with a bare <code>s2_</code> prefix (<code>s2_intersects()</code>, <code>s2_buffer_cells()</code>, <code>s2_x()</code>, …) operates here, or coerces its input here via <code>as_s2_geography()</code>. The coercion is why half-wrong calls half-work: <code>s2_x("POINT (147 -42)")</code> succeeds because character is assumed to be WKT.</p></li>
<li><p><strong>wk vctrs</strong> – plain coordinate vectors from the wk package, carrying a crs attribute. <code>s2_lnglat(lng, lat)</code> is just <code>wk::xy()</code> with a longlat crs; <code>s2_point(x, y, z)</code> is <code>wk::xyz()</code> with a special “unit sphere” crs. These are the plottable, data-frame-able, “normal” things. They are the exits from s2 back into everything else.</p></li>
</ol>
<p>The mnemonic: <strong><code>s2_cell_*</code> functions take cells, <code>as_*</code> functions change family, everything else takes geography.</strong></p>
<p>Worth internalizing: <code>s2_lnglat()</code> and <code>s2_point()</code> are <em>constructors from coordinates</em>, not converters. <code>s2_lnglat(res$cell[1])</code> doesn’t error helpfully, it just constructs nonsense (a point at longitude whatever-your-cell-double-is). The converters are all spelled <code>as_</code>: <code>as_s2_cell()</code>, <code>as_s2_lnglat()</code>, <code>as_s2_point()</code>, <code>as_s2_geography()</code>, <code>as_s2_cell_union()</code>.</p>
</section>
<section id="the-conversion-table" class="level2">
<h2 class="anchored" data-anchor-id="the-conversion-table">The conversion table</h2>
<table class="caption-top table">
<colgroup>
<col style="width: 16%">
<col style="width: 13%">
<col style="width: 21%">
<col style="width: 24%">
<col style="width: 23%">
</colgroup>
<thead>
<tr class="header">
<th>From \ To</th>
<th>s2_cell</th>
<th>s2_geography</th>
<th>lnglat (wk_xy)</th>
<th>point (wk_xyz)</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>character token</td>
<td><code>as_s2_cell()</code></td>
<td>–</td>
<td>–</td>
<td>–</td>
</tr>
<tr class="even">
<td>integer64</td>
<td><code>as_s2_cell()</code></td>
<td>–</td>
<td>–</td>
<td>–</td>
</tr>
<tr class="odd">
<td>s2_cell</td>
<td><code>s2_cell_parent()</code>, <code>s2_cell_child()</code></td>
<td><code>s2_cell_center()</code>, <code>s2_cell_polygon()</code>, <code>s2_cell_boundary()</code></td>
<td><code>s2_cell_to_lnglat()</code></td>
<td>via lnglat</td>
</tr>
<tr class="even">
<td>s2_geography (points)</td>
<td><code>as_s2_cell()</code></td>
<td>–</td>
<td><code>s2_x()</code> + <code>s2_y()</code>, or wk (see below)</td>
<td>via lnglat</td>
</tr>
<tr class="odd">
<td>lnglat / lon,lat vectors</td>
<td><code>as_s2_cell()</code></td>
<td><code>as_s2_geography()</code></td>
<td><code>s2_lnglat(lng, lat)</code></td>
<td><code>as_s2_point()</code></td>
</tr>
<tr class="even">
<td>point (unit sphere xyz)</td>
<td>via lnglat</td>
<td>–</td>
<td><code>as_s2_lnglat()</code></td>
<td>–</td>
</tr>
<tr class="odd">
<td>polygon-ish anything</td>
<td><code>s2_covering_cell_ids()</code></td>
<td><code>as_s2_geography()</code></td>
<td>–</td>
<td>–</td>
</tr>
</tbody>
</table>
<p>Notes on the sharp edges:</p>
<ul>
<li><code>as_s2_cell()</code> on coordinates or point geographies gives the <strong>level-30 leaf cell</strong>; use <code>s2_cell_parent(cell, level)</code> to coarsen. Parent of a parent is fine; you can’t go below your current level except via <code>s2_cell_child()</code>.</li>
<li><code>as_s2_cell()</code> on <code>integer64</code> reinterprets bits directly – so a cell-ID column read from parquet as integer64 converts with no string round-trip. If arrow hands you character tokens, <code>as_s2_cell()</code> handles that too.</li>
<li><code>s2_cell_to_lnglat()</code> <strong>is</strong> the centre point in one hop. You do not need <code>s2_cell_center()</code> (which makes geography pointers) unless you want to feed the result into geography predicates.</li>
</ul>
</section>
<section id="the-geodesic-flag-or-why-did-my-plot-fail" class="level2">
<h2 class="anchored" data-anchor-id="the-geodesic-flag-or-why-did-my-plot-fail">The geodesic flag, or: why did my plot fail</h2>
<p><code>wk::as_wkb()</code> on an s2 geography tags the result <code>geodesic = TRUE</code>, because that’s the truth: S2 edges are great-circle arcs, not straight lines in longitude/latitude space. wk then refuses to plot it, because plotting geodesic edges as straight segments would be a lie and wk won’t densify them for you.</p>
<p>For <strong>points</strong> the edge interpretation is vacuous, so the refusal is technically principled and practically infuriating. The escape hatch:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_set_geodesic</span>(wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_wkb</span>(geog), <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>))</span></code></pre></div></div>
<p>For points this is always fine. For lines and polygons it is fine when your features are small (a level-10+ cell polygon spans a few km; nothing visible bends at that scale) and a lie when they are large (a level-0 face “polygon” drawn with straight lonlat edges is wildly wrong). The honest fix for large features is densification, which is section 5.</p>
</section>
<section id="recipe-1-raster-grids-points-and-polygons-to-s2-groupings" class="level2">
<h2 class="anchored" data-anchor-id="recipe-1-raster-grids-points-and-polygons-to-s2-groupings">Recipe 1: raster grids, points, and polygons to s2 groupings</h2>
<p>The pattern for all of these is: get to lonlat coordinates or geography, snap to leaf cells, coarsen to a working level, then treat the cell column as an ordinary grouping variable.</p>
<section id="points" class="level3">
<h3 class="anchored" data-anchor-id="points">Points</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(s2)</span>
<span id="cb4-2"></span>
<span id="cb4-3"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## from bare vectors -- one line to cells at your working level</span></span>
<span id="cb4-4">cells <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_cell_parent</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_s2_cell</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_lnglat</span>(lon, lat)), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">level =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)</span>
<span id="cb4-5"></span>
<span id="cb4-6"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## then it's just dplyr</span></span>
<span id="cb4-7">d <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb4-8">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">cell =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_cell_parent</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_s2_cell</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_lnglat</span>(lon, lat)), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb4-9">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">summarize</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">sst =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mean</span>(sst), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">n =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">n</span>(), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">.by =</span> cell) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span></span>
<span id="cb4-10">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mutate</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">centre =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_cell_to_lnglat</span>(cell))</span></code></pre></div></div>
<p>Level choice: each level quarters the cell area. Rough edge lengths – level 4 ~ 400 km, level 8 ~ 25 km, level 12 ~ 1.5 km, level 16 ~ 100 m. <code>s2_cell_area(cell)</code> gives you exact areas in m^2 if you’d rather check than memorise.</p>
</section>
<section id="regular-raster-grids" class="level3">
<h3 class="anchored" data-anchor-id="regular-raster-grids">Regular raster grids</h3>
<p>A raster is just points-with-benefits here: take the cell centres. With vaster (or any xy-from-cell logic):</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1">xy <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> vaster<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">xy_from_cell</span>(dimension, extent, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">cells =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq_len</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">prod</span>(dimension)))</span>
<span id="cb5-2">s2cell <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_cell_parent</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_s2_cell</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_lnglat</span>(xy[, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], xy[, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>])), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">level =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">7</span>)</span>
<span id="cb5-3"></span>
<span id="cb5-4"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## aggregate a value vector (e.g. one time slice of SST) onto the s2 grid</span></span>
<span id="cb5-5">agg <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tapply</span>(values, s2cell, mean, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">na.rm =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb5-6">centres <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_cell_to_lnglat</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_s2_cell</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">names</span>(agg)))</span></code></pre></div></div>
<p>Two things worth saying out loud:</p>
<ul>
<li>If the grid is in a <strong>projected</strong> crs, unproject the centres first (<code>reproj::reproj_xy(xy, "EPSG:4326", source = crs)</code>); s2 lives on the sphere and only speaks degrees at the door.</li>
<li>This is <em>point sampling</em> of the raster into cells, which is the right cheap default. If you need area-weighted binning (cells much coarser than pixels is fine; cells finer than pixels is where you’d care), that’s an exact-extract style problem and out of scope for a one-liner – though note that at coarse-cells-over-fine-pixels the sampling error washes out with pixel count.</li>
</ul>
<p>The pleasant surprise is that this makes s2 a perfectly good equal-ish-area DGGS for the “bin scattered obs / reduce a global grid” workflows people reach for H3 for, with the advantage that the cell arithmetic is pure R vctrs – no geometry objects until you ask for them.</p>
</section>
<section id="polygons" class="level3">
<h3 class="anchored" data-anchor-id="polygons">Polygons</h3>
<p>Polygons map to <em>sets</em> of cells, so the target type is <code>s2_cell_union</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb6-1">g <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_s2_geography</span>(wkt_or_wkb_or_sf_geometry)</span>
<span id="cb6-2"></span>
<span id="cb6-3"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## interior/exterior covering at bounded resolution</span></span>
<span id="cb6-4">cov <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_covering_cell_ids</span>(g, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">min_level =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">max_level =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">max_cells =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">64</span>)</span>
<span id="cb6-5">cov_int <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_covering_cell_ids</span>(g, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">max_cells =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">64</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">interior =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span></code></pre></div></div>
<p><code>max_cells</code> is the budget: the covering adapts level within your bounds to approximate the shape with at most that many cells. <code>interior = TRUE</code> gives cells fully inside (a conservative mask), default gives cells that collectively contain the polygon (a permissive mask). There’s a <code>buffer</code> argument (metres) for dilating before covering.</p>
<p>Point-in-polygon at scale then becomes cell arithmetic instead of geometry:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb7-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## which obs cells fall in the polygon covering? no geometry predicates,</span></span>
<span id="cb7-2"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## just uint64 range containment</span></span>
<span id="cb7-3">inside <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_cell_union_contains</span>(cov, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_s2_cell_union</span>(obs_cells))</span></code></pre></div></div>
<p>For an exact answer, use the permissive covering as a prefilter and run <code>s2_intersects()</code> on the survivors – the classic index-then-refine join, except the index is a column you can keep in parquet.</p>
<p>This is the shape of the Pacific-domain-mask problem, incidentally: express the domain polygon once as a covering, save the cell union as tokens, and membership tests against any point set are joins.</p>
</section>
</section>
<section id="recipe-2-s2-to-rgl-plotting-on-the-actual-sphere" class="level2">
<h2 class="anchored" data-anchor-id="recipe-2-s2-to-rgl-plotting-on-the-actual-sphere">Recipe 2: s2 to rgl (plotting on the actual sphere)</h2>
<p>The bridge is <code>as_s2_point()</code>: it converts longlat wk_xy to <strong>unit-sphere xyz</strong> – which is exactly what rgl wants, no trig required.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb8-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(rgl)</span>
<span id="cb8-2"></span>
<span id="cb8-3"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## cell centres as points on the sphere</span></span>
<span id="cb8-4">xyz <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.matrix</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.data.frame</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_s2_point</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_cell_to_lnglat</span>(cells))))</span>
<span id="cb8-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">points3d</span>(xyz, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">col =</span> palr<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sst_pal</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">64</span>)[scales<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rescale</span>(vals, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">64</span>)) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|&gt;</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">round</span>()])</span>
<span id="cb8-6"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">spheres3d</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">radius =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.999</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">col =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"grey20"</span>)  <span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## backdrop globe</span></span></code></pre></div></div>
<p>Cells as quads is barely more work, because <code>s2_cell_vertex(x, k)</code> gives vertex k (0..3) as a point geography, and quads want exactly that:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb9-1">cell_quads3d <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(cells, ...) {</span>
<span id="cb9-2">  v <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">lapply</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(k) {</span>
<span id="cb9-3">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.matrix</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.data.frame</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_s2_point</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_s2_lnglat</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_cell_vertex</span>(cells, k)))))</span>
<span id="cb9-4">  })</span>
<span id="cb9-5">  <span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## interleave to vertex-per-row quad order: v0 v1 v2 v3 per cell</span></span>
<span id="cb9-6">  m <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">matrix</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">t</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">do.call</span>(cbind, v)), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ncol =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>L, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">byrow =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb9-7">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">quads3d</span>(m, ...)</span>
<span id="cb9-8">}</span>
<span id="cb9-9"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cell_quads3d</span>(cells, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">col =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"firebrick"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">alpha =</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.8</span>)</span></code></pre></div></div>
<p>Caveats, both about coarse cells:</p>
<ul>
<li>A quad’s edges are drawn as chords through the sphere, so coarse cells (level &lt; ~4) visibly cut the ball and their “flat” faces sit below the unit sphere. The fix is subdivision: replace each coarse cell with its children at a uniform deeper level (<code>s2_cell_child()</code> recursively, or just build your grouping at a deeper level and aggregate the <em>values</em> coarsely instead).</li>
<li>For genuinely smooth cell patches you want the quadmesh treatment – densify each cell face in (face, i, j) space and push vertices to the sphere – which is an anglr/quadmesh shaped job and a natural follow-up post.</li>
</ul>
<p>The general s2-geography-to-rgl path for arbitrary lines/polygons is: densify edges first (next section), then <code>wk::wk_vertices()</code>, <code>as_s2_point()</code>, and index into <code>segments3d()</code>/<code>polygon3d()</code>. Densify-first matters for the same chord-vs-arc reason.</p>
</section>
<section id="recipe-3-geodesic-densification-bigcurve-and-wkpool" class="level2">
<h2 class="anchored" data-anchor-id="recipe-3-geodesic-densification-bigcurve-and-wkpool">Recipe 3: geodesic densification, bigcurve, and wkpool</h2>
<p>Here’s the conceptual unification. wk’s geodesic flag marks “these edges are great circles, do not interpolate them linearly in lonlat”. bigcurve’s whole job is adaptive line densification under a coordinate transformation – insert vertices until the straight segments approximate the true curve to tolerance. A geodesic edge is precisely this problem where the transformation is <em>sphere to plate carree</em>: the true curve is the great circle, the drawing space is lonlat.</p>
<p>So the missing verb has an obvious signature:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb10-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## proposed: consume geodesic-flagged wkb, emit densified planar wkb</span></span>
<span id="cb10-2">flatten_geodesic <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(x, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">tolerance_m =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10000</span>) {</span>
<span id="cb10-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">stopifnot</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">isTRUE</span>(wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_is_geodesic</span>(x)))</span>
<span id="cb10-4">  <span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## densify each edge along the great circle to tolerance, then</span></span>
<span id="cb10-5">  wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_set_geodesic</span>(densified, <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>)</span>
<span id="cb10-6">}</span></code></pre></div></div>
<p>with the inverse-ish operation being what bigcurve already does for projections (densify in the <em>projected</em> space). Composed, you get the honest pipeline for “draw this s2 polygon in LAEA”:</p>
<pre><code>s2_geography --[flatten_geodesic, tol]--&gt; planar lonlat wkb
             --[bigcurve densify under proj, tol]--&gt; projected wkb</code></pre>
<p>Three candidate engines for the great-circle interpolation itself:</p>
<ol type="1">
<li><strong>s2 itself</strong>: <code>s2_interpolate_normalized(line, t)</code> walks a polyline by fraction – correct but per-feature-scalar-ish and awkward for edge-wise adaptive refinement.</li>
<li><strong>geographiclib</strong>: direct/inverse problems on the ellipsoid – and this is the point where you decide whether “geodesic” means sphere (s2’s world) or ellipsoid (GeographicLib’s). For rendering, sphere is fine and consistent with the flag’s provenance; for measurement, s2 already defers radius choices to you.</li>
<li><strong>a small C++ slerp in bigcurve’s core</strong>: unit-vector slerp between edge endpoints is ~5 lines, exactly matches S2’s edge definition, and fits bigcurve’s adaptive bisection (recurse while the chord midpoint deviates from the slerp midpoint by &gt; tol). This is the one I’d do: the s2 edge model <em>is</em> slerp, so bigcurve gains geodesic support without a dependency, and wkpool provides the vertex pooling for shared edges (think: densifying a lattice of cell polygons without duplicating every shared boundary).</li>
</ol>
<p>A cell-union boundary is a mesh of shared arcs, and densifying per-polygon quadruples the work and the vertex count. Pool first, densify unique edges once, reconstruct. That’s the silicate instinct applied to the sphere, and it would make “draw 10k cell polygons in any projection, correctly” cheap.</p>
<p>I think: <code>flatten_geodesic()</code> (or <code>wk_geodesic_densify()</code>) belongs API-wise with wk-adjacent tooling, implementation-wise in bigcurve’s C++ core, with wkpool underneath for shared-edge economy. And its existence retroactively makes wk’s plot refusal feel fair – the refusal is an unfilled socket, and this is the plug.</p>
</section>
<section id="appendix-the-four-verbs" class="level2">
<h2 class="anchored" data-anchor-id="appendix-the-four-verbs">Appendix: the four verbs</h2>
<p>A core of wrappers is tiny:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb12-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## cell column (character/integer64/cell) -&gt; lonlat centres</span></span>
<span id="cb12-2">cell_lonlat <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(x) s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_cell_to_lnglat</span>(s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_s2_cell</span>(x))</span>
<span id="cb12-3"></span>
<span id="cb12-4"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## lon, lat -&gt; cells at level</span></span>
<span id="cb12-5">lonlat_cell <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(x, y, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">level =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">30</span>L) {</span>
<span id="cb12-6">  s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_cell_parent</span>(s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_s2_cell</span>(s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_lnglat</span>(x, y)), level)</span>
<span id="cb12-7">}</span>
<span id="cb12-8"></span>
<span id="cb12-9"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## cell column -&gt; plottable planar polygons (small-cell assumption)</span></span>
<span id="cb12-10">cell_poly <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(x) {</span>
<span id="cb12-11">  wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_set_geodesic</span>(wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_wkb</span>(s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">s2_cell_polygon</span>(s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_s2_cell</span>(x))), <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>)</span>
<span id="cb12-12">}</span>
<span id="cb12-13"></span>
<span id="cb12-14"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## anything s2 -&gt; unit sphere xyz matrix for rgl</span></span>
<span id="cb12-15">sphere_xyz <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(x) {</span>
<span id="cb12-16">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.matrix</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.data.frame</span>(s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_s2_point</span>(s2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_s2_lnglat</span>(wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_xy</span>(x)))))</span>
<span id="cb12-17">}</span></code></pre></div></div>
<p>I’m not sure these need a package so much as a place to be seen.</p>


</section>

 ]]></description>
  <category>s2</category>
  <category>wk</category>
  <category>spatial</category>
  <category>rgl</category>
  <guid>https://hypertidy.org/posts/2026-08-10_s2-guide/</guid>
  <pubDate>Mon, 10 Aug 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Icechunk is coming - versioned cloud-native Zarr, GDAL, and R</title>
  <dc:creator>Michael Sumner</dc:creator>
  <link>https://hypertidy.org/posts/2026-06-26_icechunk-is-coming/</link>
  <description><![CDATA[ 






<p><a href="https://icechunk.io">Icechunk</a> is a transactional storage engine for Zarr and provides git-style versioning for large scientific datasets. This post is about Icechunk from a GDAL perspective and some impact that will have on the R community and its interactions with Zarr.</p>
<section id="what-is-icechunk" class="level2">
<h2 class="anchored" data-anchor-id="what-is-icechunk">What is Icechunk?</h2>
<p>Zarr is a chunked array format designed for cloud object storage. Icechunk is a Rust library that sits on top of Zarr and adds git-like snapshots combined with <a href="https://icechunk.io/en/stable/understanding/concepts/#why-transactions-matter">ACID transactions</a> - (which means reliable and guaranteed read and write).</p>
<p>(From their docs) this provides:</p>
<ul>
<li>Safety: Recover from corrupted or accidentally deleted data using version history</li>
<li>Consistency: Ensure readers always see complete, valid snapshots - never partial writes</li>
<li>Reproducibility: Reference any version of your data permanently via commits or tags</li>
</ul>
<p>Typical usage involves the Python packages <a href="https://pypi.org/project/xarray/">xarray</a> and <a href="https://pypi.org/project/icechunk/">icechunk</a>, but <a href="https://crates.io/crates/icechunk">Rust Icechunk</a> can be used directly, in combination with <a href="https://crates.io/crates/zarrs_icechunk">zarrs_icechunk</a>, and now in an <a href="https://github.com/OSGeo/gdal/milestone/72">upcoming GDAL release 3.14</a> via the read-only <a href="https://gdal.org/en/latest/drivers/raster/icechunk.html">Icechunk driver</a>.</p>
<p>Please be aware that Icechunk in GDAL is very new, and not yet in an officially supported release but if you are interested to get involved it can be used today. (GDAL 3.14 is milestoned for sometime in November 2026).</p>
</section>
<section id="what-works-in-gdal-and-r-today" class="level2">
<h2 class="anchored" data-anchor-id="what-works-in-gdal-and-r-today">What works in GDAL and R today</h2>
<p>GDAL 3.14 ships a <code>/vsiicechunk/</code> virtual filesystem and Icechunk driver, and will be available in gdalcubes, gdalraster, sf, terra, and vapour in various forms once that support lands in R packages.</p>
<p>The following examples can be run in this Linux docker container, for example:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> pull ghcr.io/hypertidy/gdal-r-full:dev</span>
<span id="cb1-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">docker</span> run <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--rm</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-ti</span>  ghcr.io/hypertidy/gdal-r-full:dev</span></code></pre></div></div>
<p>(If that doesn’t work, or you want help with docker/GDAL/R just contact me directly)</p>
<p>Here is a single cli call that gives a GDAL multidimensional summary of an Icechunk store.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">gdal</span> mdim info <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb2-2">  /vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb2-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--config</span> AWS_NO_SIGN_REQUEST YES <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--config</span> AWS_REGION us-west-2</span></code></pre></div></div>
<details>
<summary>
Click here for the full mdim info output
</summary>
<pre><code>Driver: Icechunk

Dimensions:
  Name (path)  Size      Type      Direction
  -----------  ----  ------------  ---------
  /init_time   3264
  /latitude     721  HORIZONTAL_Y
  /lead_time     61
  /longitude   1440  HORIZONTAL_X

Coordinates (indexing variables):
  Name (path)   Dimension    Type              Unit
  -----------  -----------  -------  ------------------------
  /init_time   (init_time)  Int64    seconds since 1970-01-01
  /latitude    (latitude)   Float64  degree_north
  /lead_time   (lead_time)  Float64  seconds
  /longitude   (longitude)  Float64  degree_east

Data variables:
                  Name (path)                   Type              Unit                    Shape             Chunk size
  -------------------------------------------  -------  ------------------------  ---------------------  -----------------

 (/init_time):
  /expected_forecast_length                    Float64  seconds                   [3264]                 [23360]
  /ingested_forecast_length                    Float64  seconds                   [3264]                 [23360]

 (/init_time, /lead_time):
  /valid_time                                  Int64    seconds since 1970-01-01  [3264, 61]             [23360, 61]

 (/init_time, /lead_time, /latitude, /longitude):
  /dew_point_temperature_2m                    Float32  degree_Celsius            [3264, 61, 721, 1440]  [1, 61, 241, 240]
  /downward_long_wave_radiation_flux_surface   Float32  W m-2                     [3264, 61, 721, 1440]  [1, 61, 241, 240]
  /downward_short_wave_radiation_flux_surface  Float32  W m-2                     [3264, 61, 721, 1440]  [1, 61, 241, 240]
  /geopotential_height_500hpa                  Float32  m                         [3264, 61, 721, 1440]  [1, 61, 241, 240]
  /geopotential_height_850hpa                  Float32  m                         [3264, 61, 721, 1440]  [1, 61, 241, 240]
  /geopotential_height_925hpa                  Float32  m                         [3264, 61, 721, 1440]  [1, 61, 241, 240]
  /precipitation_surface                       Float32  kg m-2 s-1                [3264, 61, 721, 1440]  [1, 61, 241, 240]
  /pressure_reduced_to_mean_sea_level          Float32  Pa                        [3264, 61, 721, 1440]  [1, 61, 241, 240]
  /pressure_surface                            Float32  Pa                        [3264, 61, 721, 1440]  [1, 61, 241, 240]
  /temperature_2m                              Float32  degree_Celsius            [3264, 61, 721, 1440]  [1, 61, 241, 240]
  /temperature_850hpa                          Float32  degree_Celsius            [3264, 61, 721, 1440]  [1, 61, 241, 240]
  /temperature_925hpa                          Float32  degree_Celsius            [3264, 61, 721, 1440]  [1, 61, 241, 240]
  /total_cloud_cover_atmosphere                Float32  percent                   [3264, 61, 721, 1440]  [1, 61, 241, 240]
  /wind_u_100m                                 Float32  m s-1                     [3264, 61, 721, 1440]  [1, 61, 241, 240]
  /wind_u_10m                                  Float32  m s-1                     [3264, 61, 721, 1440]  [1, 61, 241, 240]
  /wind_v_100m                                 Float32  m s-1                     [3264, 61, 721, 1440]  [1, 61, 241, 240]
  /wind_v_10m                                  Float32  m s-1                     [3264, 61, 721, 1440]  [1, 61, 241, 240]

Scalar arrays:
  Name (path)   Type   Unit
  ------------  -----  ----
  /spatial_ref  Int64

Attributes:
         Name           Type                                      Value
  -------------------  ------  ----------------------------------------------------------------------------
  attribution          String  "ECMWF AIFS Single forecast data processed by dynamical.org from ECMWF Open
                               Data."
  dataset_id           String  "ecmwf-aifs-single-forecast"
  dataset_version      String  "0.1.0"
  description          String  "Weather forecasts from the ECMWF Artificial Intelligence Forecasting System
                               (AIFS) Single model."
  forecast_domain      String  "Forecast lead time 0-360 hours (0-15 days) ahead"
  forecast_resolution  String  "6 hourly"
  license              String  "CC-BY-4.0"
  name                 String  "ECMWF AIFS Single forecast"
  spatial_domain       String  "Global"
  spatial_resolution   String  "0.25 degrees (~20km)"
  time_domain          String  "Forecasts initialized 2024-04-01 00:00:00 UTC to Present"
  time_resolution      String  "Forecasts initialized every 6 hours"

Arrays:

  - /init_time:
      Dimensions:    (/init_time)
      Shape:         [3264]
      Chunk size:    [23360]
      Type:          Int64
      Unit:          seconds since 1970-01-01
      Nodata value:  0

      Attributes:
                 Name            Type                       Value
        ----------------------  ------  ---------------------------------------------
        calendar                String  "proleptic_gregorian"
        long_name               String  "Forecast initialization time"
        standard_name           String  "forecast_reference_time"
        statistics_approximate  String  {"min":"2024-04-01T00:00:00","max":"Present"}

      Structural metadata:
        COMPRESSOR  { "name": "blosc", "configuration": { "typesize": 8, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } }

  - /latitude:
      Dimensions:    (/latitude)
      Shape:         [721]
      Chunk size:    [721]
      Type:          Float64
      Unit:          degree_north
      Nodata value:  "NaN"

      Attributes:
                 Name            Type          Value
        ----------------------  ------  --------------------
        _FillValue              String  "AAAAAAAA+H8="
        axis                    String  "Y"
        long_name               String  "Latitude"
        statistics_approximate  String  {"min":-90,"max":90}

      Structural metadata:
        COMPRESSOR  { "name": "blosc", "configuration": { "typesize": 8, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } }

  - /lead_time:
      Dimensions:    (/lead_time)
      Shape:         [61]
      Chunk size:    [61]
      Type:          Float64
      Unit:          seconds
      Nodata value:  "NaN"

      Attributes:
                 Name            Type                         Value
        ----------------------  ------  --------------------------------------------------
        _FillValue              String  "AAAAAAAA+H8="
        dtype                   String  "timedelta64[us]"
        long_name               String  "Forecast lead time"
        standard_name           String  "forecast_period"
        statistics_approximate  String  {"min":"0 days 00:00:00","max":"15 days 00:00:00"}

      Structural metadata:
        COMPRESSOR  { "name": "blosc", "configuration": { "typesize": 8, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } }

  - /longitude:
      Dimensions:    (/longitude)
      Shape:         [1440]
      Chunk size:    [1440]
      Type:          Float64
      Unit:          degree_east
      Nodata value:  "NaN"

      Attributes:
                 Name            Type             Value
        ----------------------  ------  -------------------------
        _FillValue              String  "AAAAAAAA+H8="
        axis                    String  "X"
        long_name               String  "Longitude"
        statistics_approximate  String  {"min":-180,"max":179.75}

      Structural metadata:
        COMPRESSOR  { "name": "blosc", "configuration": { "typesize": 8, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } }

  - /dew_point_temperature_2m:
      Dimensions:    (/init_time, /lead_time, /latitude, /longitude)
      Shape:         [3264, 61, 721, 1440]
      Chunk size:    [1, 61, 241, 240]
      Type:          Float32
      Unit:          degree_Celsius
      Nodata value:  "NaN"

      Attributes:
            Name        Type                                     Value
        -------------  ------  --------------------------------------------------------------------------
        _FillValue     String  "AAAAAAAA+H8="
        coordinates    String  "expected_forecast_length ingested_forecast_length spatial_ref valid_time"
        long_name      String  "2 metre dewpoint temperature"
        short_name     String  "2d"
        standard_name  String  "dew_point_temperature"
        step_type      String  "instant"

      Structural metadata:
        COMPRESSOR  { "name": "sharding_indexed", "configuration": { "chunk_shape": [ 1, 61, 241, 240 ], "codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "blosc", "configuration": { "typesize": 4, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } } ], "index_codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "crc32c" } ], "index_location": "end" } }

  - /downward_long_wave_radiation_flux_surface:
      Dimensions:    (/init_time, /lead_time, /latitude, /longitude)
      Shape:         [3264, 61, 721, 1440]
      Chunk size:    [1, 61, 241, 240]
      Type:          Float32
      Unit:          W m-2
      Nodata value:  "NaN"

      Attributes:
            Name        Type                                     Value
        -------------  ------  --------------------------------------------------------------------------
        _FillValue     String  "AAAAAAAA+H8="
        coordinates    String  "expected_forecast_length ingested_forecast_length spatial_ref valid_time"
        long_name      String  "Surface downward long-wave radiation flux"
        short_name     String  "sdlwrf"
        standard_name  String  "surface_downwelling_longwave_flux_in_air"
        step_type      String  "avg"

      Structural metadata:
        COMPRESSOR  { "name": "sharding_indexed", "configuration": { "chunk_shape": [ 1, 61, 241, 240 ], "codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "blosc", "configuration": { "typesize": 4, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } } ], "index_codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "crc32c" } ], "index_location": "end" } }

  - /downward_short_wave_radiation_flux_surface:
      Dimensions:    (/init_time, /lead_time, /latitude, /longitude)
      Shape:         [3264, 61, 721, 1440]
      Chunk size:    [1, 61, 241, 240]
      Type:          Float32
      Unit:          W m-2
      Nodata value:  "NaN"

      Attributes:
            Name        Type                                     Value
        -------------  ------  --------------------------------------------------------------------------
        _FillValue     String  "AAAAAAAA+H8="
        coordinates    String  "expected_forecast_length ingested_forecast_length spatial_ref valid_time"
        long_name      String  "Surface downward short-wave radiation flux"
        short_name     String  "sdswrf"
        standard_name  String  "surface_downwelling_shortwave_flux_in_air"
        step_type      String  "avg"

      Structural metadata:
        COMPRESSOR  { "name": "sharding_indexed", "configuration": { "chunk_shape": [ 1, 61, 241, 240 ], "codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "blosc", "configuration": { "typesize": 4, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } } ], "index_codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "crc32c" } ], "index_location": "end" } }

  - /expected_forecast_length:
      Dimensions:    (/init_time)
      Shape:         [3264]
      Chunk size:    [23360]
      Type:          Float64
      Unit:          seconds
      Nodata value:  "NaN"

      Attributes:
                 Name            Type                         Value
        ----------------------  ------  --------------------------------------------------
        _FillValue              String  "AAAAAAAA+H8="
        dtype                   String  "timedelta64[us]"
        long_name               String  "Expected forecast length"
        statistics_approximate  String  {"min":"0 days 00:00:00","max":"15 days 00:00:00"}

      Structural metadata:
        COMPRESSOR  { "name": "blosc", "configuration": { "typesize": 8, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } }

  - /geopotential_height_500hpa:
      Dimensions:    (/init_time, /lead_time, /latitude, /longitude)
      Shape:         [3264, 61, 721, 1440]
      Chunk size:    [1, 61, 241, 240]
      Type:          Float32
      Unit:          m
      Nodata value:  "NaN"

      Attributes:
            Name        Type                                     Value
        -------------  ------  --------------------------------------------------------------------------
        _FillValue     String  "AAAAAAAA+H8="
        coordinates    String  "expected_forecast_length ingested_forecast_length spatial_ref valid_time"
        long_name      String  "Geopotential height"
        short_name     String  "gh"
        standard_name  String  "geopotential_height"
        step_type      String  "instant"

      Structural metadata:
        COMPRESSOR  { "name": "sharding_indexed", "configuration": { "chunk_shape": [ 1, 61, 241, 240 ], "codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "blosc", "configuration": { "typesize": 4, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } } ], "index_codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "crc32c" } ], "index_location": "end" } }

  - /geopotential_height_850hpa:
      Dimensions:    (/init_time, /lead_time, /latitude, /longitude)
      Shape:         [3264, 61, 721, 1440]
      Chunk size:    [1, 61, 241, 240]
      Type:          Float32
      Unit:          m
      Nodata value:  "NaN"

      Attributes:
            Name        Type                                     Value
        -------------  ------  --------------------------------------------------------------------------
        _FillValue     String  "AAAAAAAA+H8="
        coordinates    String  "expected_forecast_length ingested_forecast_length spatial_ref valid_time"
        long_name      String  "Geopotential height"
        short_name     String  "gh"
        standard_name  String  "geopotential_height"
        step_type      String  "instant"

      Structural metadata:
        COMPRESSOR  { "name": "sharding_indexed", "configuration": { "chunk_shape": [ 1, 61, 241, 240 ], "codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "blosc", "configuration": { "typesize": 4, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } } ], "index_codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "crc32c" } ], "index_location": "end" } }

  - /geopotential_height_925hpa:
      Dimensions:    (/init_time, /lead_time, /latitude, /longitude)
      Shape:         [3264, 61, 721, 1440]
      Chunk size:    [1, 61, 241, 240]
      Type:          Float32
      Unit:          m
      Nodata value:  "NaN"

      Attributes:
            Name        Type                                     Value
        -------------  ------  --------------------------------------------------------------------------
        _FillValue     String  "AAAAAAAA+H8="
        coordinates    String  "expected_forecast_length ingested_forecast_length spatial_ref valid_time"
        long_name      String  "Geopotential height"
        short_name     String  "gh"
        standard_name  String  "geopotential_height"
        step_type      String  "instant"

      Structural metadata:
        COMPRESSOR  { "name": "sharding_indexed", "configuration": { "chunk_shape": [ 1, 61, 241, 240 ], "codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "blosc", "configuration": { "typesize": 4, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } } ], "index_codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "crc32c" } ], "index_location": "end" } }

  - /ingested_forecast_length:
      Dimensions:    (/init_time)
      Shape:         [3264]
      Chunk size:    [23360]
      Type:          Float64
      Unit:          seconds
      Nodata value:  "NaN"

      Attributes:
                 Name            Type                         Value
        ----------------------  ------  --------------------------------------------------
        _FillValue              String  "AAAAAAAA+H8="
        dtype                   String  "timedelta64[us]"
        long_name               String  "Ingested forecast length"
        statistics_approximate  String  {"min":"0 days 00:00:00","max":"15 days 00:00:00"}

      Structural metadata:
        COMPRESSOR  { "name": "blosc", "configuration": { "typesize": 8, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } }

  - /precipitation_surface:
      Dimensions:    (/init_time, /lead_time, /latitude, /longitude)
      Shape:         [3264, 61, 721, 1440]
      Chunk size:    [1, 61, 241, 240]
      Type:          Float32
      Unit:          kg m-2 s-1
      Nodata value:  "NaN"

      Attributes:
            Name        Type                                       Value
        -------------  ------  ------------------------------------------------------------------------------
        _FillValue     String  "AAAAAAAA+H8="
        comment        String  "Average precipitation rate since the previous forecast step. Units equivalent
                               to mm/s."
        coordinates    String  "expected_forecast_length ingested_forecast_length spatial_ref valid_time"
        long_name      String  "Precipitation rate"
        short_name     String  "prate"
        standard_name  String  "precipitation_flux"
        step_type      String  "avg"

      Structural metadata:
        COMPRESSOR  { "name": "sharding_indexed", "configuration": { "chunk_shape": [ 1, 61, 241, 240 ], "codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "blosc", "configuration": { "typesize": 4, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } } ], "index_codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "crc32c" } ], "index_location": "end" } }

  - /pressure_reduced_to_mean_sea_level:
      Dimensions:    (/init_time, /lead_time, /latitude, /longitude)
      Shape:         [3264, 61, 721, 1440]
      Chunk size:    [1, 61, 241, 240]
      Type:          Float32
      Unit:          Pa
      Nodata value:  "NaN"

      Attributes:
            Name        Type                                     Value
        -------------  ------  --------------------------------------------------------------------------
        _FillValue     String  "AAAAAAAA+H8="
        coordinates    String  "expected_forecast_length ingested_forecast_length spatial_ref valid_time"
        long_name      String  "Pressure reduced to MSL"
        short_name     String  "prmsl"
        standard_name  String  "air_pressure_at_mean_sea_level"
        step_type      String  "instant"

      Structural metadata:
        COMPRESSOR  { "name": "sharding_indexed", "configuration": { "chunk_shape": [ 1, 61, 241, 240 ], "codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "blosc", "configuration": { "typesize": 4, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } } ], "index_codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "crc32c" } ], "index_location": "end" } }

  - /pressure_surface:
      Dimensions:    (/init_time, /lead_time, /latitude, /longitude)
      Shape:         [3264, 61, 721, 1440]
      Chunk size:    [1, 61, 241, 240]
      Type:          Float32
      Unit:          Pa
      Nodata value:  "NaN"

      Attributes:
            Name        Type                                     Value
        -------------  ------  --------------------------------------------------------------------------
        _FillValue     String  "AAAAAAAA+H8="
        coordinates    String  "expected_forecast_length ingested_forecast_length spatial_ref valid_time"
        long_name      String  "Surface pressure"
        short_name     String  "sp"
        standard_name  String  "surface_air_pressure"
        step_type      String  "instant"

      Structural metadata:
        COMPRESSOR  { "name": "sharding_indexed", "configuration": { "chunk_shape": [ 1, 61, 241, 240 ], "codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "blosc", "configuration": { "typesize": 4, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } } ], "index_codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "crc32c" } ], "index_location": "end" } }

  - /spatial_ref:
      Dimensions:    ()
      Shape:         []
      Type:          Int64
      Nodata value:  0

      Attributes:
                   Name               Type                                        Value
        ---------------------------  -------  -----------------------------------------------------------------------------
        comment                      String   "This coordinate reference system matches the source data which follows WMO
                                              conventions of assuming the earth is a perfect sphere with a radius of 6,371,
                                              229m. It is similar to EPSG:4326, but EPSG:4326 uses a more accurate
                                              representation of the earth's shape."
        crs_wkt                      String   "GEOGCS[\"unknown\",DATUM[\"unknown\",SPHEROID[\"unknown\",6371229,0]],
                                              PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",
                                              0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AXIS[\"Longitude\",EAST],
                                              AXIS[\"Latitude\",NORTH]]"
        geographic_crs_name          String   "unknown"
        grid_mapping_name            String   "latitude_longitude"
        horizontal_datum_name        String   "unknown"
        inverse_flattening           Float64  0
        longitude_of_prime_meridian  Float64  0
        prime_meridian_name          String   "Greenwich"
        reference_ellipsoid_name     String   "unknown"
        semi_major_axis              Float64  6371229
        semi_minor_axis              Float64  6371229
        spatial_ref                  String   "GEOGCS[\"unknown\",DATUM[\"unknown\",SPHEROID[\"unknown\",6371229,0]],
                                              PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",
                                              0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AXIS[\"Longitude\",EAST],
                                              AXIS[\"Latitude\",NORTH]]"

      Structural metadata:
        COMPRESSOR  { "name": "zstd", "configuration": { "level": 0, "checksum": false } }

  - /temperature_2m:
      Dimensions:    (/init_time, /lead_time, /latitude, /longitude)
      Shape:         [3264, 61, 721, 1440]
      Chunk size:    [1, 61, 241, 240]
      Type:          Float32
      Unit:          degree_Celsius
      Nodata value:  "NaN"

      Attributes:
            Name        Type                                     Value
        -------------  ------  --------------------------------------------------------------------------
        _FillValue     String  "AAAAAAAA+H8="
        coordinates    String  "expected_forecast_length ingested_forecast_length spatial_ref valid_time"
        long_name      String  "2 metre temperature"
        short_name     String  "2t"
        standard_name  String  "air_temperature"
        step_type      String  "instant"

      Structural metadata:
        COMPRESSOR  { "name": "sharding_indexed", "configuration": { "chunk_shape": [ 1, 61, 241, 240 ], "codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "blosc", "configuration": { "typesize": 4, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } } ], "index_codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "crc32c" } ], "index_location": "end" } }

  - /temperature_850hpa:
      Dimensions:    (/init_time, /lead_time, /latitude, /longitude)
      Shape:         [3264, 61, 721, 1440]
      Chunk size:    [1, 61, 241, 240]
      Type:          Float32
      Unit:          degree_Celsius
      Nodata value:  "NaN"

      Attributes:
            Name        Type                                     Value
        -------------  ------  --------------------------------------------------------------------------
        _FillValue     String  "AAAAAAAA+H8="
        coordinates    String  "expected_forecast_length ingested_forecast_length spatial_ref valid_time"
        long_name      String  "Temperature"
        short_name     String  "t"
        standard_name  String  "air_temperature"
        step_type      String  "instant"

      Structural metadata:
        COMPRESSOR  { "name": "sharding_indexed", "configuration": { "chunk_shape": [ 1, 61, 241, 240 ], "codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "blosc", "configuration": { "typesize": 4, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } } ], "index_codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "crc32c" } ], "index_location": "end" } }

  - /temperature_925hpa:
      Dimensions:    (/init_time, /lead_time, /latitude, /longitude)
      Shape:         [3264, 61, 721, 1440]
      Chunk size:    [1, 61, 241, 240]
      Type:          Float32
      Unit:          degree_Celsius
      Nodata value:  "NaN"

      Attributes:
            Name        Type                                     Value
        -------------  ------  --------------------------------------------------------------------------
        _FillValue     String  "AAAAAAAA+H8="
        coordinates    String  "expected_forecast_length ingested_forecast_length spatial_ref valid_time"
        long_name      String  "Temperature"
        short_name     String  "t"
        standard_name  String  "air_temperature"
        step_type      String  "instant"

      Structural metadata:
        COMPRESSOR  { "name": "sharding_indexed", "configuration": { "chunk_shape": [ 1, 61, 241, 240 ], "codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "blosc", "configuration": { "typesize": 4, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } } ], "index_codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "crc32c" } ], "index_location": "end" } }

  - /total_cloud_cover_atmosphere:
      Dimensions:    (/init_time, /lead_time, /latitude, /longitude)
      Shape:         [3264, 61, 721, 1440]
      Chunk size:    [1, 61, 241, 240]
      Type:          Float32
      Unit:          percent
      Nodata value:  "NaN"

      Attributes:
            Name        Type                                     Value
        -------------  ------  --------------------------------------------------------------------------
        _FillValue     String  "AAAAAAAA+H8="
        coordinates    String  "expected_forecast_length ingested_forecast_length spatial_ref valid_time"
        long_name      String  "Total cloud cover"
        short_name     String  "tcc"
        standard_name  String  "cloud_area_fraction"
        step_type      String  "instant"

      Structural metadata:
        COMPRESSOR  { "name": "sharding_indexed", "configuration": { "chunk_shape": [ 1, 61, 241, 240 ], "codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "blosc", "configuration": { "typesize": 4, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } } ], "index_codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "crc32c" } ], "index_location": "end" } }

  - /valid_time:
      Dimensions:    (/init_time, /lead_time)
      Shape:         [3264, 61]
      Chunk size:    [23360, 61]
      Type:          Int64
      Unit:          seconds since 1970-01-01
      Nodata value:  0

      Attributes:
                 Name            Type                       Value
        ----------------------  ------  ---------------------------------------------
        calendar                String  "proleptic_gregorian"
        long_name               String  "Valid time"
        standard_name           String  "time"
        statistics_approximate  String  {"min":"2024-04-01T00:00:00","max":"Present"}

      Structural metadata:
        COMPRESSOR  { "name": "blosc", "configuration": { "typesize": 8, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } }

  - /wind_u_100m:
      Dimensions:    (/init_time, /lead_time, /latitude, /longitude)
      Shape:         [3264, 61, 721, 1440]
      Chunk size:    [1, 61, 241, 240]
      Type:          Float32
      Unit:          m s-1
      Nodata value:  "NaN"

      Attributes:
            Name        Type                                     Value
        -------------  ------  --------------------------------------------------------------------------
        _FillValue     String  "AAAAAAAA+H8="
        coordinates    String  "expected_forecast_length ingested_forecast_length spatial_ref valid_time"
        long_name      String  "100 metre U wind component"
        short_name     String  "100u"
        standard_name  String  "eastward_wind"
        step_type      String  "instant"

      Structural metadata:
        COMPRESSOR  { "name": "sharding_indexed", "configuration": { "chunk_shape": [ 1, 61, 241, 240 ], "codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "blosc", "configuration": { "typesize": 4, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } } ], "index_codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "crc32c" } ], "index_location": "end" } }

  - /wind_u_10m:
      Dimensions:    (/init_time, /lead_time, /latitude, /longitude)
      Shape:         [3264, 61, 721, 1440]
      Chunk size:    [1, 61, 241, 240]
      Type:          Float32
      Unit:          m s-1
      Nodata value:  "NaN"

      Attributes:
            Name        Type                                     Value
        -------------  ------  --------------------------------------------------------------------------
        _FillValue     String  "AAAAAAAA+H8="
        coordinates    String  "expected_forecast_length ingested_forecast_length spatial_ref valid_time"
        long_name      String  "10 metre U wind component"
        short_name     String  "10u"
        standard_name  String  "eastward_wind"
        step_type      String  "instant"

      Structural metadata:
        COMPRESSOR  { "name": "sharding_indexed", "configuration": { "chunk_shape": [ 1, 61, 241, 240 ], "codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "blosc", "configuration": { "typesize": 4, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } } ], "index_codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "crc32c" } ], "index_location": "end" } }

  - /wind_v_100m:
      Dimensions:    (/init_time, /lead_time, /latitude, /longitude)
      Shape:         [3264, 61, 721, 1440]
      Chunk size:    [1, 61, 241, 240]
      Type:          Float32
      Unit:          m s-1
      Nodata value:  "NaN"

      Attributes:
            Name        Type                                     Value
        -------------  ------  --------------------------------------------------------------------------
        _FillValue     String  "AAAAAAAA+H8="
        coordinates    String  "expected_forecast_length ingested_forecast_length spatial_ref valid_time"
        long_name      String  "100 metre V wind component"
        short_name     String  "100v"
        standard_name  String  "northward_wind"
        step_type      String  "instant"

      Structural metadata:
        COMPRESSOR  { "name": "sharding_indexed", "configuration": { "chunk_shape": [ 1, 61, 241, 240 ], "codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "blosc", "configuration": { "typesize": 4, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } } ], "index_codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "crc32c" } ], "index_location": "end" } }

  - /wind_v_10m:
      Dimensions:    (/init_time, /lead_time, /latitude, /longitude)
      Shape:         [3264, 61, 721, 1440]
      Chunk size:    [1, 61, 241, 240]
      Type:          Float32
      Unit:          m s-1
      Nodata value:  "NaN"

      Attributes:
            Name        Type                                     Value
        -------------  ------  --------------------------------------------------------------------------
        _FillValue     String  "AAAAAAAA+H8="
        coordinates    String  "expected_forecast_length ingested_forecast_length spatial_ref valid_time"
        long_name      String  "10 metre V wind component"
        short_name     String  "10v"
        standard_name  String  "northward_wind"
        step_type      String  "instant"

      Structural metadata:
        COMPRESSOR  { "name": "sharding_indexed", "configuration": { "chunk_shape": [ 1, 61, 241, 240 ], "codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "blosc", "configuration": { "typesize": 4, "cname": "zstd", "clevel": 3, "shuffle": "shuffle", "blocksize": 0 } } ], "index_codecs": [ { "name": "bytes", "configuration": { "endian": "little" } }, { "name": "crc32c" } ], "index_location": "end" } }</code></pre>
</details>
<p>It’s a lot of output! For comparison let’s see what xarray will show as equivalent summary.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb4-1"><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>xarray.Dataset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> Size: <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">14TB</span></span>
<span id="cb4-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Dimensions:</span>                                     <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">init_time:</span> 3264,</span>
<span id="cb4-3">                                                 <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">latitude:</span> 721, lead_time: 61,</span>
<span id="cb4-4">                                                 <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">longitude:</span> 1440<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb4-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Coordinates:</span></span>
<span id="cb4-6">  <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">*</span> init_time                                   <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">init_time</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">datetime64</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">[</span>ns<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">]</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">26kB</span> ...</span>
<span id="cb4-7">  <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">*</span> latitude                                    <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">latitude</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">float64</span> 6kB 90.0 ....</span>
<span id="cb4-8">  <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">*</span> lead_time                                   <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">lead_time</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">float64</span> 488B 0.0 ...</span>
<span id="cb4-9">  <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">*</span> longitude                                   <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">longitude</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">float64</span> 12kB <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-180...</span></span>
<span id="cb4-10"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Data</span> variables: <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">12/21</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb4-11">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">dew_point_temperature_2m</span>                    <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">init_time,</span> lead_time, latitude, longitude<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">float32</span> 827GB dask.array<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>chunksize=<span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">3264,</span> 61, 721, 1440<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">,</span> meta=np.ndarray<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb4-12">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">downward_long_wave_radiation_flux_surface</span>   <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">init_time,</span> lead_time, latitude, longitude<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">float32</span> 827GB dask.array<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>chunksize=<span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">3264,</span> 61, 721, 1440<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">,</span> meta=np.ndarray<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb4-13">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">downward_short_wave_radiation_flux_surface</span>  <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">init_time,</span> lead_time, latitude, longitude<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">float32</span> 827GB dask.array<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>chunksize=<span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">3264,</span> 61, 721, 1440<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">,</span> meta=np.ndarray<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb4-14">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">expected_forecast_length</span>                    <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">init_time</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">float64</span> 26kB dask.array<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>chunksize=<span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">3264,</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">,</span> meta=np.ndarray<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb4-15">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">geopotential_height_500hpa</span>                  <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">init_time,</span> lead_time, latitude, longitude<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">float32</span> 827GB dask.array<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>chunksize=<span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">3264,</span> 61, 721, 1440<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">,</span> meta=np.ndarray<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb4-16">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">geopotential_height_850hpa</span>                  <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">init_time,</span> lead_time, latitude, longitude<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">float32</span> 827GB dask.array<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>chunksize=<span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">3264,</span> 61, 721, 1440<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">,</span> meta=np.ndarray<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb4-17">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">...</span>                                          ...</span>
<span id="cb4-18">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">total_cloud_cover_atmosphere</span>                <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">init_time,</span> lead_time, latitude, longitude<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">float32</span> 827GB dask.array<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>chunksize=<span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">3264,</span> 61, 721, 1440<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">,</span> meta=np.ndarray<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb4-19">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">valid_time</span>                                  <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">init_time,</span> lead_time<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">float32</span> 796kB dask.array<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>chunksize=<span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">3264,</span> 61<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">,</span> meta=np.ndarray<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb4-20">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">wind_u_100m</span>                                 <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">init_time,</span> lead_time, latitude, longitude<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">float32</span> 827GB dask.array<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>chunksize=<span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">3264,</span> 61, 721, 1440<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">,</span> meta=np.ndarray<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb4-21">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">wind_u_10m</span>                                  <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">init_time,</span> lead_time, latitude, longitude<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">float32</span> 827GB dask.array<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>chunksize=<span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">3264,</span> 61, 721, 1440<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">,</span> meta=np.ndarray<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb4-22">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">wind_v_100m</span>                                 <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">init_time,</span> lead_time, latitude, longitude<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">float32</span> 827GB dask.array<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>chunksize=<span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">3264,</span> 61, 721, 1440<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">,</span> meta=np.ndarray<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb4-23">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">wind_v_10m</span>                                  <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">init_time,</span> lead_time, latitude, longitude<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">float32</span> 827GB dask.array<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>chunksize=<span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">3264,</span> 61, 721, 1440<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">,</span> meta=np.ndarray<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb4-24"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Attributes:</span></span>
<span id="cb4-25">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">attribution:</span>          ECMWF AIFS Single forecast data processed by dynami...</span>
<span id="cb4-26">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">dataset_id:</span>           ecmwf-aifs-single-forecast</span>
<span id="cb4-27">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">dataset_version:</span>      0.1.0</span>
<span id="cb4-28">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">description:</span>          Weather forecasts from the ECMWF Artificial Intelli...</span>
<span id="cb4-29">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">forecast_domain:</span>      Forecast lead time 0-360 hours <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">0-15</span> days<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">ahead</span></span>
<span id="cb4-30">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">forecast_resolution:</span>  6 hourly</span>
<span id="cb4-31">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">license:</span>              CC-BY-4.0</span>
<span id="cb4-32">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">name:</span>                 ECMWF AIFS Single forecast</span>
<span id="cb4-33">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">spatial_domain:</span>       Global</span>
<span id="cb4-34">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">spatial_resolution:</span>   0.25 degrees <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">~20km</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb4-35">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">time_domain:</span>          Forecasts initialized 2024-04-01 00:00:00 UTC to Pr...</span>
<span id="cb4-36">    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">time_resolution:</span>      Forecasts initialized every 6 hours</span></code></pre></div></div>
<p>That’s a lot denser, but the important thing is that GDAL has access to the same rich underlying Zarr data and note that xarray has clipped the full list of variables in the summary.</p>
<p>There are a few things to unpack in this cli call.</p>
<section id="gdal-multidimensional" class="level3">
<h3 class="anchored" data-anchor-id="gdal-multidimensional">GDAL multidimensional</h3>
<p><code>gdal mdim</code> provides a <a href="https://gdal.org/en/stable/programs/index.html#multidimensional-raster-commands">family of apps</a> for the <a href="https://gdal.org/en/stable/user/multidim_raster_data_model.html">GDAL multidimensional raster model</a> - this means that array data is presented in its native form with potentially multiple dimensions, and multiple variables like the NetCDF4 or HDF5 file type, whereas the classic 2D raster model (a 2D grid with one or more bands) can only present one variable and must unroll higher dimension out as bands.</p>
<p>The xarray output and the gdal mdim info output above is <em>human readable</em>, for a programmatic summary we can call</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb5-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">gdal</span> mdim info <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--format</span> json <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb5-2">  /vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb5-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--config</span> AWS_NO_SIGN_REQUEST YES <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--config</span> AWS_REGION us-west-2</span></code></pre></div></div>
<p>and in R we can do</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb6-1">gdalraster<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">set_config_option</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AWS_NO_SIGN_REQUEST"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"YES"</span>)</span>
<span id="cb6-2">gdalraster<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">set_config_option</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AWS_REGION"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"us-west-2"</span>)</span>
<span id="cb6-3">json <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> gdalraster<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">mdim_info</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">cout =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>)</span>
<span id="cb6-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cat</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">substr</span>(json, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1000</span>))</span>
<span id="cb6-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># {</span></span>
<span id="cb6-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   "type": "group",</span></span>
<span id="cb6-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   "driver": "Icechunk",</span></span>
<span id="cb6-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   "name": "/",</span></span>
<span id="cb6-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   "attributes": {</span></span>
<span id="cb6-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     "attribution": "ECMWF AIFS Single forecast data processed by dynamical.org from ECMWF Open Data.",</span></span>
<span id="cb6-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     "dataset_id": "ecmwf-aifs-single-forecast",</span></span>
<span id="cb6-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     "dataset_version": "0.1.0",</span></span>
<span id="cb6-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     "description": "Weather forecasts from the ECMWF Artificial Intelligence Forecasting System (AIFS) Single model.",</span></span>
<span id="cb6-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     "forecast_domain": "Forecast lead time 0-360 hours (0-15 days) ahead",</span></span>
<span id="cb6-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     "forecast_resolution": "6 hourly",</span></span>
<span id="cb6-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     "license": "CC-BY-4.0",</span></span>
<span id="cb6-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     "name": "ECMWF AIFS Single forecast",</span></span>
<span id="cb6-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     "spatial_domain": "Global",</span></span>
<span id="cb6-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     "spatial_resolution": "0.25 degrees (~20km)",</span></span>
<span id="cb6-20"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     "time_domain": "Forecasts initialized 2024-04-01 00:00:00 UTC to Present",</span></span>
<span id="cb6-21"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     "time_resolution": "Forecasts initialized every 6 hours"</span></span>
<span id="cb6-22"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   },</span></span>
<span id="cb6-23"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   "dimensions": [</span></span>
<span id="cb6-24"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     {</span></span>
<span id="cb6-25"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#       "name": "init_time",</span></span>
<span id="cb6-26"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#       "full_name": "/init_time",</span></span>
<span id="cb6-27"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#       "size": 3264,</span></span>
<span id="cb6-28"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#       "indexing_variable": "/init_time"</span></span>
<span id="cb6-29"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     },</span></span>
<span id="cb6-30"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     {</span></span>
<span id="cb6-31"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#       "name": "latitude",</span></span>
<span id="cb6-32"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#       "full_name": "/latit...  </span></span></code></pre></div></div>
<p>or with terra (in-dev, only on github atm)</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb7-1">terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">setGDALconfig</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AWS_NO_SIGN_REQUEST"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"YES"</span>)</span>
<span id="cb7-2">terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">setGDALconfig</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AWS_REGION"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"us-west-2"</span>)</span>
<span id="cb7-3">terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rast</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk"</span>)</span>
<span id="cb7-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># class       : SpatRaster</span></span>
<span id="cb7-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># size        : 721, 1440, 3384768  (nrow, ncol, nlyr)</span></span>
<span id="cb7-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># resolution  : 0.25, 0.25  (x, y)</span></span>
<span id="cb7-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># extent      : -180.125, 179.875, -90.125, 90.125  (xmin, xmax, ymin, ymax)</span></span>
<span id="cb7-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># coord. ref. : lon/lat WGS 84 (CRS84) (OGC:CRS84)</span></span>
<span id="cb7-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># sources     : v0.1.0.icechunk</span></span>
<span id="cb7-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># varnames    : dew_point_temperature_2m (2 metre dewpoint temperature)</span></span>
<span id="cb7-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#               downward_long_wave_radiation_flux_surface (Surface downward long-wave radiation flux)</span></span>
<span id="cb7-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#               downward_short_wave_radiation_flux_surface (Surface downward short-wave radiation flux)</span></span>
<span id="cb7-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#               geopotential_height_500hpa (Geopotential height)</span></span>
<span id="cb7-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#               geopotential_height_850hpa (Geopotential height)</span></span>
<span id="cb7-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#               ...</span></span>
<span id="cb7-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># names       :    dew_p~600_1,    dew_p~600_2,    dew_p~600_3,    dew_p~600_4,    dew_p~600_5,    dew_p~600_6, ...</span></span>
<span id="cb7-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># unit        : degree_Celsius, degree_Celsius, degree_Celsius, degree_Celsius, degree_Celsius, degree_Celsius, ...</span></span>
<span id="cb7-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># depth       : 1.71193e+09 to 1.78241e+09 (: 3264 steps)</span></span>
<span id="cb7-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># time        : 0</span></span>
<span id="cb7-20"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Warning message:</span></span>
<span id="cb7-21"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [rast] skipped array (different geometry): /valid_time</span></span></code></pre></div></div>
<p>or sf/stars</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb8-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Sys.setenv</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AWS_NO_SIGN_REQUEST"</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"YES"</span>)</span>
<span id="cb8-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Sys.setenv</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AWS_REGION"</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"us-west-2"</span>)</span>
<span id="cb8-3">s <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">gdal_read_mdim</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">proxy =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb8-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str</span>(s)</span>
<span id="cb8-5">List of <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span></span>
<span id="cb8-6"> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span> array_list<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span>List of <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">17</span></span>
<span id="cb8-7">  ..<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span> dew_point_temperature_2m                  <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span></span>
<span id="cb8-8">  ..<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span> downward_long_wave_radiation_flux_surface <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span></span>
<span id="cb8-9">  ..<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span> downward_short_wave_radiation_flux_surface<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span></span>
<span id="cb8-10">...</span></code></pre></div></div>
<details>
<summary>
Click here for the full sf structure info output
</summary>
<pre><code>List of 4
 $ array_list:List of 17
  ..$ dew_point_temperature_2m                  : NULL
  ..$ downward_long_wave_radiation_flux_surface : NULL
  ..$ downward_short_wave_radiation_flux_surface: NULL
  ..$ geopotential_height_500hpa                : NULL
  ..$ geopotential_height_850hpa                : NULL
  ..$ geopotential_height_925hpa                : NULL
  ..$ precipitation_surface                     : NULL
  ..$ pressure_reduced_to_mean_sea_level        : NULL
  ..$ pressure_surface                          : NULL
  ..$ temperature_2m                            : NULL
  ..$ temperature_850hpa                        : NULL
  ..$ temperature_925hpa                        : NULL
  ..$ total_cloud_cover_atmosphere              : NULL
  ..$ wind_u_100m                               : NULL
  ..$ wind_u_10m                                : NULL
  ..$ wind_v_100m                               : NULL
  ..$ wind_v_10m                                : NULL
 $ dimensions:List of 4
  ..$ init_time:List of 5
  .. ..$ from     : int 1
  .. ..$ to       : int 3264
  .. ..$ values   :List of 1
  .. .. ..$ : num [1:3264(1d)] 1.71e+09 1.71e+09 1.71e+09 1.71e+09 1.71e+09 ...
  .. .. .. ..- attr(*, "units")= chr "seconds since 1970-01-01"
  .. .. .. ..- attr(*, "d_names")= chr "init_time"
  .. .. .. ..- attr(*, "attributes")= Named chr [1:4] "proleptic_gregorian" "Forecast initialization time" "forecast_reference_time" "{ \"min\": \"2024-04-01T00:00:00\", \"max\": \"Present\" }"
  .. .. .. .. ..- attr(*, "names")= chr [1:4] "calendar" "long_name" "standard_name" "statistics_approximate"
  .. ..$ type     : chr ""
  .. ..$ direction: chr ""
  ..$ lead_time:List of 5
  .. ..$ from     : int 1
  .. ..$ to       : int 61
  .. ..$ values   :List of 1
  .. .. ..$ : num [1:61(1d)] 0 21600 43200 64800 86400 ...
  .. .. .. ..- attr(*, "units")= chr "seconds"
  .. .. .. ..- attr(*, "d_names")= chr "lead_time"
  .. .. .. ..- attr(*, "attributes")= Named chr [1:5] "AAAAAAAA+H8=" "timedelta64[us]" "Forecast lead time" "forecast_period" ...
  .. .. .. .. ..- attr(*, "names")= chr [1:5] "_FillValue" "dtype" "long_name" "standard_name" ...
  .. ..$ type     : chr ""
  .. ..$ direction: chr ""
  ..$ latitude :List of 5
  .. ..$ from     : int 1
  .. ..$ to       : int 721
  .. ..$ values   :List of 1
  .. .. ..$ : num [1:721(1d)] 90 89.8 89.5 89.2 89 ...
  .. .. .. ..- attr(*, "units")= chr "degree_north"
  .. .. .. ..- attr(*, "d_names")= chr "latitude"
  .. .. .. ..- attr(*, "attributes")= Named chr [1:4] "AAAAAAAA+H8=" "Y" "Latitude" "{ \"min\": -90.0, \"max\": 90.0 }"
  .. .. .. .. ..- attr(*, "names")= chr [1:4] "_FillValue" "axis" "long_name" "statistics_approximate"
  .. ..$ type     : chr "HORIZONTAL_Y"
  .. ..$ direction: chr ""
  ..$ longitude:List of 5
  .. ..$ from     : int 1
  .. ..$ to       : int 1440
  .. ..$ values   :List of 1
  .. .. ..$ : num [1:1440(1d)] -180 -180 -180 -179 -179 ...
  .. .. .. ..- attr(*, "units")= chr "degree_east"
  .. .. .. ..- attr(*, "d_names")= chr "longitude"
  .. .. .. ..- attr(*, "attributes")= Named chr [1:4] "AAAAAAAA+H8=" "X" "Longitude" "{ \"min\": -180.0, \"max\": 179.75 }"
  .. .. .. .. ..- attr(*, "names")= chr [1:4] "_FillValue" "axis" "long_name" "statistics_approximate"
  .. ..$ type     : chr "HORIZONTAL_X"
  .. ..$ direction: chr ""
 $ srs       : chr NA
 $ geometry  : list()</code></pre>
</details>
<p>That is leveraged by stars to give</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb10-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Sys.setenv</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AWS_NO_SIGN_REQUEST"</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"YES"</span>)</span>
<span id="cb10-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">Sys.setenv</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AWS_REGION"</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"us-west-2"</span>)</span>
<span id="cb10-3">stars<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">read_mdim</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">proxy =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb10-4"></span>
<span id="cb10-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># stars_proxy object with 17 attributes in 17 file(s):</span></span>
<span id="cb10-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># $dew_point_temperature_2m</span></span>
<span id="cb10-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [1] "[...]/v0.1.0.icechunk"</span></span>
<span id="cb10-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># </span></span>
<span id="cb10-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># $downward_long_wave_radiation_flux_surface</span></span>
<span id="cb10-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [1] "[...]/v0.1.0.icechunk"</span></span>
<span id="cb10-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># </span></span>
<span id="cb10-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ...</span></span>
<span id="cb10-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># </span></span>
<span id="cb10-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># $temperature_925hpa</span></span>
<span id="cb10-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [1] "[...]/v0.1.0.icechunk"</span></span>
<span id="cb10-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># </span></span>
<span id="cb10-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># $total_cloud_cover_atmosphere</span></span>
<span id="cb10-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [1] "[...]/v0.1.0.icechunk"</span></span>
<span id="cb10-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># </span></span>
<span id="cb10-20"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># $wind_u_100m</span></span>
<span id="cb10-21"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [1] "[...]/v0.1.0.icechunk"</span></span>
<span id="cb10-22"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># </span></span>
<span id="cb10-23"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># $wind_u_10m</span></span>
<span id="cb10-24"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [1] "[...]/v0.1.0.icechunk"</span></span>
<span id="cb10-25"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># </span></span>
<span id="cb10-26"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># $wind_v_100m</span></span>
<span id="cb10-27"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [1] "[...]/v0.1.0.icechunk"</span></span>
<span id="cb10-28"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># </span></span>
<span id="cb10-29"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># $wind_v_10m</span></span>
<span id="cb10-30"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [1] "[...]/v0.1.0.icechunk"</span></span>
<span id="cb10-31"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># </span></span>
<span id="cb10-32"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># dimension(s):</span></span>
<span id="cb10-33"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#           from   to         offset     delta  refsys x/y</span></span>
<span id="cb10-34"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># longitude    1 1440         -180.1      0.25      NA [x]</span></span>
<span id="cb10-35"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># latitude     1  721          90.12     -0.25      NA [y]</span></span>
<span id="cb10-36"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># lead_time    1   61          0 [s] 21600 [s] udunits    </span></span>
<span id="cb10-37"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># init_time    1 3264 2024-04-01 UTC   6 hours POSIXct    </span></span></code></pre></div></div>
<p>Please note that this store contains pretty simple regular array data, and so the relationship of data arrays to their coordinates is simple, the map in longitude/latitude is a regular grid and so slices of that can correspond to the normal <em>raster</em> model where a bounding box (or corner coordinates + resolution) is sufficient to georeference the data. Multidimensional data can be more general and more complex than that.</p>
<p>This post won’t explore any more detail of the various ways R packages support mdim.</p>
</section>
</section>
<section id="multidimensional" class="level2">
<h2 class="anchored" data-anchor-id="multidimensional">Multidimensional?</h2>
<p><em>Please hang in here for a few sections</em>, we are diverting down a GDAL and object storage rabbit hole, but it’s not long and then we’ll get back to Icechunk in the naming a thing section.</p>
<p>We can treat this store as raster data in the normal 2D way, but note that there are caveats and real performance implications depending on the way data is laid out in a given store.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb11-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">gdalinfo</span> /vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--config</span> AWS_NO_SIGN_REQUEST YES <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--config</span> AWS_REGION us-west-2</span>
<span id="cb11-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Driver:</span> Icechunk/Icechunk</span>
<span id="cb11-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Files:</span> /vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk</span>
<span id="cb11-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Size</span> is 512, 512</span>
<span id="cb11-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Subdatasets:</span></span>
<span id="cb11-6">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_1_NAME</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ZARR:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsiicechunk/{/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk}"</span>:/dew_point_temperature_2m</span>
<span id="cb11-7">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_1_DESC</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3264x61x721x1440</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">]</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/dew_point_temperature_2m</span> <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Float32</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-8">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_2_NAME</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ZARR:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsiicechunk/{/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk}"</span>:/downward_long_wave_radiation_flux_surface</span>
<span id="cb11-9">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_2_DESC</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3264x61x721x1440</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">]</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/downward_long_wave_radiation_flux_surface</span> <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Float32</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-10">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_3_NAME</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ZARR:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsiicechunk/{/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk}"</span>:/downward_short_wave_radiation_flux_surface</span>
<span id="cb11-11">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_3_DESC</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3264x61x721x1440</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">]</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/downward_short_wave_radiation_flux_surface</span> <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Float32</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-12">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_4_NAME</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ZARR:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsiicechunk/{/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk}"</span>:/geopotential_height_500hpa</span>
<span id="cb11-13">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_4_DESC</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3264x61x721x1440</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">]</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/geopotential_height_500hpa</span> <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Float32</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-14">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_5_NAME</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ZARR:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsiicechunk/{/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk}"</span>:/geopotential_height_850hpa</span>
<span id="cb11-15">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_5_DESC</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3264x61x721x1440</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">]</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/geopotential_height_850hpa</span> <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Float32</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-16">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_6_NAME</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ZARR:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsiicechunk/{/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk}"</span>:/geopotential_height_925hpa</span>
<span id="cb11-17">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_6_DESC</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3264x61x721x1440</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">]</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/geopotential_height_925hpa</span> <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Float32</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-18">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_7_NAME</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ZARR:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsiicechunk/{/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk}"</span>:/precipitation_surface</span>
<span id="cb11-19">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_7_DESC</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3264x61x721x1440</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">]</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/precipitation_surface</span> <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Float32</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-20">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_8_NAME</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ZARR:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsiicechunk/{/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk}"</span>:/pressure_reduced_to_mean_sea_level</span>
<span id="cb11-21">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_8_DESC</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3264x61x721x1440</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">]</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/pressure_reduced_to_mean_sea_level</span> <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Float32</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-22">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_9_NAME</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ZARR:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsiicechunk/{/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk}"</span>:/pressure_surface</span>
<span id="cb11-23">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_9_DESC</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3264x61x721x1440</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">]</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/pressure_surface</span> <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Float32</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-24">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_10_NAME</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ZARR:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsiicechunk/{/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk}"</span>:/temperature_2m</span>
<span id="cb11-25">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_10_DESC</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3264x61x721x1440</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">]</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/temperature_2m</span> <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Float32</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-26">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_11_NAME</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ZARR:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsiicechunk/{/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk}"</span>:/temperature_850hpa</span>
<span id="cb11-27">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_11_DESC</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3264x61x721x1440</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">]</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/temperature_850hpa</span> <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Float32</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-28">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_12_NAME</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ZARR:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsiicechunk/{/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk}"</span>:/temperature_925hpa</span>
<span id="cb11-29">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_12_DESC</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3264x61x721x1440</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">]</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/temperature_925hpa</span> <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Float32</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-30">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_13_NAME</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ZARR:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsiicechunk/{/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk}"</span>:/total_cloud_cover_atmosphere</span>
<span id="cb11-31">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_13_DESC</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3264x61x721x1440</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">]</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/total_cloud_cover_atmosphere</span> <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Float32</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-32">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_14_NAME</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ZARR:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsiicechunk/{/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk}"</span>:/valid_time</span>
<span id="cb11-33">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_14_DESC</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3264x61</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">]</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/valid_time</span> <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Int64</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-34">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_15_NAME</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ZARR:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsiicechunk/{/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk}"</span>:/wind_u_100m</span>
<span id="cb11-35">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_15_DESC</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3264x61x721x1440</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">]</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/wind_u_100m</span> <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Float32</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-36">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_16_NAME</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ZARR:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsiicechunk/{/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk}"</span>:/wind_u_10m</span>
<span id="cb11-37">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_16_DESC</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3264x61x721x1440</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">]</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/wind_u_10m</span> <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Float32</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-38">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_17_NAME</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ZARR:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsiicechunk/{/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk}"</span>:/wind_v_100m</span>
<span id="cb11-39">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_17_DESC</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3264x61x721x1440</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">]</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/wind_v_100m</span> <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Float32</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-40">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_18_NAME</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ZARR:<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsiicechunk/{/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk}"</span>:/wind_v_10m</span>
<span id="cb11-41">  <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">SUBDATASET_18_DESC</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">[</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">3264x61x721x1440</span><span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">]</span> <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/wind_v_10m</span> <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Float32</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-42"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Corner</span> Coordinates:</span>
<span id="cb11-43"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Upper</span> Left  <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span>    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">0.0,</span>    0.0<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-44"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Lower</span> Left  <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span>    <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">0.0,</span>  512.0<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-45"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Upper</span> Right <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span>  <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">512.0,</span>    0.0<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-46"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Lower</span> Right <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span>  <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">512.0,</span>  512.0<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb11-47"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">Center</span>      <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span>  <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">256.0,</span>  256.0<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span></code></pre></div></div>
<p>Those strings like <code>"ZARR:\"/vsiicechunk/{/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk}\":/dew_point_temperature_2m"</code> (note the embedded escaped quotes) can be used by the more tradition GDAL classic raster model.</p>
<p>We’ll come back to the <code>/vsiicechunk</code> and <code>{/vsis3}</code> parts of this string a bit later.</p>
</section>
<section id="setting-config-options-and-environment-variables-for-gdal" class="level2">
<h2 class="anchored" data-anchor-id="setting-config-options-and-environment-variables-for-gdal">Setting config options and environment variables for GDAL</h2>
<p>GDAL config options can be set through a package wrapper - <code>gdalraster::set_config_option("AWS_NO_SIGN_REQUEST", "YES")</code> or <code>terra::setGDALconfig("AWS_NO_SIGN_REQUEST", "YES")</code> - or as process environment variables (<code>Sys.setenv(AWS_NO_SIGN_REQUEST = "YES")</code>, or <code>KEY=value</code> in the shell). Both routes are <em>global and sticky</em>: once set they can affect every other step, spooky-action-at-a-distance that we want to avoid.</p>
<p>That’s why setting config <em>inline</em> - <code>--config KEY=VALUE</code> on the <code>gdal mdim info</code> call is a clean option: it lives and dies in one line, a bit like <code>with</code> in Python or <code>with()</code> in R. The R equivalent for env vars and options is <code>withr::with_envvar()</code> / <code>withr::with_options()</code>, true scoped context managers.</p>
<p>With online storage it can be bewildering what exact configuration you need and what you have, but it does come down to a fairly comprehensible set of ideas. This post is not a guide to online storage access configuration, or a guide to <a href="https://gdal.org/en/stable/user/configoptions.html">GDAL configuration options</a> but there’s a finite set of concepts in this jungle so don’t be put off by the complexity.</p>
<p><strong>Auth - keyed vs anonymous.</strong> If a dataset is not public, you will need something like <code>AWS_ACCESS_KEY_ID</code> and <code>AWS_SECRET_ACCESS_KEY</code>. If it is public, you will need something like <code>AWS_NO_SIGN_REQUEST=YES</code> (or <code>--no-sign-request</code>).</p>
<p><strong>Addressing.</strong> Everything is a <em>key</em> inside a <em>bucket</em>. You reference an object as <code>s3://bucket/key</code> (Amazon), <code>gs://bucket/key</code> (Google Storage), <code>abfss://…</code> (Azure storage) - and that’s the whole model. (Azure bends it slightly: a storage <em>account</em> holds <em>containers</em> holding <em>blobs</em>, and the scheme carries the account, <code>abfss://container@account.dfs.core.windows.net/key</code>.)</p>
<p><strong>Endpoint, region, hosting-style.</strong> That object is hosted on an <em>endpoint</em> - <code>s3://</code> is shorthand for <code>s3.&lt;region&gt;.amazonaws.com</code>, <code>gs://</code> for <code>storage.googleapis.com</code>. A <em>custom</em> endpoint means pointing at your own host - <code>AWS_S3_ENDPOINT=your.endpoint.net</code> - so that <code>s3</code> in <code>s3://bucket/key</code> resolves to your host and not Amazon’s. Virtual hosting just means the bucket goes before the endpoint in the final url <code>https://bucket.s3.&lt;region&gt;.amazonaws.com/key</code> vs <code>https://my.custom.endpoint.net/bucket/key</code>. It’s confusing, until you know what it does and when you might need it. For non-AWS S3 implementations (MinIO, Ceph) this usually goes together with path-style addressing, <code>AWS_VIRTUAL_HOSTING=NO</code>, since most of them don’t do virtual-host style. Again on a custom S3 <code>AWS_REGION</code> is sometimes needed but probably shouldn’t matter - set it to ““.</p>
<section id="gdal-and-the-virtual-filesystem" class="level3">
<h3 class="anchored" data-anchor-id="gdal-and-the-virtual-filesystem">GDAL and the virtual filesystem</h3>
<p>Everything above is generic - true of any R tool talking to object storage - and GDAL addresses remote objects through its own <em>virtual filesystem</em> (VSI) rather than the <code>s3://</code>-style schemes. Again, bucket, key but with a different prefix:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>cloud-native</th>
<th>GDAL VSI</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>s3://bucket/key</code></td>
<td><code>/vsis3/bucket/key</code></td>
</tr>
<tr class="even">
<td><code>gs://bucket/key</code></td>
<td><code>/vsigs/bucket/key</code></td>
</tr>
<tr class="odd">
<td><code>abfss://container/key</code></td>
<td><code>/vsiaz/container/key</code></td>
</tr>
</tbody>
</table>
<p>This is the most common first stumble: <code>s3://…</code> handed to <code>terra::rast()</code> or <code>vapour::gdal_raster_data()</code> won’t open; the <code>/vsis3/…</code> form will. Be aware that some packages in R and other langs will do their own heuristics to wrap different protocols: ‘https://’, ‘s3://’, ‘virtualizarr://’ etc - this can be helpful but also very confusing, and this post cannot summarize that landscape. When GDAL is in use my preference is to use GDAL protocols without wrapping. The config options from above (<code>AWS_NO_SIGN_REQUEST</code>, <code>AWS_S3_ENDPOINT</code>, and friends) all still apply unchanged - VSI only changes how you <em>name</em> the object, it doesn’t affect how or if it’s accessible.</p>
<p>The same <code>/vsi*</code> mechanism abstracts over a whole zoo of access methods, all of which GDAL then reads as if they were ordinary local files:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 50%">
<col style="width: 50%">
</colgroup>
<thead>
<tr class="header">
<th>prefix</th>
<th>what it opens</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><code>/vsicurl/</code></td>
<td>any file over plain HTTP/HTTPS (range-requested, not downloaded whole)</td>
</tr>
<tr class="even">
<td><code>/vsizip/</code></td>
<td>a file <em>inside</em> a ZIP archive</td>
</tr>
<tr class="odd">
<td><code>/vsitar/</code>, <code>/vsigzip/</code></td>
<td>likewise for TAR / gzip</td>
</tr>
<tr class="even">
<td><code>/vsimem/</code></td>
<td>an in-memory buffer</td>
</tr>
<tr class="odd">
<td><code>/vsiswift/</code>, <code>/vsioss/</code></td>
<td>OpenStack Swift, Alibaba OSS, …</td>
</tr>
<tr class="even">
<td><code>/vsistdout/</code></td>
<td>stream output straight to standard out</td>
</tr>
</tbody>
</table>
<p>The real payoff is that these <em>compose</em> - you chain prefixes right-to-left to stack the abstractions. To read a single GeoTIFF sitting inside a ZIP that lives in an S3 bucket, none of which you download in full:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb12-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/vsizip//vsis3/bucket/archive.zip/inside.tif</span></span></code></pre></div></div>
<p>GDAL signs the S3 request, range-reads the ZIP’s central directory, pulls just the bytes for <code>inside.tif</code>, and hands you a raster. That’s the whole world the VSI layer opens up: object storage, archives, compression, and HTTP are all just prefixes over the same “name a thing, read its bytes” idea.</p>
</section>
<section id="sec-naming" class="level3">
<h3 class="anchored" data-anchor-id="sec-naming">Naming a thing inside a thing</h3>
<p>That earlier chain glued its prefixes by plain concatenation - <code>/vsizip/</code> meets <code>/vsis3/</code>, and since both carry a leading slash you get the doubled <code>//</code>. GDAL finds the archive boundary inside that run by heuristic: it spots the <code>.zip</code>. The brace form, <code>/vsizip/{…}</code>, makes the boundary <em>explicit</em> rather than guessed - and once the inner path carries its own punctuation (a URL with a query string, say) explicit is the only thing that works.</p>
<p>GEBCO 2026 ships its global grid as a single zip behind a download URL ending <code>…gebco_2026_geotiff.zip?download=1</code>. To reach into it without pulling the whole archive, chain <code>/vsizip/</code> over <code>/vsicurl/</code>, braces around the inner path because of that <code>?download=1</code> which masks the fact that it’s a zip file:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb13-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">/vsizip/{/vsicurl/https://dap.ceda.ac.uk/bodc/gebco/global/gebco_2026/ice_surface_elevation/geotiff/gebco_2026_geotiff.zip?download=1}</span></span></code></pre></div></div>
<p>If we point GDAL at that archive it can’t return a single raster, so it tells you what’s in there instead:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb14-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">ERROR</span> 4: ... not recognized as being in a supported file format.</span>
<span id="cb14-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">The</span> archive contains several files:</span>
<span id="cb14-3">  <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">…}/gebco_2026_n0.0_s-90.0_w0.0_e90.0_geotiff.tif</span></span>
<span id="cb14-4">  <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">…}/gebco_2026_n0.0_s-90.0_w-180.0_e-90.0_geotiff.tif</span></span>
<span id="cb14-5">  <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">…</span>   <span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">(</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">six</span> more files<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">)</span></span>
<span id="cb14-6">  <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">…}/GEBCO_Grid_documentation.pdf</span></span>
<span id="cb14-7">  <span class="ex" style="color: null;
background-color: null;
font-style: inherit;">…}/GEBCO_Grid_terms_of_use.pdf</span></span></code></pre></div></div>
<p>That listing is a <em>logical</em> filesystem - real files physically inside the zip in an opaque form, enumerated out of a remote archive. Append one of them after the closing brace and you’ve named a single item; GDAL opens it.</p>
<p>Compare that to the subdataset syntax, multiple subdatasets of classic 2D raster we saw in the Icechunk store above:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb15-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">ZARR:</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsiicechunk/{/vsis3/.../v0.1.0.icechunk}"</span><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">:/dew_point_temperature_2m</span></span></code></pre></div></div>
<p>Again we have bracing of an internal <code>/vsi*</code> item but the thing named after it, <code>/dew_point_temperature_2m</code>, isn’t a file but a variable in a <em>logical</em> hierarchy, a node in the Zarr data model. <code>/vsizip/</code> descends into the <em>structural</em> contents of an archive, <code>ZARR:</code> over <code>/vsiicechunk/</code> into the <em>logical</em> contents of a data model - that’s why we see different forms of addressing in stores.</p>
<p>GDAL can list things, here we use <code>gdal vsi list</code> to see the structural contents:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb16-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">gdal</span> vsi list   /vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb16-2">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--config</span> AWS_NO_SIGN_REQUEST YES <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--config</span> AWS_REGION us-west-2</span>
<span id="cb16-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">repo</span></span>
<span id="cb16-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">chunks</span></span>
<span id="cb16-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">manifests</span></span>
<span id="cb16-6"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">overwritten</span></span>
<span id="cb16-7"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">snapshots</span></span>
<span id="cb16-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">transactions</span></span></code></pre></div></div>
<p>ok we think, those look like directories so let’s go deeper (eek!):</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb17-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">gdal</span> vsi list   /vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk/chunks <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb17-2"> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--config</span> AWS_NO_SIGN_REQUEST YES <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--config</span> AWS_REGION us-west-2</span>
<span id="cb17-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">0003DSF0M0N9B5MP3R9G</span></span>
<span id="cb17-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">0010VXY96ABV0RP45T30</span></span>
<span id="cb17-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">0015TP2QVGT990JE9PM0</span></span>
<span id="cb17-6"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">...</span></span></code></pre></div></div>
<p>yes it’s valid content but not something we can understand - we are seeing the internal structure of a complex sophisticated cloud format. We know it’s Icechunk so lets wield the Icechunk virtualization tool</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb18-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">gdal</span> vsi list /vsiicechunk/{/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk} <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\</span></span>
<span id="cb18-2"> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--config</span> AWS_NO_SIGN_REQUEST YES <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--config</span> AWS_REGION us-west-2</span>
<span id="cb18-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">zarr.json</span></span>
<span id="cb18-4"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">dew_point_temperature_2m</span></span>
<span id="cb18-5"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">downward_long_wave_radiation_flux_surface</span></span>
<span id="cb18-6"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">downward_short_wave_radiation_flux_surface</span></span>
<span id="cb18-7"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">expected_forecast_length</span></span>
<span id="cb18-8"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">geopotential_height_500hpa</span></span>
<span id="cb18-9"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">geopotential_height_850hpa</span></span>
<span id="cb18-10"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">geopotential_height_925hpa</span></span>
<span id="cb18-11"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">ingested_forecast_length</span></span>
<span id="cb18-12"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">init_time</span></span>
<span id="cb18-13"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">latitude</span></span>
<span id="cb18-14"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">lead_time</span></span>
<span id="cb18-15"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">longitude</span></span>
<span id="cb18-16"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">precipitation_surface</span></span>
<span id="cb18-17"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pressure_reduced_to_mean_sea_level</span></span>
<span id="cb18-18"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pressure_surface</span></span>
<span id="cb18-19"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">spatial_ref</span></span>
<span id="cb18-20"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">temperature_2m</span></span>
<span id="cb18-21"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">temperature_850hpa</span></span>
<span id="cb18-22"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">temperature_925hpa</span></span>
<span id="cb18-23"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">total_cloud_cover_atmosphere</span></span>
<span id="cb18-24"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">valid_time</span></span>
<span id="cb18-25"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">wind_u_100m</span></span>
<span id="cb18-26"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">wind_u_10m</span></span>
<span id="cb18-27"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">wind_v_100m</span></span>
<span id="cb18-28"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">wind_v_10m</span></span></code></pre></div></div>
<p>and now we are back in <em>logical</em> ground, this is the interpretation that <code>gdal mdim info</code> gave us above, not raw listing of the structural contents.</p>
<p>That shows some of the depth of GDAL abstraction. “Name a thing, read its bytes” it’s not just bytes on a disk. A thing can be an object in a bucket, a member of an archive, a variable in a chunked store, etc - and the path is the recipe for access.</p>
<p>The distinction between <em>material</em> structure (thousands of small manifest files on S3) and <em>logical/virtual</em> structure (the clean array hierarchy you see via <code>/vsiicechunk/</code>) is a key icechunk concept. GDAL’s <code>/vsi*</code> filesystem stack handles this transparently - the same way <code>/vsicurl/</code>, <code>/vsis3/</code>, <code>/vsigs/</code> abstract over HTTP and object stores.</p>
</section>
</section>
<section id="what-about-python" class="level2">
<h2 class="anchored" data-anchor-id="what-about-python">What about python?</h2>
<p>Above we saw how GDAL and some R packages interact with the Icechunk store, how do we do that in Python?</p>
<p>This time we don’t have a single description of the store, we have to use infrastructure to encode the details.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb19-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> icechunk</span>
<span id="cb19-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> xarray </span>
<span id="cb19-3"></span>
<span id="cb19-4">storage <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> icechunk.s3_storage(</span>
<span id="cb19-5">    bucket<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"dynamical-ecmwf-aifs-single"</span>,</span>
<span id="cb19-6">    prefix<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ecmwf-aifs-single-forecast/v0.1.0.icechunk"</span>,</span>
<span id="cb19-7">    region<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"us-west-2"</span>,</span>
<span id="cb19-8">    anonymous<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>,           <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># the AWS_NO_SIGN_REQUEST=YES equivalent</span></span>
<span id="cb19-9">)</span>
<span id="cb19-10">repo <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> icechunk.Repository.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">open</span>(storage)</span>
<span id="cb19-11">session <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> repo.readonly_session(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"main"</span>)</span>
<span id="cb19-12">ds <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> xarray.open_zarr(session.store, consolidated<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span>
<span id="cb19-13">ds</span>
<span id="cb19-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># &lt;xarray.Dataset&gt; Size: 14TB</span></span>
<span id="cb19-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Dimensions:                                     (init_time: 3265,</span></span>
<span id="cb19-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#                                                  lead_time: 61, latitude: 721,</span></span>
<span id="cb19-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#                                                  longitude: 1440)</span></span>
<span id="cb19-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Coordinates:</span></span>
<span id="cb19-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   * init_time                                   (init_time) datetime64[ns] 26kB ...</span></span>
<span id="cb19-20"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     expected_forecast_length                    (init_time) timedelta64[us] 26kB dask.array&lt;chunksize=(3265,), meta=np.ndarray&gt;</span></span>
<span id="cb19-21"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     ingested_forecast_length                    (init_time) timedelta64[us] 26kB dask.array&lt;chunksize=(3265,), meta=np.ndarray&gt;</span></span>
<span id="cb19-22"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   * lead_time                                   (lead_time) timedelta64[us] 488B ...</span></span>
<span id="cb19-23"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     valid_time                                  (init_time, lead_time) datetime64[ns] 2MB dask.array&lt;chunksize=(3265, 61), meta=np.ndarray&gt;</span></span>
<span id="cb19-24"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   * latitude                                    (latitude) float64 6kB 90.0 ....</span></span>
<span id="cb19-25"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   * longitude                                   (longitude) float64 12kB -180...</span></span>
<span id="cb19-26"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     spatial_ref                                 int64 8B ...</span></span>
<span id="cb19-27"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Data variables: (12/17)</span></span>
<span id="cb19-28"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     dew_point_temperature_2m                    (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(1, 61, 241, 240), meta=np.ndarray&gt;</span></span>
<span id="cb19-29"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     downward_long_wave_radiation_flux_surface   (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(1, 61, 241, 240), meta=np.ndarray&gt;</span></span>
<span id="cb19-30"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     geopotential_height_500hpa                  (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(1, 61, 241, 240), meta=np.ndarray&gt;</span></span>
<span id="cb19-31"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     geopotential_height_850hpa                  (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(1, 61, 241, 240), meta=np.ndarray&gt;</span></span>
<span id="cb19-32"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     precipitation_surface                       (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(1, 61, 241, 240), meta=np.ndarray&gt;</span></span>
<span id="cb19-33"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     downward_short_wave_radiation_flux_surface  (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(1, 61, 241, 240), meta=np.ndarray&gt;</span></span>
<span id="cb19-34"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     ...                                          ...</span></span>
<span id="cb19-35"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     wind_u_100m                                 (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(1, 61, 241, 240), meta=np.ndarray&gt;</span></span>
<span id="cb19-36"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     temperature_925hpa                          (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(1, 61, 241, 240), meta=np.ndarray&gt;</span></span>
<span id="cb19-37"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     total_cloud_cover_atmosphere                (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(1, 61, 241, 240), meta=np.ndarray&gt;</span></span>
<span id="cb19-38"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     wind_u_10m                                  (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(1, 61, 241, 240), meta=np.ndarray&gt;</span></span>
<span id="cb19-39"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     wind_v_100m                                 (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(1, 61, 241, 240), meta=np.ndarray&gt;</span></span>
<span id="cb19-40"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     wind_v_10m                                  (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(1, 61, 241, 240), meta=np.ndarray&gt;</span></span>
<span id="cb19-41"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Attributes:</span></span>
<span id="cb19-42"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     dataset_id:           ecmwf-aifs-single-forecast</span></span>
<span id="cb19-43"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     dataset_version:      0.1.0</span></span>
<span id="cb19-44"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     name:                 ECMWF AIFS Single forecast</span></span>
<span id="cb19-45"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     description:          Weather forecasts from the ECMWF Artificial Intelli...</span></span>
<span id="cb19-46"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     attribution:          ECMWF AIFS Single forecast data processed by dynami...</span></span>
<span id="cb19-47"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     license:              CC-BY-4.0</span></span>
<span id="cb19-48"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     spatial_domain:       Global</span></span>
<span id="cb19-49"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     spatial_resolution:   0.25 degrees (~20km)</span></span>
<span id="cb19-50"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     time_domain:          Forecasts initialized 2024-04-01 00:00:00 UTC to Pr...</span></span>
<span id="cb19-51"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     time_resolution:      Forecasts initialized every 6 hours</span></span>
<span id="cb19-52"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     forecast_domain:      Forecast lead time 0-360 hours (0-15 days) ahead</span></span>
<span id="cb19-53"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     forecast_resolution:  6 hourly</span></span></code></pre></div></div>
<p>We looked above at this xarray representation but up there we didn’t use the same infrastructure or show the code. The one above was made with</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb20-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="cb20-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> xarray</span>
<span id="cb20-3"></span>
<span id="cb20-4">os.environ[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AWS_NO_SIGN_REQUEST"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"YES"</span></span>
<span id="cb20-5">os.environ[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AWS_REGION"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"us-west-2"</span></span>
<span id="cb20-6"></span>
<span id="cb20-7">ds <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> xarray.open_dataset(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsis3/dynamical-ecmwf-aifs-single/ecmwf-aifs-single-forecast/v0.1.0.icechunk"</span>, engine <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gdalxarray"</span>, chunks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {})</span>
<span id="cb20-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># &lt;xarray.Dataset&gt; Size: 14TB</span></span>
<span id="cb20-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Dimensions:                                     (init_time: 3265,</span></span>
<span id="cb20-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#                                                  latitude: 721, lead_time: 61,</span></span>
<span id="cb20-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#                                                  longitude: 1440)</span></span>
<span id="cb20-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Coordinates:</span></span>
<span id="cb20-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   * init_time                                   (init_time) datetime64[ns] 26kB ...</span></span>
<span id="cb20-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     expected_forecast_length                    (init_time) float64 26kB dask.array&lt;chunksize=(3265,), meta=np.ndarray&gt;</span></span>
<span id="cb20-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     ingested_forecast_length                    (init_time) float64 26kB dask.array&lt;chunksize=(3265,), meta=np.ndarray&gt;</span></span>
<span id="cb20-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   * latitude                                    (latitude) float64 6kB 90.0 ....</span></span>
<span id="cb20-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   * lead_time                                   (lead_time) float64 488B 0.0 ...</span></span>
<span id="cb20-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     valid_time                                  (init_time, lead_time) float32 797kB dask.array&lt;chunksize=(3265, 61), meta=np.ndarray&gt;</span></span>
<span id="cb20-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   * longitude                                   (longitude) float64 12kB -180...</span></span>
<span id="cb20-20"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     spatial_ref                                 float32 4B ...</span></span>
<span id="cb20-21"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Data variables: (12/17)</span></span>
<span id="cb20-22"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     dew_point_temperature_2m                    (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(3265, 61, 721, 1440), meta=np.ndarray&gt;</span></span>
<span id="cb20-23"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     downward_long_wave_radiation_flux_surface   (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(3265, 61, 721, 1440), meta=np.ndarray&gt;</span></span>
<span id="cb20-24"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     downward_short_wave_radiation_flux_surface  (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(3265, 61, 721, 1440), meta=np.ndarray&gt;</span></span>
<span id="cb20-25"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     geopotential_height_500hpa                  (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(3265, 61, 721, 1440), meta=np.ndarray&gt;</span></span>
<span id="cb20-26"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     geopotential_height_850hpa                  (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(3265, 61, 721, 1440), meta=np.ndarray&gt;</span></span>
<span id="cb20-27"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     geopotential_height_925hpa                  (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(3265, 61, 721, 1440), meta=np.ndarray&gt;</span></span>
<span id="cb20-28"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     ...                                          ...</span></span>
<span id="cb20-29"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     temperature_925hpa                          (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(3265, 61, 721, 1440), meta=np.ndarray&gt;</span></span>
<span id="cb20-30"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     total_cloud_cover_atmosphere                (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(3265, 61, 721, 1440), meta=np.ndarray&gt;</span></span>
<span id="cb20-31"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     wind_u_100m                                 (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(3265, 61, 721, 1440), meta=np.ndarray&gt;</span></span>
<span id="cb20-32"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     wind_u_10m                                  (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(3265, 61, 721, 1440), meta=np.ndarray&gt;</span></span>
<span id="cb20-33"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     wind_v_100m                                 (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(3265, 61, 721, 1440), meta=np.ndarray&gt;</span></span>
<span id="cb20-34"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     wind_v_10m                                  (init_time, lead_time, latitude, longitude) float32 827GB dask.array&lt;chunksize=(3265, 61, 721, 1440), meta=np.ndarray&gt;</span></span>
<span id="cb20-35"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Attributes:</span></span>
<span id="cb20-36"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     attribution:          ECMWF AIFS Single forecast data processed by dynami...</span></span>
<span id="cb20-37"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     dataset_id:           ecmwf-aifs-single-forecast</span></span>
<span id="cb20-38"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     dataset_version:      0.1.0</span></span>
<span id="cb20-39"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     description:          Weather forecasts from the ECMWF Artificial Intelli...</span></span>
<span id="cb20-40"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     forecast_domain:      Forecast lead time 0-360 hours (0-15 days) ahead</span></span>
<span id="cb20-41"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     forecast_resolution:  6 hourly</span></span>
<span id="cb20-42"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     license:              CC-BY-4.0</span></span>
<span id="cb20-43"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     name:                 ECMWF AIFS Single forecast</span></span>
<span id="cb20-44"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     spatial_domain:       Global</span></span>
<span id="cb20-45"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     spatial_resolution:   0.25 degrees (~20km)</span></span>
<span id="cb20-46"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     time_domain:          Forecasts initialized 2024-04-01 00:00:00 UTC to Pr...</span></span>
<span id="cb20-47"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     time_resolution:      Forecasts initialized every 6 hours</span></span></code></pre></div></div>
<p>That seems very similar, and hopefully for the in-dev GDAL xarray backend <a href="https://github.com/hypertidy/gdalxarray">gdalxarray</a> it’s an equivalent connection (I’m not going to validate any further here in this post).</p>
<p>What this illustrates is while the configuration set up can be confusing, these are the very same generic store-auth settings cast different ways. Is it desirable to consolidate these? Is one necessarily better or more capable? Icechunk-Python auth and addressing through typed constructor arguments, GDAL through the VSI path plus config.</p>
<section id="what-about-not-python" class="level3">
<h3 class="anchored" data-anchor-id="what-about-not-python">What about not python?</h3>
<p>I don’t have a strong opinion on what’s the right moves, I do have a strong opinion though that this not just a Python community. It was hard looking over the fence at xarray and Zarr and going ‘uhoh, this looks like I can’t ignore it’. The landscape and GDAL’s particular take on it is something I had the privilege to take a long deep dive on. I can’t ever remember the typed constructor incantation above - but I also web search <code>EMPTY_DIR</code> to find <code>GDAL_DISABLE_READDIR_ON_OPEN</code> which I just can’t recall though I reach for it often. This for me is about opening up to audiences that are not already in the tent, and maybe some who won’t ever use Python.</p>
<p>Should we have easier wrappers? Write the helper that sniffs an <code>s3://</code> address and sets the right path-style / anonymous / region knobs? How do we make that cross-language? I love everything out in the open, <code>protocol://path/to/data</code>, a few env vars a few function arguments, but env vars and function calls are hard won expertise.</p>
<p>Is consolidating these worth doing? Is there any scope for that or prior art?</p>
</section>
</section>
<section id="other-r-zarr-readers" class="level2">
<h2 class="anchored" data-anchor-id="other-r-zarr-readers">Other R Zarr readers</h2>
<p>Beyond GDAL, there is growing native R support for Zarr:</p>
<ul>
<li><strong>pizzarr</strong> (CRAN) - pure R Zarr V2 reader; r-universe builds include an optional high-performance Rust backend via the zarrs crate</li>
<li><strong>Rarr</strong> (Bioconducator) - A simple native R reader for Zarr Arrays, Rarr is actively developed and has good support for a growing number of codecs</li>
<li><strong>zarr</strong> (CRAN) - a native implementation in R with support for all required features of Zarr version 3.</li>
<li><strong>CopernicusMarine</strong> - Subset and download marine data from EU Copernicus Marine Service Information, has an in-built Zarr engine designed around the service</li>
<li><strong>zaro</strong> (r-universe) - Low-level Zarr interface (V2 and V3) built on Arrow filesystem and codec infrastructure</li>
<li><strong>RNetCDF</strong>, <strong>ncdf4</strong> - with the right build set up NetCDF has Zarr support, and this for example has been proved for using with <strong>tidync</strong></li>
</ul>
<p>Work is ongoing on multidim API coverage in the R ecosystem, I particularly want tight api bindings to the GDAL API with gdalraster but that will be a while still in the making.</p>
<p>The GDAL multidim API (used by <code>/vsiicechunk/</code>) is distinct from the classic 2D raster driver - terra now auto-detects which to use for the multidim drivers which are VRT/HDF5/NetCDF/Zarr/GRIB. If terra gives unexpected results, check whether it’s related to recent changes in mdim support.</p>
<p>For some time stars via sf has support for accessing Zarr via the classic or multidimensional raster model of GDAL.</p>
<p>For Icechunk specifically see <a href="https://icechunk.io">icechunk.io</a> and see the GDAL icechunk driver docs at <a href="https://gdal.org/en/latest/drivers/raster/icechunk.html">gdal.org icechunk</a> and ZARR driver at <a href="https://gdal.org/en/latest/drivers/raster/zarr.html">gdal.org zarr</a>.</p>


</section>

 ]]></description>
  <category>code</category>
  <category>news</category>
  <guid>https://hypertidy.org/posts/2026-06-26_icechunk-is-coming/</guid>
  <pubDate>Fri, 26 Jun 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Grids: regular, rectilinear, curvilinear</title>
  <dc:creator>Michael Sumner</dc:creator>
  <link>https://hypertidy.org/posts/2026-05-28_regular-rectilinear-curvilinear/</link>
  <description><![CDATA[ 






<section id="the-problem-with-curvilinear" class="level2">
<h2 class="anchored" data-anchor-id="the-problem-with-curvilinear">The problem with “curvilinear”</h2>
<p>The word <em>curvilinear</em> has a problem in geospatial and earth observation. It gets used interchangeably with “has two-dimensional lon/lat coordinate arrays” or even “is in geographic coordinates”. Neither of these is a helpful meaning, and conflating them causes real problems - both in how we think about data and in how we write code to handle it.</p>
<p>This post attempts to explain the terminology from a particular stance. Start with this hierarchy:</p>
<blockquote class="blockquote">
<p><strong>regular -&gt; rectilinear -&gt; curvilinear -&gt; unstructured</strong></p>
</blockquote>
<p>Each step relaxes a geometric constraint, and our starting point is that the first 3 types are a <em>structured grid</em>. The distinguishing criterion between each step is not the coordinate reference system. It is the <em>minimum amount of information needed to specify the position of every cell</em> - which is exactly the amount of information that has to be stored.</p>
<p>This post is a companion to <a href="https://www.hypertidy.org/posts/2026-02-07_cryptic-curvilinear-detection/cryptic-curvilinear">Detecting hidden grids in curvilinear coordinates</a>, which develops the practical detection problem. Here we make a stance about a confusing feature of terminology and explain why it matters with real world examples.</p>
<hr>
</section>
<section id="structured-grids" class="level2">
<h2 class="anchored" data-anchor-id="structured-grids">Structured grids</h2>
<p>“Structured” just means that every element in every dimension has the <em>same number of other-dimension counterparts</em>. It’s rectangular: in 1D that’s a set of intervals, in 2D a mosaic of axis-aligned rectangular cells, in 3D a mosaic of axis-aligned rectangular cuboids, and upwards. We can replace intervals, rectangles, cuboids, tesseracts, … with another kind of element: a point. In 1D it’s just a sequence of points, in 2D and above it’s the Cartesian product of sequences of indices. When we talk about structured vs unstructured we are not talking about the spacing of coordinates in any sense - always just index positions. The index structure is, in more formal terms, a cubical complex or regular CW complex; the Earth system modelling community calls this “logically rectangular,” a term we return to below in <em>“Logically rectangular” and the Earth system model vocabulary</em>.</p>
</section>
<section id="the-ladder-defined-by-storage" class="level2">
<h2 class="anchored" data-anchor-id="the-ladder-defined-by-storage">The ladder, defined by storage</h2>
<p>The cleanest single organizing principle comes from scientific visualization (the VTK data model, the KTH/Weinkauf taxonomy): grid types differ in how <em>implicitly</em> or <em>explicitly</em> positions must be stated.</p>
<section id="regular-uniform" class="level3">
<h3 class="anchored" data-anchor-id="regular-uniform">Regular (uniform)</h3>
<p>Positions are <em>fully implicit</em>. The grid is completely specified by:</p>
<ul>
<li>an origin <img src="https://latex.codecogs.com/png.latex?(x_0,%20y_0)"></li>
<li>a resolution <img src="https://latex.codecogs.com/png.latex?(%5CDelta%20x,%20%5CDelta%20y)"></li>
<li>dimensions <img src="https://latex.codecogs.com/png.latex?(n_x,%20n_y)"></li>
</ul>
<p>(Here if you need please pause for a pedantic <sup>1</sup> footnote).</p>
<p>Every cell centre follows from index arithmetic alone. No coordinate arrays are stored - or if they are, they are redundant.</p>
<div class="cell">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># synthetic example</span></span>
<span id="cb1-2">ex <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">18</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>)  <span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## extent xmin, xmax, ymin, ymax </span></span>
<span id="cb1-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># - same information as bbox xmin,ymin,xmax,ymax</span></span>
<span id="cb1-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#dimension, or shape (ncols,nrows)</span></span>
<span id="cb1-5">dm <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">8</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>)</span>
<span id="cb1-6"></span>
<span id="cb1-7"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(terra)</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-stderr">
<pre><code>terra 1.9.44</code></pre>
</div>
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1">r <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rast</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ext</span>(ex), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ncols =</span> dm[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">nrows =</span> dm[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>])</span>
<span id="cb3-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.polygons</span>(r), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">asp =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lwd =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb3-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">points</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">xyFromCell</span>(r, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ncell</span>(r)))</span></code></pre></div></div>
</details>
<div class="cell-output-display">
<div id="fig-regular" class="quarto-float quarto-figure quarto-figure-center anchored">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-regular-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<img src="https://hypertidy.org/posts/2026-05-28_regular-rectilinear-curvilinear/index_files/figure-html/fig-regular-1.png" class="img-fluid figure-img" width="672">
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-regular-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: A regular grid: uniform spacing, axis-aligned. Fully specified by four numbers and a CRS.
</figcaption>
</figure>
</div>
</div>
</div>
<p>There are two clear ways to think about a regular grid, little area rectangles or a set of centre points - each contains exactly the same information, and each is completely definable by six numbers - ncols, nrows, xmin, ymin, xmax, ymax. Those numbers might be rearrange in order or expressed as <code>offset</code> <code>scale</code> (a transform), but that doesn’t change anything materially.<br>
The netCDF convention for a regular grid stores 1D coordinate variables, a value for every step in the dimension. This is the <strong>degenerate rectilinear</strong> case: the 1D vectors happen to be uniformly spaced, so the six-number specification is sufficient. Many files store them anyway, and many readers treat the uniform case no differently from the variable-spacing case.</p>
<p>What are the coordinates? Well they are the positions of the points above, the centres of a cell. It’s trivial to observe that we only need 8 points for every x coordinate, 5 for every y coordinate. But, in turn we don’t need the coordinates at all <em>because they were generated</em> from 6 numbers. Those six are extremely general, every regular grid ever is a variation on those values: positive ncols, nrows &gt;= 1; any numbers at all for xmin, xmax, ymin, ymax so long at xmax &gt; xmin; ymax &gt; ymin.</p>
<p>The coordinates stored in the 1D vectors are <em>derived</em> values - computed from those 6 numbers by the formula xi=xmin+(i+0.5)⋅Δxx_i = x_{min} + (i + 0.5) x xi=xmin+(i+0.5)⋅Δx (or the equivalent for edges). So storing them is not just redundant, it is a lossy redundancy - the stored floats have accumulated rounding error relative to the exact values the formula would produce. The 6-number specification is not only more compact, it is more precise.</p>
<p><em>In the wild:</em> NSIDC sea ice concentration products, ERA5 reanalysis on a regular lat/lon grid, most remote sensing imagery.</p>
<hr>
</section>
<section id="rectilinear" class="level3">
<h3 class="anchored" data-anchor-id="rectilinear">Rectilinear</h3>
<p>Positions are <em>semi-implicit</em>. The grid remains axis-aligned and orthogonal, but the spacing along each axis may vary independently. Specified by:</p>
<ul>
<li>one 1D vector of <img src="https://latex.codecogs.com/png.latex?x"> positions (length <img src="https://latex.codecogs.com/png.latex?n_x">)</li>
<li>one 1D vector of <img src="https://latex.codecogs.com/png.latex?y"> positions (length <img src="https://latex.codecogs.com/png.latex?n_y">)</li>
</ul>
<p>Cell <img src="https://latex.codecogs.com/png.latex?(i,%20j)"> is at <img src="https://latex.codecogs.com/png.latex?(x_i,%20y_j)"> - still the outer product of two 1D sequences. Two arrays, not <img src="https://latex.codecogs.com/png.latex?n_x%20%5Ctimes%20n_y"> arrays.</p>
<div class="cell">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># synthetic example</span></span>
<span id="cb4-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># x &lt;- c(0, 1, 3, 6, 10)   # non-uniform</span></span>
<span id="cb4-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># y &lt;- c(0, 2, 3, 7)</span></span>
<span id="cb4-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># show grid lines: horizontal and vertical only, variable spacing</span></span>
<span id="cb4-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># contrast with regular above</span></span></code></pre></div></div>
</details>
</div>
<p>This is the grid type that netCDF calls simply “a grid with coordinate variables” - 1D variables sharing a name with a dimension. A logarithmically-spaced pressure axis is rectilinear. A global ocean product with uniform longitude spacing but Mercator-compressed latitude spacing is rectilinear (and, as the companion post shows, is <em>detected</em> as rectilinear in lon/lat with Mercator y-spacing).</p>
<p><em>In the wild:</em> BRAN ocean reanalysis (uniform lon, Mercator lat), pressure-level atmospheric data, many regional climate model outputs.</p>
<p><strong>Note on contested usage:</strong> Some systems (particularly in HPC/CFD) have begun using <em>rectilinear</em> to cover both regular and curvilinear cases, blurring the distinction. In this post, <em>rectilinear</em> means what it says: rectangle-forming, axis-aligned, independently varying in each dimension. The distinction from regular matters because the storage and the indexing arithmetic differ. (See <em>The affine/homogeneous coordinates aside</em> below for why rectilinear grids quietly drop the geotransform machinery.)</p>
<hr>
</section>
<section id="curvilinear" class="level3">
<h3 class="anchored" data-anchor-id="curvilinear">Curvilinear</h3>
<p>Positions are <em>fully explicit</em>. The grid retains the combinatorial structure of a regular grid - cells are still indexed <img src="https://latex.codecogs.com/png.latex?(i,%20j)">, each cell still has four neighbours - but the physical positions of cells cannot be expressed as the outer product of two 1D sequences. You need:</p>
<ul>
<li>a 2D array of <img src="https://latex.codecogs.com/png.latex?x"> (or longitude) values, shape <img src="https://latex.codecogs.com/png.latex?(n_y,%20n_x)"></li>
<li>a 2D array of <img src="https://latex.codecogs.com/png.latex?y"> (or latitude) values, shape <img src="https://latex.codecogs.com/png.latex?(n_y,%20n_x)"></li>
</ul>
<p>Total storage: <img src="https://latex.codecogs.com/png.latex?2%20%5Ctimes%20n_x%20n_y"> values instead of <img src="https://latex.codecogs.com/png.latex?n_x%20+%20n_y">.</p>
<div class="cell">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># synthetic example: apply a smooth warp to a regular grid</span></span>
<span id="cb5-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># e.g. rotate + shear, or a polar stereographic → lonlat forward projection</span></span>
<span id="cb5-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># show that rows are no longer horizontal, columns no longer vertical</span></span>
<span id="cb5-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># this is the ESMF example: a grid warped to put the pole over land</span></span></code></pre></div></div>
</details>
</div>
<p>The canonical geoscience examples are ocean model grids warped to move the numerical pole away from the geographic North Pole (tripolar grids, ORCA family), and polar stereographic or Lambert conformal grids <em>stored</em> in geographic coordinates. The cells are quadrilaterals. Grid lines curve. But the connectivity is the same as a regular grid - you can still walk from <img src="https://latex.codecogs.com/png.latex?(i,%20j)"> to <img src="https://latex.codecogs.com/png.latex?(i+1,%20j)">.</p>
<p>In CF conventions, curvilinear grids are identified by <em>auxiliary coordinate variables</em>: <code>lat(y, x)</code> and <code>lon(y, x)</code> are 2D variables listed in the <code>coordinates</code> attribute, not 1D dimension coordinates. This is the code-level signal.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb6-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># detecting curvilinear in vapour</span></span>
<span id="cb6-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># </span><span class="al" style="color: #AD0000;
background-color: null;
font-style: inherit;">TODO</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">: show vapour::gdal_raster_data on a curvilinear source</span></span>
<span id="cb6-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># show that coordinate arrays have dim &gt; 1 in both axes</span></span>
<span id="cb6-4"></span>
<span id="cb6-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># library(vapour)</span></span>
<span id="cb6-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># src &lt;- "some_ocean_model.nc"</span></span>
<span id="cb6-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ...</span></span></code></pre></div></div>
</div>
<p><em>In the wild:</em> NEMO/ORCA ocean models, MOM6, ROMS, HYCOM - all use bipolar or tripolar grids. Many Antarctic sea ice model outputs. MODIS/VIIRS L2 swath products (genuinely curvilinear - scan geometry, not a reprojected regular grid).</p>
<hr>
</section>
<section id="unstructured" class="level3">
<h3 class="anchored" data-anchor-id="unstructured">Unstructured</h3>
<p>No implicit connectivity. Each cell’s position and its neighbours must be stated explicitly. The index <img src="https://latex.codecogs.com/png.latex?(i,%20j)"> no longer applies - cells are numbered arbitrarily. This covers:</p>
<ul>
<li>triangulations (FEM ocean models: FVCOM, SCHISM, ADCIRC)</li>
<li>Voronoi/MPAS hexagonal grids</li>
<li>point clouds</li>
</ul>
<div class="cell">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb7-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># synthetic Delaunay triangulation over a region</span></span>
<span id="cb7-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># contrast with the i,j indexing of curvilinear above</span></span></code></pre></div></div>
</details>
</div>
<p><em>In the wild:</em> MPAS-Ocean, FVCOM coastal ocean models, coastline data, terrain TINs.</p>
<hr>
</section>
</section>
<section id="the-spectrum-as-a-table" class="level2">
<h2 class="anchored" data-anchor-id="the-spectrum-as-a-table">The spectrum as a table</h2>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th>Type</th>
<th>Connectivity</th>
<th>Coordinate storage</th>
<th>CF signal</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Regular</td>
<td>implicit <img src="https://latex.codecogs.com/png.latex?(i,j)"></td>
<td>origin + step (4 numbers)</td>
<td>1D coord vars, uniform spacing</td>
</tr>
<tr class="even">
<td>Rectilinear</td>
<td>implicit <img src="https://latex.codecogs.com/png.latex?(i,j)"></td>
<td>two 1D vectors</td>
<td>1D coord vars, variable spacing</td>
</tr>
<tr class="odd">
<td>Curvilinear</td>
<td>implicit <img src="https://latex.codecogs.com/png.latex?(i,j)"></td>
<td>two 2D arrays</td>
<td>2D auxiliary coord vars</td>
</tr>
<tr class="even">
<td>Unstructured</td>
<td>explicit table</td>
<td><img src="https://latex.codecogs.com/png.latex?n_%7Bcells%7D"> position pairs + connectivity</td>
<td>UGRID convention</td>
</tr>
</tbody>
</table>
<p>The progression is strict containment: every regular grid is rectilinear; every rectilinear grid <em>could</em> be stored as curvilinear (wastefully); a curvilinear grid is a structured grid (logically rectangular in the sense of Balaji 2006).</p>
<hr>
</section>
<section id="logically-rectangular-and-the-earth-system-model-vocabulary" class="level2">
<h2 class="anchored" data-anchor-id="logically-rectangular-and-the-earth-system-model-vocabulary">“Logically rectangular” and the Earth system model vocabulary</h2>
<p>The Earth system modelling community (ESMF, GFDL, NEMO) uses a slightly different framing that is worth knowing. Balaji (2006) defines a grid as <em>logically rectangular</em> if its coordinate space maps one-to-one to an integer index space - i.e., cells can be addressed as <img src="https://latex.codecogs.com/png.latex?(i,%20j)">. This covers regular, rectilinear, <em>and</em> curvilinear in our ladder above. Unstructured grids are not logically rectangular.</p>
<p>ESMF then distinguishes <em>uniform</em>, <em>rectilinear</em>, and <em>curvilinear</em> as subtypes of logically rectangular - exactly the ladder above minus unstructured. This is consistent usage.</p>
<p>The key sentence from Balaji: <em>“The coordinate space may continue to be physically curvilinear; yet, in index space, grid cells will be rectilinear boxes.”</em> Index space is always regular. Physical space may be warped. Curvilinear is a statement about physical space, not index space.</p>
<hr>
</section>
<section id="curvilinear-is-not-a-crs" class="level2">
<h2 class="anchored" data-anchor-id="curvilinear-is-not-a-crs">“Curvilinear” is not a CRS</h2>
<p>Here is the specific conflation this post exists to name.</p>
<p>A regular Polar Stereographic grid - say, the NSIDC 25 km Southern Ocean grid on EPSG:3412 - is <strong>regular</strong>. It has uniform spacing in projected coordinates. It can be fully specified by four numbers and a CRS string. We are not saying anything at all about the <em>area</em> or <em>distance</em> on the Earth represented by those cells. This is true in Stereographic (it varies), it’s true in Mercator, in longitude/latitude, in Lambert Azimuthal Equal Area, in Equidistant Conic: it doesn’t matter - the real-world “correctness” of area, distance, or angle is irrelevant to the regularity of the grid in its native CRS.</p>
<p>When a Polar Stereographic grid’s cell centres are forward-projected to geographic coordinates and stored as <code>lat(y, x)</code> / <code>lon(y, x)</code> two-dimensional arrays in a NetCDF file, the <em>storage format</em> looks curvilinear. The CF signal is present: 2D auxiliary coordinate variables. But the underlying geometry is not curvilinear - it is regular, with two arrays of floating-point noise standing in for four exact integers.</p>
<p>This is the entropy problem described in the companion post. The confusion arises because:</p>
<ol type="1">
<li>CF conventions use the shape of coordinate arrays as the proxy for grid type, and a reprojected regular grid has 2D arrays.</li>
<li>Many tools that read CF files label anything with 2D coordinate variables as “curvilinear” without checking whether those arrays have rectilinear or regular structure.</li>
<li>“Geographic coordinates” and “curvilinear coordinates” have been conflated in enough documentation that people assume any lon/lat grid is curvilinear.</li>
</ol>
<p>The correct test is geometric, not syntactic: <em>can the positions be expressed as the outer product of two 1D sequences (rectilinear), or as origin + step (regular)?</em> If yes, the grid is not curvilinear regardless of how its coordinates are stored.</p>
<p>The detection methods in <a href="https://www.hypertidy.org/posts/2026-02-07_cryptic-curvilinear-detection/cryptic-curvilinear">the companion post</a> are the practical implementation of this test.</p>
<hr>
</section>
<section id="genuinely-curvilinear-examples" class="level2">
<h2 class="anchored" data-anchor-id="genuinely-curvilinear-examples">Genuinely curvilinear examples</h2>
<p>To make the distinction concrete: these are cases where the 2D coordinate arrays are <em>not</em> hiding a regular grid.</p>
<p><strong>ORCA bipolar ocean grid.</strong> The NEMO ocean model uses a grid that is regular in the Southern Ocean and tropics but deforms near the North Pole to place the numerical singularity over land (Greenland and Siberia). The resulting <code>nav_lon</code> / <code>nav_lat</code> arrays cannot be factored into 1D sequences by any projection. This is genuinely curvilinear.</p>
<div class="cell">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb8-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># </span><span class="al" style="color: #AD0000;
background-color: null;
font-style: inherit;">TODO</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">: plot nav_lon/nav_lat from an ORCA file, zoom to Arctic</span></span>
<span id="cb8-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># show the non-rectangular cells near the bipolar region</span></span></code></pre></div></div>
</details>
</div>
<p><strong>MODIS/VIIRS L2 swath.</strong> Sensor scan geometry produces curved swaths. Adjacent scan lines are not parallel, and the scan angle distortion means cells grow toward swath edges. No single projected CRS straightens this.</p>
<div class="cell">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb9-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># </span><span class="al" style="color: #AD0000;
background-color: null;
font-style: inherit;">TODO</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">: plot boundary polygon coloured by scan-line index</span></span>
<span id="cb9-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># show the bow-tie artifact at scan edges</span></span></code></pre></div></div>
</details>
</div>
<p><strong>WRF/rotated-pole regional climate model.</strong> The model domain is regular in a rotated coordinate system (pole relocated to the equator of a rotated sphere), which becomes curvilinear in standard geographic coordinates. Detecting this is the Mercator analogue for rotated-pole: project to the rotated CRS, check for regularity there.</p>
<hr>
</section>
<section id="the-degenerate-rectilinear-case" class="level2">
<h2 class="anchored" data-anchor-id="the-degenerate-rectilinear-case">The degenerate rectilinear case</h2>
<p>One last terminological note. NetCDF files frequently store 1D coordinate vectors for regular grids - a <code>lon</code> vector of length <img src="https://latex.codecogs.com/png.latex?n_x"> and a <code>lat</code> vector of length <img src="https://latex.codecogs.com/png.latex?n_y">, both uniformly spaced. Strictly speaking this is <em>rectilinear</em> storage (the format allows variable spacing), but the data is <em>regular</em> (the spacing happens to be uniform).</p>
<p>This is the <strong>degenerate rectilinear</strong> case: a rectilinear format carrying regular content. It matters because:</p>
<ul>
<li>Tools that test only for uniform spacing on 1D arrays correctly identify the grid as regular.</li>
<li>Tools that check only “does it have 1D coordinate variables?” treat it as rectilinear (variable spacing allowed) and may not exploit the regularity.</li>
<li>The four-number specification is recoverable from the 1D vectors trivially.</li>
</ul>
<p>For the netCDF case it is mostly a storage efficiency question. The interesting case is when the 1D vectors are <em>non-uniformly spaced</em> - genuinely rectilinear - which occurs in pressure-level data, stretched ocean grids (e.g.&nbsp;MOM with enhanced tropical resolution), and Mercator-latitude ocean products.</p>
<hr>
</section>
<section id="references-and-further-reading" class="level2">
<h2 class="anchored" data-anchor-id="references-and-further-reading">References and further reading</h2>
<ul>
<li><p>Balaji, V. (2006). <em>A standard description of grids used in earth system models.</em> GFDL/NOAA. The foundational taxonomy for the Earth system modelling community; introduces the logically rectangular / mosaic framework. <a href="https://extranet.gfdl.noaa.gov/~vb/gridstd/">GFDL grid standards</a></p></li>
<li><p>ESMF Reference Manual §12.1 - the most consistently used set of definitions in practice for geoscience regridding tools. Distinguishes uniform / rectilinear / curvilinear as subtypes of logically rectangular. <a href="https://earthsystemmodeling.org">earthsystemmodeling.org</a></p></li>
<li><p>CF Conventions §5 - the syntactic signal: 1D dimension coordinates vs 2D auxiliary coordinates. Does not define grid type geometrically but the encoding conventions follow the taxonomy here. <a href="https://cfconventions.org/Data/cf-conventions/cf-conventions-1.11/cf-conventions.html#coordinate-types">cfconventions.org</a></p></li>
<li><p>Weinkauf, T. (KTH, 2015). <em>Grids and Interpolation</em> - the clearest single slide deck on the implicit/semi-implicit/explicit storage framing. Not geoscience-specific; comes from scientific visualisation.</p></li>
<li><p>Wikipedia: <a href="https://en.wikipedia.org/wiki/Regular_grid">Regular grid</a> - covers the structured grid sub-hierarchy with reasonable precision.</p></li>
<li><p><em>Detecting hidden grids in curvilinear coordinates</em> (this site, 2026-02-07) - the practical companion: how to test whether a 2D lon/lat array is actually a regular projected grid with lost metadata.</p></li>
<li><p><em>Coordinates broken in NetCDF</em> (this site, 2025-09-04) - an earlier treatment of the related problem of CF files with missing or misleading coordinate metadata.</p></li>
</ul>
<hr>
</section>
<section id="the-ladder-is-manifold-agnostic-and-that-is-the-point" class="level2">
<h2 class="anchored" data-anchor-id="the-ladder-is-manifold-agnostic-and-that-is-the-point">The ladder is manifold-agnostic — and that is the point</h2>
<p>The regular → rectilinear → curvilinear ladder characterises grids by their index structure and coordinate storage alone. The manifold — sphere, plane, torus, ellipsoid — is not part of the definition. You can have a regular grid on a sphere (HEALPix patches, cubed-sphere faces) or a regular grid on a plane; regularity is a property of the indexing, not the embedding. This is not a limitation of the ladder; it is the source of its generality and simplicity.</p>
<p>The longlat case is a clean illustration of what happens when you <em>ignore</em> the manifold. A global regular longlat grid is perfectly regular by the ladder’s definition: uniform spacing, Cartesian product index, four numbers and a CRS to specify it completely. And that’s fine — until your application needs to know that the top row is a pole and the left and right edges are the same meridian. At that point the ladder gives you no help. The wrapping logic, the polar singularity, the fact that cells covering Fiji and cells covering its antipode are not indexed as neighbours — none of that is encoded in the grid structure. It is external knowledge that the application must supply.</p>
<p>The ladder doesn’t fail at that boundary. It never claimed to address it.</p>
<hr>
</section>
<section id="what-wrapping-logic-reveals" class="level2">
<h2 class="anchored" data-anchor-id="what-wrapping-logic-reveals">What “wrapping logic” reveals</h2>
<p>A regular longlat grid at 1° resolution has 360 × 180 = 64,800 cells. The top row contains 360 cells, all of which map to the South Pole — a single point. One cell (or at most three, for a T-shaped pole cap) would do the same geometric work. The other 357 are redundant, and the grid structure has no way to say so. Similarly, cell <img src="https://latex.codecogs.com/png.latex?(0,%20j)"> and cell <img src="https://latex.codecogs.com/png.latex?(359,%20j)"> are geographic neighbours — they share an edge at the antimeridian — but in index space they are as far apart as two cells can be. The Cartesian product index is silent about this.</p>
<p>This is not a pathology. It is simply what you get when you discretise a sphere with a tool designed for a flat rectangle. The grid is doing its job correctly by the ladder’s definition. The mismatch is between the ladder’s scope and the application’s needs.</p>
<p>When the manifold <em>does</em> matter to an application — global topology, correct adjacency, equal-area cells, no polar redundancy — you are outside the ladder’s scope. That is precisely the problem that DGGS are designed to solve.</p>
<hr>
</section>
<section id="dggs-discretising-the-manifold-directly" class="level2">
<h2 class="anchored" data-anchor-id="dggs-discretising-the-manifold-directly">DGGS: discretising the manifold directly</h2>
<p>Discrete global grid systems (H3, S2, HEALPix, ISEA) approach the problem from the other direction. The manifold comes first; the discretisation is designed for the sphere, so adjacency, area, and topology are correct by construction. H3 hexagons near Fiji are indexed as neighbours of other hexagons near Fiji. There is no polar row of redundant cells because the addressing scheme is built around the sphere’s actual geometry.</p>
<p>The price is the Cartesian product index structure. DGGS cells don’t factor into rows and columns in any natural global sense (HEALPix has local patches with regular structure; H3 has a hierarchical addressing scheme with local regularity; but neither is globally Cartesian-product-indexable). This is why DGGS feel like unstructured grids to tools that expect <img src="https://latex.codecogs.com/png.latex?(i,%20j)"> — and in the ladder’s terms, they are. The structured/unstructured distinction still applies internally to a DGGS, but the global topology is not rectangular.</p>
<p>So DGGS sit outside the ladder not because they are more complex than unstructured grids, but because the ladder’s premise — index structure independent of the manifold — is exactly what DGGS reject. They are a different answer to a different question.</p>
<p>The ladder is the right tool when the manifold can be treated as external context. DGGS are the right tool when the manifold must be built into the addressing scheme. These are complementary, not competing.</p>
<hr>
</section>
<section id="workarounds-that-live-on-the-ladder" class="level2">
<h2 class="anchored" data-anchor-id="workarounds-that-live-on-the-ladder">Workarounds that live on the ladder</h2>
<p>Most real-world geospatial work stays on the ladder but handles the manifold through external convention rather than anything the grid structure provides. This is worth naming:</p>
<ul>
<li><strong>Tripolar ocean grids</strong> (NEMO ORCA, MOM6 tripolar): curvilinear grids that move the numerical pole singularity over land. The grid is still logically rectangular — still on the ladder — but the topology (two northern poles, one southern pole) is encoded in the coordinate arrays and the model’s halo-exchange logic, not in the index structure.</li>
<li><strong>NSIDC polar grids</strong>: regular projected grids (EPSG:3412, EPSG:3413) that implicitly wrap in the azimuthal sense. The grid structure doesn’t know this; applications that care about the antimeridian handle it separately.</li>
<li><strong><code>vaster</code> and <code>vapour</code></strong>: deliberately silent about topology. They operate on the ladder — grid arithmetic, coordinate lookup, raster I/O — and leave manifold questions to the caller. This is the right scope decision; encoding sphere topology into a raster grid library would be the wrong level of abstraction.</li>
</ul>
<p>The “subtle relationships and workflows and workarounds” in curvilinear ocean models are mostly the accumulated cost of living on the ladder with a spherical manifold. The ladder is the floor, not the ceiling.</p>
<hr>
</section>
<section id="the-seawifs-l3bin-the-connector" class="level2">
<h2 class="anchored" data-anchor-id="the-seawifs-l3bin-the-connector">The SeaWiFS L3bin: the connector</h2>
<p>The sinusoidal L3 grid and the L3bin addressing scheme together make the ladder boundary more concrete than any abstract example.</p>
<p>The <strong>sinusoidal regular grid</strong> is the rectangular-index version of global equal-area ocean colour compositing. It is a regular grid in sinusoidal projection: uniform spacing in projected coordinates, four numbers and a CRS, perfectly on the ladder. But sinusoidal projection preserves area by compressing x-extent toward the poles: high-latitude rows are physically narrow. When you impose a uniform <img src="https://latex.codecogs.com/png.latex?n_x"> column count across all rows, most high-latitude cells fall outside the Earth’s disk and are fill. The grid is valid; it is just mostly empty at the poles, and the emptiness is structural, baked in by the choice to force a sphere into a rectangle.</p>
<p>The <strong>SeaWiFS/MODIS/VIIRS L3bin</strong> resolves this directly. Each latitude row contains exactly as many bins as needed to tile that latitude band — no more, no fewer. Row widths vary. The result is a ragged array: not a matrix, not a Cartesian product, not on the ladder. By the ladder’s definition it is unstructured — each bin has an explicit address (the bin number) rather than a <img src="https://latex.codecogs.com/png.latex?(i,%20j)"> index pair.</p>
<p>But it is the <em>simplest possible</em> unstructured case. The bins are still organised by latitude band. The addressing scheme (bin number → row → column-within-row) is well-defined and compact. The geometry is explicit but not complicated. It is unstructured by necessity, not by complexity — the minimum departure from the ladder required to take the sphere seriously.</p>
<p>The contrast is the hook:</p>
<ul>
<li>The sinusoidal regular grid: <em>technically</em> on the ladder, equal-area, but with a wasteful rectangular fiction imposed on a spherical reality. The manifold is ignored; fill cells paper over the mismatch.</li>
<li>The L3bin: off the ladder, but honest. The ragged structure <em>is</em> the sphere’s geometry expressed directly in the data structure. You traded the Cartesian product index for an accurate account of what you actually have.</li>
</ul>
<p>This is the L3bin’s value as a connector. It is not an exotic case — it is used in virtually every ocean colour dataset from SeaWiFS through PACE. And it sits exactly at the boundary the ladder defines, making the boundary legible.</p>
<p>A post that works through the L3bin addressing scheme in R — reading bin numbers, recovering lat/lon, comparing to the sinusoidal regular grid counterpart — would make the abstract ladder concrete in a way that the regular/rectilinear/curvilinear examples cannot. That is post #2 (or #3, after the curvilinear detection post).</p>
<hr>
</section>
<section id="the-affinehomogeneous-coordinates-aside" class="level2">
<h2 class="anchored" data-anchor-id="the-affinehomogeneous-coordinates-aside">The affine/homogeneous coordinates aside</h2>
<p><em>(Parked from a detour in the main post draft — too interesting to discard, too long to include.)</em></p>
<p>The GDAL geotransform is a 2×3 affine matrix (equivalently, a 3×3 homogeneous matrix with the bottom row fixed at [0, 0, 1]): the full projective/affine machinery for mapping pixel index → Cartesian position in a flat 2D plane. This is a genuinely 2D spatial problem, so the homogeneous coordinates are natural.</p>
<p>The rectilinear case exposes how much of that is a 1.5D trick. A rectilinear grid is two independent 1D problems — “where is column <img src="https://latex.codecogs.com/png.latex?i"> on the x-axis” and “where is row <img src="https://latex.codecogs.com/png.latex?j"> on the y-axis” — combined. There is no cross-term, no rotation, no shear. The “transform” collapses to two separate monotone sequences. Nobody would think to write a 3×3 homogeneous matrix for a 1D lookup table, because a lookup table is obviously just a lookup table.</p>
<p>The geotransform works for the regular case because regular grids are the degenerate rectilinear case — the lookup tables happen to be arithmetic sequences, so they compress back to origin + step, which is what the six geotransform numbers encode. The homogeneous machinery is present, but with most of its degrees of freedom set to zero.</p>
<p>The shadow quality: the geotransform is the full 2D affine apparatus applied to what is, in the regular case, a product of two 1D arithmetic sequences. When you move to rectilinear coordinates — genuinely variable spacing, two lookup tables — no one reaches for a matrix from a higher-dimensional space. The 1D lookup table is self-evidently sufficient. The geotransform convention makes the regular case look more 2D than it is. (Rotated or sheared regular grids are the exception that proves the rule — see the footnote on the Regular section above.)</p>
<hr>
<p><em>The gproj package, which implements regularity detection, is at <a href="https://github.com/mdsumner/gproj">github.com/mdsumner/gproj</a>. The vaster package provides the regular grid arithmetic used throughout the hypertidy ecosystem.</em></p>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>A regular grid need not be axis-aligned: an affine transform — with rotation, shear, and independent dx/dy — still specifies every cell position implicitly from its index. GDAL’s geotransform is exactly this: six numbers covering origin, scale, and the two off-diagonal (rotation/shear) components. The index structure, the storage economy, and the “regular” classification are unchanged. Rotated or sheared regular grids are uncommon in practice but not exotic; some satellite products use them. We ignore the off-diagonal case in the main text because it adds notation without changing the argument.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>gdal</category>
  <category>raster</category>
  <category>netcdf</category>
  <category>projections</category>
  <category>theory</category>
  <guid>https://hypertidy.org/posts/2026-05-28_regular-rectilinear-curvilinear/</guid>
  <pubDate>Thu, 28 May 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>R, GDAL, Zarr, and the Missing Recipe Format for Raster Datasets</title>
  <dc:creator>Michael Sumner</dc:creator>
  <link>https://hypertidy.org/posts/2026-05-27_raster-stack-zarr-virtualization/</link>
  <description><![CDATA[ 






<p><em>The virtual-Zarr format that lets xarray describe 16,000 files in a tiny store doesn’t exist in R yet - but the pieces are in place, this post tries to show where they fit together</em> .</p>
<p>A common scenario in geospatial data workflows is multiple raster files. Let’s consider 23 snapshots of OISST sea surface temperature spread across 2025. One NetCDF file per date, each raster is the same grid, four variables each. Let’s see what R does with that. Here we assume <code>files</code> is a vector of paths to the NetCDF files.</p>
<p>(Fully reproducible setup, downloading directly from NCEI is in the appendix.)</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">str</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">basename</span>(files))</span>
<span id="cb1-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># chr [1:23] "oisst-avhrr-v02r01.20250101.nc" "oisst-avhrr-v02r01.20250117.nc" "oisst-avhrr-v02r01.20250202.nc" ...</span></span>
<span id="cb1-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(terra)</span>
<span id="cb1-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rast</span>(files)</span>
<span id="cb1-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># class       : SpatRaster</span></span>
<span id="cb1-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># size        : 720, 1440, 92  (nrow, ncol, nlyr)</span></span>
<span id="cb1-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># resolution  : 0.25, 0.25  (x, y)</span></span>
<span id="cb1-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># extent      : 0, 360, -90, 90</span></span>
<span id="cb1-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># coord. ref. : lon/lat WGS 84 (CRS84)</span></span>
<span id="cb1-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># sources     : oisst-avhrr-v02r01.20250101.nc (4 layers)</span></span>
<span id="cb1-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#               oisst-avhrr-v02r01.20250117.nc (4 layers)</span></span>
<span id="cb1-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#               ... and 21 more sources</span></span>
<span id="cb1-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># varnames    : anom, err, ice, sst, anom, err, ...</span></span>
<span id="cb1-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># names       : anom_zlev=0, err_zlev=0, ice_zlev=0, sst_zlev=0, ...</span></span>
<span id="cb1-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># time (days) : 2025-01-01 to 2025-12-19 (23 steps)</span></span></code></pre></div></div>
<p>The result is pretty good. <code>terra::rast()</code> is no slouch: it reads all subdatasets across all files, concatenates band-wise, preserves the time axis, carries units, and records depth metadata. For a lot of workflows this is exactly what is needed, and it’s lazy — no pixel values are read until they are needed.</p>
<p>But look at the names: <code>anom_zlev=0</code>, <code>err_zlev=0</code>, <code>ice_zlev=0</code>, <code>sst_zlev=0</code> — repeated 23 times, 92 layers total. Four variables and 23 timesteps are there, flattened into band order. The n-D structure — <em>this is a (variable × time × lat × lon) array</em> — is implicit in the interleaving, not encoded anywhere. There’s an implicit contract about how this was unrolled, there are conventions for band ordering, workarounds for the <code>_zlev=0</code> naming. Nothing is wrong, but the band-stack model forces all of that n-D structure into a single axis, and recovering it requires a lot of information that is not usual in the multi-band model.</p>
<p>We can sidestep the variable interleaving with the <code>vrt://</code> URI syntax:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vrt</span>(</span>
<span id="cb2-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sprintf</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"vrt://%s?sd_name=sst"</span>, files),</span>
<span id="cb2-3">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/tmp/sst_stack.vrt"</span>,</span>
<span id="cb2-4">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">options =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-separate"</span>,</span>
<span id="cb2-5">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">overwrite =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span></span>
<span id="cb2-6">)</span>
<span id="cb2-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># class       : SpatRaster</span></span>
<span id="cb2-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># size        : 720, 1440, 23  (nrow, ncol, nlyr)</span></span>
<span id="cb2-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># source      : sst_stack.vrt</span></span>
<span id="cb2-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># names       : r_1, r_2, r_3, ..., r_23</span></span></code></pre></div></div>
<p>One variable, 23 layers. The <code>vrt://</code> GDAL protocol is worth knowing: it lets us name a subdataset in a URI query string, vectorizing cleanly with <code>sprintf</code> and using the underlying raster-stack capability of GDAL VRT (<code>-separate</code>). But there’s a trade-off: no time axis, layer names are <code>r_1</code> through <code>r_23</code> rather than dates. We fixed the variable interleaving problem and lost the coordinate metadata.</p>
<p><code>rast(files, "sst")</code> also selects a single subdataset without the <code>vrt://</code> syntax, and produces the same 23-layer result — but it goes through the <code>ncdf4</code> backend rather than GDAL, so it doesn’t produce a VRT file and doesn’t carry the same interoperability guarantees. The two approaches look similar but are doing different things underneath, and that distinction matters for what comes next.</p>
<p><code>stars</code> approaches this differently. In default <em>proxy</em> (lazy) mode it uses GDAL classic raster and keeps variables as separate attributes, file-per-timestep structure explicit, doing this by harvesting metadata from the underlying files as carried by GDAL. <code>stars::read_mdim()</code> goes further — it uses the GDAL multidimensional API and gives named attributes, real dimension coordinates with udunits, proper n-D structure. The representation is right, but it too has limits; details are in the appendix.</p>
<p>None of these approaches is <em>the best</em> — each embeds real expertise and genuine design choices. But with excellent tools like terra and stars, <strong>you still can’t hand someone a file that says “this is a year of global SST.”</strong> You can hand them code, or you can hand them gigabytes. The code only works if they have the same files in the same place. The gigabytes require a format decision and a copy operation.</p>
<p>There’s no serializable recipe that is light, language-agnostic, and handles a broad variety of cases.</p>
<section id="fifteen-minutes-with-xarray" class="level2">
<h2 class="anchored" data-anchor-id="fifteen-minutes-with-xarray">Fifteen minutes with xarray</h2>
<p>A key moment for me was while learning Python and xarray, I was impressed that xarray could open these files — a huge list of them (16,337 days of OISST at time of writing) as local files or remote URLs. xarray checks them all for consistency, detects that what is really there is four variables in longitude, latitude, and time, and that time is an implicit concatenation from a single time step in each of those thousands of files. It’s remarkable: one object in memory in Python, ready to lazy-read any part of the four 16337×720×1440 arrays. But that took about 15 minutes to run, let alone the software and computing infrastructure setup I had been through. And then: how do you share that progress with colleagues?</p>
<p>Everybody knows you aren’t supposed to serialize in-memory objects to <em>binary</em>, we all have scary experiences with <code>.Rdata</code> and <code>.rds</code>, and Python has the same with its <em>pickle</em>. (I thought: <em>“hmmm, what’s the xarray equivalent of a VRT?”</em> Spoiler: there isn’t one — and although VirtualiZarr does serve that role, it’s missing an intermediate state where we only record what files were opened and what structure was learned.)</p>
<hr>
</section>
<section id="what-the-vrt-already-does" class="level2">
<h2 class="anchored" data-anchor-id="what-the-vrt-already-does">What the VRT already does</h2>
<p>Back to our 23 files — a sample of the 16,337+ in the full OISST set as of writing.</p>
<p>Open <code>/tmp/sst_stack.vrt</code> in a text editor (<code>browseURL("/tmp/sst_stack.vrt")</code> works too). It’s a few kilobytes of XML that fully describes a virtual 23-layer SST dataset — source files, spatial extent, band mapping. It’s a tiny file: you could email it, put it in a repository, or patch the source paths to <code>/vsicurl/https://....nc</code> URLs so it works without any local files at all. Anyone with GDAL can open it with <code>rast("sst_stack.vrt")</code> and get exactly what you built, without copying a byte of the underlying data.</p>
<p>That’s a <em>recipe</em>: we’ve described our dataset as a small chunk of text that GDAL can open very quickly.</p>
<p><strong>VRT</strong> <a href="https://gdal.org/en/stable/drivers/raster/vrt.html">(Virtual Format)</a> is a plain XML file that describes a virtual raster dataset. It can mosaic, warp, reproject, reband, and combine source files without touching the underlying data. For the 2D raster world it is exactly the recipe format that’s missing from the terra/stars in-memory story. The 2D VRT is one of GDAL’s most underappreciated features — already powering a lot of geospatial infrastructure, especially where band-stacking or tile-mosaicking is sufficient as the solution.</p>
<p>So what about the full n-D case: multiple variables, time coordinates, arbitrary dimensions?</p>
<hr>
</section>
<section id="what-zarr-figured-out" class="level2">
<h2 class="anchored" data-anchor-id="what-zarr-figured-out">What Zarr figured out</h2>
<p>The Python/xarray community has been working on exactly this problem, approaching it from the chunk level rather than the file level.</p>
<p>Zarr is a chunked array format. An array is split into regular chunks; each chunk is compressed independently and stored as a separate object — a file, an S3 object, a range of bytes in a larger store. The chunks themselves are opaque: raw numeric arrays in compressed bytes, not self-describing. What makes Zarr work is the metadata sidecar: a small JSON file that records the shape, dtype, chunk layout, compression codec, and dimension coordinates — everything xarray needs to provide a rich, self-describing interface over the data. This is really good for cloud access: fetch only the chunks you need, in parallel, without a format-specific library. It’s a bit like taking a pile of NetCDF files and turning them inside out — all the metadata (tiny in comparison to the data) is placed upfront in JSON, and all the compressed opaque chunks are stored as individually addressable objects.</p>
<p>The deeper insight comes when you realize that <strong>HDF5, netCDF4, GeoTIFF, GRIB, and others already store their data in chunks</strong>. The chunks are buried inside files that require format-specific libraries to navigate, but the byte ranges are all there. We just need an index.</p>
<p><a href="https://github.com/fsspec/kerchunk">kerchunk</a> and its successor <a href="https://virtualizarr.readthedocs.io/">VirtualiZarr</a> do exactly that: open your existing files, find where each chunk lives (file path, byte offset, byte length), and write that index as a JSON or Parquet reference store. The bulk data never moves. What you get is a Zarr-shaped recipe that points back into your original files. It’s worth noting that this idea is not new — NASA and Unidata’s <a href="https://opendap.github.io/DMRpp-wiki/DMRpp.html">DMR++</a> format did the same thing for OPeNDAP, indexing chunk byte-ranges in HDF5/netCDF files as a sidecar that an OPeNDAP server can serve directly. <a href="https://cogeo.org">Cloud-Optimized GeoTIFF</a> is the same idea applied to TIFF: the IFD tile index is moved to the front of the file so a client can read the chunk map in one range request and fetch only what it needs thereafter. The difference is that Zarr’s client-side framing made the pattern composable and ecosystem-wide rather than tied to a specific server or format.</p>
<p>NOTE: this is very much pseudo code showing the overall style - see the <a href="https://virtualizarr.readthedocs.io/en/latest/usage.html">Usage VirtualiZarr pages</a> for more examples, and we have a real example in the appendix.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> virtualizarr <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> open_virtual_dataset</span>
<span id="cb3-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> xarray <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> xr</span>
<span id="cb3-3"></span>
<span id="cb3-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># index the chunks — no data copied</span></span>
<span id="cb3-5">combined <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> xr.concat(</span>
<span id="cb3-6">    [open_virtual_dataset(f) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> f <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> files],</span>
<span id="cb3-7">    dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"time"</span></span>
<span id="cb3-8">)</span>
<span id="cb3-9">combined.virtualize.to_kerchunk(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sst_virtual.zarr"</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">format</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"zarr"</span>)</span>
<span id="cb3-10"></span>
<span id="cb3-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># open it — chunk-aware, lazy, S3-ready, fully structured</span></span>
<span id="cb3-12">ds <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> xr.open_dataset(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sst_virtual.zarr/"</span>, engine<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"kerchunk"</span>)</span>
<span id="cb3-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># &lt;xarray.Dataset&gt;</span></span>
<span id="cb3-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Dimensions:  (time: 23, zlev: 1, lat: 720, lon: 1440)</span></span>
<span id="cb3-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Coordinates:</span></span>
<span id="cb3-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   * time     (time) datetime64[ns] 2025-01-01 ... 2025-12-19</span></span>
<span id="cb3-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   * lat      (lat) float32 -90.0 ... 89.75</span></span>
<span id="cb3-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   * lon      (lon) float32 0.0 ... 359.75</span></span>
<span id="cb3-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Data variables: anom, err, ice, sst</span></span></code></pre></div></div>
<p>The store is kilobytes of metadata, the actual data stays where it is, and the n-D structure is fully encoded. Compare this to the terra output above:</p>
<ul>
<li><code>sst</code>, <code>anom</code>, <code>err</code>, <code>ice</code> as <strong>named variables</strong>, not interleaved bands</li>
<li><code>time</code> as a real coordinate with date values, not layer indices <code>r_1..r_23</code></li>
<li><code>lat</code>, <code>lon</code>, <code>zlev</code> as labelled dimensions with coordinate values</li>
<li>the whole thing serialized as a directory anyone with Zarr support can open</li>
</ul>
<p>This is what <code>stars::read_mdim()</code> is reaching toward in R — the representation is structurally similar. The difference is that the xarray object came from a small Zarr store, not from reconstructing structure at runtime over 23 original netCDF files. That difference is the recipe.</p>
<hr>
</section>
<section id="gdal-is-the-bridge" class="level2">
<h2 class="anchored" data-anchor-id="gdal-is-the-bridge">GDAL is the bridge</h2>
<p>GDAL already spans both worlds.</p>
<p>GDAL has a <a href="https://gdal.org/en/stable/user/multidim_raster_data_model.html"><strong>multidimensional API</strong></a> (the “mdim” layer) that represents data as named arrays with labelled dimensions — the same model as xarray, not the band-stacked model of classic GDAL rasters. GDAL’s Zarr driver can open VirtualiZarr/kerchunk reference stores through this API. A store created in Python is readable via GDAL — which means it’s reachable from R today.</p>
<p>More importantly, GDAL has <a href="https://gdal.org/en/stable/programs/gdal_mdim_mosaic.html"><code>gdal mdim mosaic</code></a> — a tool that takes a collection of raster files and produces a <strong>multidim VRT</strong> describing them as a unified n-D array dataset. This VRT encodes the array structure, dimension names, coordinate values, and source file mapping. It is the GDAL-native recipe for an n-D dataset — and as shown in the appendix, <code>stars::read_mdim()</code> opens it faithfully, giving you the full multi-file dataset with the right structure, right coordinates, and a serializable description that travels independently of the original files.</p>
<p>Converting a GDAL multidim VRT to a Zarr reference store is a small transformation on kilobytes of XML, not a data operation. That’s the near-term path to R-native VirtualiZarr creation.</p>
<hr>
</section>
<section id="the-gap-creation-not-reading" class="level2">
<h2 class="anchored" data-anchor-id="the-gap-creation-not-reading">The gap: creation, not reading</h2>
<p>R can <em>read</em> virtual stores — Zarr and multidim VRT and VirtualiZarr Parquet reference via <code>stars::read_mdim()</code>, <code>gdalraster::mdim_translate</code> or <a href="https://github.com/hypertidy/zaro"><code>zaro</code></a> or real Zarr with <a href="https://CRAN.R-project.org/package=pizzarr">pizzarr</a> or <a href="https://CRAN.R-project.org/package=zarr">zarr</a>. What R can’t yet do is <em>create</em> the chunk-level byte-reference stores that VirtualiZarr generates for HDF5 and netCDF.</p>
<p>That’s a frontier, and I’ve been exloring where the pieces could converge:</p>
<p><strong><code>gdal mdim mosaic</code></strong> creates multidim VRTs from file collections today. The XML fully describes what a VirtualiZarr store needs — shape, chunks, coordinate values, source file paths and byte offsets. This is the hard structural work; emitting the Zarr reference store format from it is a straightforward conversion.</p>
<p><strong><code>rhdf5</code></strong> can now open remote HDF5 files at URLs or on S3 (dev version on GitHub). The next step is extracting chunk byte-reference indices directly - a core operation that VirtualiZarr performs in Python (with HDF5 library, GRIB libraries, TIFF libraries some in C++ and some in Rust). Step 7 in the appendix shows remote access, and a pending PR in my rhdf5 fork has already use that to harvest remote byte references from HDF5 and NetCDF.</p>
<p><strong><code>zaro</code></strong> (pure-R Zarr reader via Arrow) already reads VirtualiZarr Parquet stores. The reading side is there, and needs comparison with <code>pizzarr</code>, and <code>zarr</code> and other zarr-enabled packages.</p>
<p><strong><code>vrtstack</code></strong> generates multidim VRTs from 2D raster collections in R - the <code>gdal mdim mosaic</code> equivalent <em>for simple cases of multiple 2D raster</em> - without leaving R.</p>
<p>The path: <code>vrtstack</code> or <code>gdal mdim mosaic</code> for file collections → emit Zarr reference store → readable by <code>zaro</code>, xarray, or any Zarr-aware tool. In this way R can become a first-class creator of portable dataset descriptions, not just a consumer of what Python produces.</p>
<hr>
</section>
<section id="why-this-matters-beyond-r" class="level2">
<h2 class="anchored" data-anchor-id="why-this-matters-beyond-r">Why this matters beyond R</h2>
<p>A Zarr reference store created from OISST netCDF files is immediately openable in Python, R, GDAL and in theory any tool that speaks Zarr.</p>
<p>STAC is complementary but different: STAC describes <em>where</em> files are and their spatiotemporal footprint. A virtual store describes <em>what the dataset structurally is</em> — how chunks map to array coordinates, what the dimensions mean, how to reassemble a slice. A STAC item can point to a virtual store the way it points to a COG.</p>
<p>The R community has genuine leverage here. GDAL is the common substrate between R and the xarray/Zarr ecosystem. R is not just catching up, there are things we can do that aren’t on any other roadmap.</p>
<hr>
</section>
<section id="what-would-help" class="level2">
<h2 class="anchored" data-anchor-id="what-would-help">What would help</h2>
<p>If you work with time series of raster files and you’ve felt this friction:</p>
<ul>
<li><strong>Talk about your use case</strong> — what files, what structure, what would a portable dataset description let you do that you can’t do today?</li>
<li><strong>Try <code>terra::vrt()</code> and try the <code>vrt://</code> URI syntax</strong> for netCDF subdataset selection.</li>
<li><strong>Try <code>stars::read_mdim()</code> on a <code>gdal mdim mosaic</code> VRT</strong> — the full tour in the appendix shows how these fit together</li>
<li><strong>Try <code>remotes::install_github("hypertidy/tidync@multi-input")</code></strong> if you have remote netCDF files and want to skip the download entirely — Step 8 in the appendix shows what this already looks like.</li>
<li><strong>Look at <code>vrtstack</code> and <code>zaro</code></strong> on the <a href="https://github.com/hypertidy">hypertidy GitHub</a>, vrtstack builds a multidim VRT from 2D rasters (multidim GDAL supports COG, but doesn’t support <em>builiding these</em>), zaro is a read-implementation for Zarr that is primarily based around Arrow.</li>
<li><strong>File issues against GDAL’s multidim API</strong> when you find gaps — R users exercising it in real workflows makes it better for everyone.</li>
<li><strong>Check out gdalraster (and my multidim branch)</strong> there’s a long way to go for multidim but <a href="https://firelab.github.io/gdalraster/">gdalraster</a> is a rich package has a super strong basis for faithfully carrying the core facilities of the multidim api <a href="https://github.com/mdsumner/gdalraster/tree/gdalmultidim-api">exposed to R</a>.</li>
</ul>
<hr>
</section>
<section id="appendix" class="level2">
<h2 class="anchored" data-anchor-id="appendix">Appendix: a progressive tour on 23 OISST files</h2>
<section id="appendix-setup" class="level3">
<h3 class="anchored" data-anchor-id="appendix-setup">Setup: download 23 OISST files from NCEI</h3>
<p>All code below uses only this setup, and we use a multidim VRT representation later to act as an intermediate form.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(glue)</span>
<span id="cb4-2"></span>
<span id="cb4-3">date  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">seq</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.Date</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2025-01-01"</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">by =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"16 days"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">length.out =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">23</span>)</span>
<span id="cb4-4">base0 <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"https://www.ncei.noaa.gov/data/sea-surface-temperature-optimum-interpolation/v2.1/access/avhrr"</span></span>
<span id="cb4-5">ym    <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">format</span>(date, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"%Y%m"</span>)</span>
<span id="cb4-6">ymd   <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">format</span>(date, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"%Y%m%d"</span>)</span>
<span id="cb4-7">urls  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">glue</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"{base0}/{ym}/oisst-avhrr-v02r01.{ymd}.nc"</span>)</span>
<span id="cb4-8"></span>
<span id="cb4-9">dir   <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tempdir</span>()</span>
<span id="cb4-10">files <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">file.path</span>(dir, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">basename</span>(urls))</span>
<span id="cb4-11"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">download.file</span>(urls, files, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">method =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"libcurl"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">mode =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"wb"</span>)</span></code></pre></div></div>
<section id="setup-part-2-create-a-gdal-multidimensional-vrt" class="level4">
<h4 class="anchored" data-anchor-id="setup-part-2-create-a-gdal-multidimensional-vrt">Setup part 2: create a GDAL multidimensional VRT</h4>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">writeLines</span>(glue<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">glue</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsicurl/{urls}"</span>), tf <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tempfile</span>())</span>
<span id="cb5-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">system</span>(glue<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">glue</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gdal mdim mosaic @{tf} oisst_2025_sample.vrt"</span>))</span>
<span id="cb5-3"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## piggyback::pb_upload("oisst_2025_sample.vrt")</span></span></code></pre></div></div>
<p>The URL structure itself is the catalogue: a simple path template over date components, no database required. 23 files, ~3.5MB each, ~80MB total.</p>
</section>
</section>
<section id="step-1-terra-rast-band-stacked-all-variables-time-preserved" class="level3">
<h3 class="anchored" data-anchor-id="step-1-terra-rast-band-stacked-all-variables-time-preserved">Step 1 — terra: <code>rast()</code>, band-stacked, all variables, time preserved</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb6-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rast</span>(files)</span>
<span id="cb6-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># class       : SpatRaster</span></span>
<span id="cb6-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># size        : 720, 1440, 92  (nrow, ncol, nlyr)</span></span>
<span id="cb6-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># sources     : oisst-avhrr-v02r01.20250101.nc (4 layers)</span></span>
<span id="cb6-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#               ... and 22 more sources</span></span>
<span id="cb6-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># varnames    : anom, err, ice, sst, anom, err, ice, sst, ...</span></span>
<span id="cb6-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># names       : anom_zlev=0, err_zlev=0, ice_zlev=0, sst_zlev=0, ...</span></span>
<span id="cb6-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># unit        :      Celsius,    Celsius,          %,    Celsius, ...</span></span>
<span id="cb6-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># time (days) : 2025-01-01 to 2025-12-19 (23 steps)</span></span></code></pre></div></div>
<p>terra uses the <code>ncdf4</code> R package for netCDF reads — a design inherited from the <code>raster</code> package - rather than routing through GDAL. This is why its behaviour differs from stars proxy mode. The time axis survives via ncdf4’s own metadata handling. The n-D structure is flattened into 92 bands; recovering it means knowing the 4-variable interleaving convention.</p>
<p>Bundling all bands from all variables that share the same dimensions is a choice made by terra, GDAL has a similar capacity but it is not used here (and is in fact unusable by these files for other reasons).</p>
</section>
<section id="step-2-terra-vrt-with-vrt-single-variable-time-lost" class="level3">
<h3 class="anchored" data-anchor-id="step-2-terra-vrt-with-vrt-single-variable-time-lost">Step 2 — terra: <code>vrt()</code> with <code>vrt://</code>, single variable, time lost</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb7-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vrt</span>(</span>
<span id="cb7-2">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sprintf</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"vrt://%s?sd_name=sst"</span>, files),</span>
<span id="cb7-3">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/tmp/sst_stack.vrt"</span>,</span>
<span id="cb7-4">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">options =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"-separate"</span>,</span>
<span id="cb7-5">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">overwrite =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span></span>
<span id="cb7-6">)</span>
<span id="cb7-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># class       : SpatRaster</span></span>
<span id="cb7-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># size        : 720, 1440, 23  (nrow, ncol, nlyr)</span></span>
<span id="cb7-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># source      : sst_stack.vrt</span></span>
<span id="cb7-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># names       : r_1, r_2, r_3, ..., r_23</span></span></code></pre></div></div>
<p><code>vrt://</code> URI syntax selects a subdataset inline; <code>?sd_name=sst</code> is the query param.. The result is a real file — a 2D recipe for this one variable. But trade-offs: time coordinate is gone, layer names are positional. The 2D VRT has no slot for dimension coordinates, it can only carry raw metadata on a dataset or on a band.</p>
</section>
<section id="step-3-stars-read_stars-proxy-gdal-classic-raster-variables-separated" class="level3">
<h3 class="anchored" data-anchor-id="step-3-stars-read_stars-proxy-gdal-classic-raster-variables-separated">Step 3 — stars: <code>read_stars()</code> proxy, GDAL classic raster, variables separated</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb8-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">read_stars</span>(files)</span>
<span id="cb8-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># stars_proxy object with 4 attributes in 92 file(s):</span></span>
<span id="cb8-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># $sst</span></span>
<span id="cb8-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#  [1] "[...]/oisst-avhrr-v02r01.20250101.nc:sst"</span></span>
<span id="cb8-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#  [2] "[...]/oisst-avhrr-v02r01.20250117.nc:sst"</span></span>
<span id="cb8-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#  ...</span></span>
<span id="cb8-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># $anom, $err, $ice  (similarly, 23 files each)</span></span>
<span id="cb8-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#</span></span>
<span id="cb8-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># dimension(s):</span></span>
<span id="cb8-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#      from   to offset delta  refsys x/y</span></span>
<span id="cb8-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># x       1 1440      0  0.25      NA [x]</span></span>
<span id="cb8-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># y       1  720     90 -0.25      NA [y]</span></span>
<span id="cb8-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># zlev    1    1  0 [m]    NA udunits</span></span>
<span id="cb8-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># time    1   23     NA    NA      NA</span></span></code></pre></div></div>
<p>stars proxy mode uses GDAL classic raster and separates variables as attributes rather than interleaving as bands — a meaningful structural difference from terra. Time is present as a dimension but without coordinate values, it’s fully lazy at this point as made clear by the lists of files in subdataset syntax.</p>
</section>
<section id="step-4-stars-read_mdim-on-a-single-file-right-model-wrong-scope" class="level3">
<h3 class="anchored" data-anchor-id="step-4-stars-read_mdim-on-a-single-file-right-model-wrong-scope">Step 4 — stars: <code>read_mdim()</code> on a single file, right model, wrong scope</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb9-1">stars<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">read_mdim</span>(files[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L])</span>
<span id="cb9-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># stars object with 4 dimensions and 4 attributes</span></span>
<span id="cb9-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># attribute(s):</span></span>
<span id="cb9-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#            Min.  1st Qu.  Median      Mean  3rd Qu.   Max.     NAs</span></span>
<span id="cb9-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># sst [°C]  -1.80    -1.80   -1.78  -1.70779    -1.64  -1.03   89635</span></span>
<span id="cb9-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># anom [°C] -0.72    -0.08   -0.02  -0.03531     0.00   0.49   89635</span></span>
<span id="cb9-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># err [°C]   0.30     0.30    0.30   0.30446     0.30   0.35   89635</span></span>
<span id="cb9-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ice [%]    0.19     0.81    0.95   0.87747     0.98   1.00   91877</span></span>
<span id="cb9-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># dimension(s):</span></span>
<span id="cb9-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#      from   to  offset  delta          refsys x/y</span></span>
<span id="cb9-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># lon     1 1440       0   0.25  WGS 84 (CRS84) [x]</span></span>
<span id="cb9-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># lat     1  720     -90   0.25  WGS 84 (CRS84) [y]</span></span>
<span id="cb9-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># zlev    1    1  0 [m]      NA         udunits</span></span>
<span id="cb9-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># time    1    1  17532 [days since 1978-01-01 12:00:00]  NA  udunits</span></span></code></pre></div></div>
<p><code>read_mdim()</code> goes through GDAL’s multidimensional API: named attributes, labelled dimensions, udunits-encoded time coordinate, proper spatial reference. This is a rich representation of the dataset.</p>
<p>But pass stars all 23 files and it silently opens only the first, one timestep, and no error. stars has other ways of concatenating files <code>along</code> dimensions but it’s only applied to GDAL classic raster context.</p>
<p>This is a pivot point.</p>
</section>
<section id="step-5-gdal-mdim-mosaic-read_mdim-the-full-tour" class="level3">
<h3 class="anchored" data-anchor-id="step-5-gdal-mdim-mosaic-read_mdim-the-full-tour">Step 5 — <code>gdal mdim mosaic</code> + <code>read_mdim()</code>, the full tour</h3>
<p><code>gdal mdim mosaic</code> is the missing piece. It takes a collection of files and produces a multidim VRT that describes them as a unified n-D array — dimension coordinates and all:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb10-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># writeLines(sprintf("/vsicurl/%s", urls), "filelist.txt")</span></span>
<span id="cb10-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">gdal</span> mdim mosaic <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--input</span> @filelist.txt <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-o</span> /tmp/sst_mdim.vrt</span></code></pre></div></div>
<p>Or from R via <code>vrtstack</code> or a direct <code>system()</code> call. Now hand that VRT to <code>read_mdim()</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb11-1">stars<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">read_mdim</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/tmp/sst_mdim.vrt"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">proxy =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb11-2">stars_proxy object with <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span> attributes <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">file</span>(s)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb11-3"><span class="er" style="color: #AD0000;
background-color: null;
font-style: inherit;">$</span>sst</span>
<span id="cb11-4">[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"[...]/sst_mdim.vrt"</span></span>
<span id="cb11-5"></span>
<span id="cb11-6"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>anom</span>
<span id="cb11-7">[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"[...]/sst_mdim.vrt"</span></span>
<span id="cb11-8"></span>
<span id="cb11-9"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>err</span>
<span id="cb11-10">[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"[...]/sst_mdim.vrt"</span></span>
<span id="cb11-11"></span>
<span id="cb11-12"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>ice</span>
<span id="cb11-13">[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"[...]/sst_mdim.vrt"</span></span>
<span id="cb11-14"></span>
<span id="cb11-15"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dimension</span>(s)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb11-16">     from   to offset delta x<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span>y</span>
<span id="cb11-17">lon     <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1440</span>      <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span> [x]</span>
<span id="cb11-18">lat     <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">720</span>    <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">90</span>  <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span> [y]</span>
<span id="cb11-19">zlev    <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>    <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>      <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>    <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NA</span></span>
<span id="cb11-20">time    <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>   <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">23</span>  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">17167</span>    <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">16</span></span></code></pre></div></div>
<p>23 time steps in <code>/tmp/sst_mdim.vrt</code> which is a tiny file of XML.</p>
<p>This is what <code>gdal mdim mosaic</code> unlocks: <code>read_mdim()</code> and friends need VRT to make the multi-file case work.</p>
</section>
<section id="step-6-skip-the-download-url-access-who-can-and-cant" class="level3">
<h3 class="anchored" data-anchor-id="step-6-skip-the-download-url-access-who-can-and-cant">Step 6 — skip the download: URL access, who can and can’t</h3>
<p>The files we downloaded are publicly accessible over plain HTTPS. Why download at all? Let’s see who can open them directly.</p>
<p><strong>GDAL</strong> — just works. GDAL’s <code>/vsicurl/</code> virtual filesystem handles HTTP range requests transparently, and both <code>terra::rast()</code> (via GDAL for the spatial read) and <code>stars::read_mdim()</code> can open the URLs directly once you prefix them appropriately. No download needed.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb12-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># GDAL-backed read direct from URL</span></span>
<span id="cb12-2">stars<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">read_mdim</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">paste0</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsicurl/"</span>, urls[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L]))</span>
<span id="cb12-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># stars object with 4 dimensions and 4 attributes</span></span>
<span id="cb12-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ... same output as Step 4, no local file required</span></span></code></pre></div></div>
<p><strong>ncdf4</strong> — the picture is more complicated. ncdf4 routes through the netCDF-C library, which expects either a local file path or an OPeNDAP/Thredds <code>dodsC</code> URL. A plain HTTPS URL fails, and the error is instructive:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb13-1">ncdf4<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nc_open</span>(urls[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb13-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># syntax error, unexpected WORD_WORD, expecting SCAN_ATTR or</span></span>
<span id="cb13-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># SCAN_DATASET or SCAN_ERROR</span></span>
<span id="cb13-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># context: &lt;!DOCTYPE^ HTML PUBLIC ...&gt;&lt;h1&gt;Not Found&lt;/h1&gt;...</span></span>
<span id="cb13-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Error in ncdf4::nc_open: NetCDF: file not found</span></span></code></pre></div></div>
<p>ncdf4 tried to parse the 404 HTML response as netCDF syntax. But there’s a workaround — the <code>#mode=bytes</code> suffix instructs the HDF5 layer to use HTTP byte-range requests rather than treating the URL as a file path:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb14-1">ncdf4<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nc_open</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sprintf</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"%s#mode=bytes"</span>, urls[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]))</span>
<span id="cb14-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># File https://...oisst-avhrr-v02r01.20250101.nc#mode=bytes</span></span>
<span id="cb14-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># (NC_FORMAT_NETCDF4):</span></span>
<span id="cb14-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   4 variables (excluding dimension variables):</span></span>
<span id="cb14-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   sst, anom, err, ice ...</span></span></code></pre></div></div>
<p>It works — but you have to know the magic suffix. And because terra’s netCDF reading is backed by ncdf4, it inherits this same limitation: plain HTTPS URLs don’t work without <code>#mode=bytes</code>, and OPeNDAP <code>dodsC</code> URLs are the usual workaround for terra users working with remote data.</p>
<p>This is the library-identity problem: the same URL provides three different outcomes depending on which library is doing the opening. GDAL’s cloud-native HTTP access is a first-class citizen but the ’/vsi*’ prefix is clearly not popular in some communities; NetCDF’s is an afterthought requiring a suffix that hardly anyone seems to know about. The recipe format inherits these differences — a VRT that references HTTPS URLs is GDAL-native and just works; a workflow built on NetCDF needs extra plumbing.</p>
</section>
<section id="step-7-rhdf5-dev-opening-the-door-to-chunk-indexing-dev-not-yet-on-cran" class="level3">
<h3 class="anchored" data-anchor-id="step-7-rhdf5-dev-opening-the-door-to-chunk-indexing-dev-not-yet-on-cran">Step 7 — rhdf5-dev: opening the door to chunk indexing <em>(dev, not yet on CRAN)</em></h3>
<p>The development version of <code>rhdf5</code> on GitHub can open remote HDF5 files over HTTPS and S3 using the ROS3 virtual file driver — no download, no <code>#mode=bytes</code> workaround, just direct anonymous access:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb15-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># remotes::install_github("grimbough/rhdf5")</span></span>
<span id="cb15-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(rhdf5)</span>
<span id="cb15-3"></span>
<span id="cb15-4">fapl <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">H5Pcreate</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"H5P_FILE_ACCESS"</span>)</span>
<span id="cb15-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">H5Pset_fapl_ros3</span>(fapl)   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># no credentials → anonymous mode</span></span>
<span id="cb15-6"></span>
<span id="cb15-7">fid <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">H5Fopen</span>(urls[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">flags =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"H5F_ACC_RDONLY"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fapl =</span> fapl)</span>
<span id="cb15-8">fid</span>
<span id="cb15-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># HDF5 FILE</span></span>
<span id="cb15-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#         name /</span></span>
<span id="cb15-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     filename</span></span>
<span id="cb15-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#</span></span>
<span id="cb15-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   name       otype       dclass              dim</span></span>
<span id="cb15-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 0 anom  H5I_DATASET    INTEGER  1440 x 720 x 1 x 1</span></span>
<span id="cb15-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1 err   H5I_DATASET    INTEGER  1440 x 720 x 1 x 1</span></span>
<span id="cb15-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2 ice   H5I_DATASET    INTEGER  1440 x 720 x 1 x 1</span></span>
<span id="cb15-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 3 lat   H5I_DATASET      FLOAT  720</span></span>
<span id="cb15-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 4 lon   H5I_DATASET      FLOAT  1440</span></span>
<span id="cb15-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 5 sst   H5I_DATASET    INTEGER  1440 x 720 x 1 x 1</span></span>
<span id="cb15-20"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 6 time  H5I_DATASET      FLOAT  1</span></span>
<span id="cb15-21"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 7 zlev  H5I_DATASET      FLOAT  1</span></span></code></pre></div></div>
<p>What you’re seeing here is the raw HDF5 structure of the file — variables as datasets, dimensions as separate float arrays, all visible without reading a single data value. This is exactly the level at which VirtualiZarr operates: it walks this structure, reads the chunk B-tree for each dataset, and records the byte offset and length of every chunk.</p>
<p>Once <code>rhdf5</code> can expose those chunk byte-references — which is the next step in its development — R will have a native path to VirtualiZarr store creation without Python, without shelling out, and working directly on files at HTTPS or S3 URLs. The <code>H5Pset_fapl_ros3()</code> call with no credentials is the anonymous S3/HTTPS access pattern: it works for any publicly accessible endpoint that supports range requests, including NCEI, NASA Earthdata public buckets, and most open data archives.</p>
<p>The reading side (<code>zaro</code>) is already there. The indexing side (<code>rhdf5-dev</code>) is almost there. The assembly side (<code>gdal mdim mosaic</code> → Zarr reference store) is a small conversion on top of infrastructure that already exists. The pieces are in place.</p>
</section>
<section id="step-8-tidync-dev-multi-file-remote-concat-in-one-call-unmerged-dev-branch" class="level3">
<h3 class="anchored" data-anchor-id="step-8-tidync-dev-multi-file-remote-concat-in-one-call-unmerged-dev-branch">Step 8 — tidync dev: multi-file remote concat in one call <em>(unmerged dev branch)</em></h3>
<p>tidync recently got an update in a dev branch to accept multiple files — long on the wishlist but out of reach in the early years of the package. I had also forgotten that remote access to NetCDF is possible via its own library by appending <code>#mode=bytes</code> to the URL — this means we don’t always need an OPeNDAP-enabled server for remote read. (GDAL uses its own techniques for remote access via <code>/vsicurl/</code>.)</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb16-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># remotes::install_github("hypertidy/tidync@multi-input")</span></span>
<span id="cb16-2">tidync<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">tidync</span>(</span>
<span id="cb16-3">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sprintf</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"%s#mode=bytes"</span>, urls),</span>
<span id="cb16-4">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">fast =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>,</span>
<span id="cb16-5">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">concat_dim =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">list</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">name =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"time"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">values =</span> date)</span>
<span id="cb16-6">)</span>
<span id="cb16-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># not a file:</span></span>
<span id="cb16-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ' https://.../oisst-avhrr-v02r01.20250101.nc#mode=bytes '</span></span>
<span id="cb16-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#</span></span>
<span id="cb16-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ... attempting remote connection</span></span>
<span id="cb16-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#</span></span>
<span id="cb16-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Connection succeeded.</span></span>
<span id="cb16-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Data Source (23): oisst-avhrr-v02r01.20250101.nc#mode=bytes,</span></span>
<span id="cb16-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#                   oisst-avhrr-v02r01.20250117.nc#mode=bytes ...</span></span>
<span id="cb16-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Concatenated along 'time' (23/23 steps, 23 sources, fast mode)</span></span>
<span id="cb16-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#</span></span>
<span id="cb16-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Grids (5) &lt;dimension family&gt; : &lt;associated variables&gt;</span></span>
<span id="cb16-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#</span></span>
<span id="cb16-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [1]   D3,D2,D1,D0 : sst, anom, err, ice    **ACTIVE GRID**</span></span>
<span id="cb16-20"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [2]   D0          : time</span></span>
<span id="cb16-21"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [3]   D1          : zlev</span></span>
<span id="cb16-22"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [4]   D2          : lat</span></span>
<span id="cb16-23"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [5]   D3          : lon</span></span>
<span id="cb16-24"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#</span></span>
<span id="cb16-25"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Dimensions 4 (all active):</span></span>
<span id="cb16-26"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   dim  name  length     min       max  unlim coord_dim</span></span>
<span id="cb16-27"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   D0   time      23   20089     20441   TRUE  TRUE</span></span>
<span id="cb16-28"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   D1   zlev       1       0         0  FALSE  TRUE</span></span>
<span id="cb16-29"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   D2   lat      720  -89.875   89.875  FALSE  TRUE</span></span>
<span id="cb16-30"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   D3   lon     1440    0.125   359.88  FALSE  TRUE</span></span></code></pre></div></div>
<p>Twenty-three remote netCDF files concatenated into one lazy n-D dataset in a single call — <code>#mode=bytes</code> vectorized over the URL vector, a synthetic time axis injected via <code>concat_dim</code>, all four variables on the active grid, unlimited time dimension, proper coordinates throughout. We have tested this on the full set of 16,000+ URLs and it opens in less than a minute using the <code>fast</code> and <code>concat_dim</code> mechanism to declare the values on the concatenating dimension.</p>
<p>The “not a file / attempting remote connection / Connection succeeded” in the output is tidync doing its own detective work: it knows this isn’t a local path, tries the remote connection, and succeeds. The <code>#mode=bytes</code> suffix — the ncdf4 workaround from Step 6 — unlocks the whole thing.</p>
<p>This is an R equivalent of the xarray open: the same structure, the same laziness, the same multi-file concatenation. The recipe here is the code rather than a store on disk — and the <code>tidync</code> object it produces is fully navigable with <code>hyper_filter()</code> and <code>hyper_array()</code> without touching the data. The serializable recipe (the Zarr store, the multidim VRT) is still the goal for sharing and interoperability, but as a working representation for analysis, this is already there.</p>
<p>In the last section of the appendix we virtualize to the “normal” xarray/Zarr model, but then open that again with different tools. The same store, opened by four different tools across two languages - there’s a recipe, but it’s not a nailed down ecosystem yet.</p>
</section>
<section id="the-xarray-counterpart-for-comparison" class="level3">
<h3 class="anchored" data-anchor-id="the-xarray-counterpart-for-comparison">The xarray counterpart — for comparison</h3>
<p>First create the VirtualiZarr version. We use the GDAL mdim VRT hosted above because it’s convenient — and that’s where this effort will start: GDAL already has all the metadata and array concatenating logic, and we can pair that with byte reference extraction improvements to GDAL’s <code>GetRawBlockInfo()</code>.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb17-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> lxml <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> etree</span>
<span id="cb17-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> requests</span>
<span id="cb17-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> urllib.parse <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> urlparse</span>
<span id="cb17-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> xarray <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> xr</span>
<span id="cb17-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> obstore.store <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> HTTPStore</span>
<span id="cb17-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> obspec_utils.registry <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> ObjectStoreRegistry</span>
<span id="cb17-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> virtualizarr <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> open_virtual_mfdataset</span>
<span id="cb17-8"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> virtualizarr.parsers <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> HDFParser</span>
<span id="cb17-9"></span>
<span id="cb17-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 1. parse the VRT to get source URLs</span></span>
<span id="cb17-11">r <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> requests.get(</span>
<span id="cb17-12">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"https://github.com/mdsumner/quarto-blog/releases/download/latest/oisst_2025_sample.vrt"</span></span>
<span id="cb17-13">)</span>
<span id="cb17-14">tree <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> etree.fromstring(r.content)</span>
<span id="cb17-15">urls <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sorted</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>(             <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># sorted: set() loses order, filenames are date-stamped</span></span>
<span id="cb17-16">    el.text.replace(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsicurl/"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>)</span>
<span id="cb17-17">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> el <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> tree.findall(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">".//SourceFilename"</span>)</span>
<span id="cb17-18">))</span>
<span id="cb17-19"></span>
<span id="cb17-20"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 2. set up the object store (anonymous HTTP)</span></span>
<span id="cb17-21">parsed <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> urlparse(urls[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb17-22">bucket <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>parsed<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>scheme<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">://</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>parsed<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>netloc<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb17-23">registry <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ObjectStoreRegistry({bucket: HTTPStore.from_url(bucket)})</span>
<span id="cb17-24">parser <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> HDFParser()</span>
<span id="cb17-25"></span>
<span id="cb17-26"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 3. open as a single virtual dataset — no data read, no files downloaded</span></span>
<span id="cb17-27">combined <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> open_virtual_mfdataset(</span>
<span id="cb17-28">    urls,</span>
<span id="cb17-29">    concat_dim<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"time"</span>,</span>
<span id="cb17-30">    parser<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>parser,</span>
<span id="cb17-31">    registry<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>registry,</span>
<span id="cb17-32">    combine<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"nested"</span>,</span>
<span id="cb17-33">)</span>
<span id="cb17-34"></span>
<span id="cb17-35"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># 4. serialize — a directory store describing all 23 files</span></span>
<span id="cb17-36">combined.virtualize.to_kerchunk(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sst_virtual.zarr"</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">format</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"zarr"</span>)</span></code></pre></div></div>
<p>Now open with xarray:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb18-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># note the trailing slash — this is a Zarr directory store, not a single file</span></span>
<span id="cb18-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># fsspec needs it to resolve the .zmetadata index correctly</span></span>
<span id="cb18-3">ds <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> xr.open_dataset(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sst_virtual.zarr/"</span>, engine<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"kerchunk"</span>)</span>
<span id="cb18-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># &lt;xarray.Dataset&gt; Size: 763MB</span></span>
<span id="cb18-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Dimensions:  (time: 23, zlev: 1, lat: 720, lon: 1440)</span></span>
<span id="cb18-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Coordinates:</span></span>
<span id="cb18-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   * time  (time) datetime64[ns] 2025-01-01T12:00:00 ... 2025-12-19T12:00:00</span></span>
<span id="cb18-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   * zlev  (zlev) float32 0.0</span></span>
<span id="cb18-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   * lat   (lat) float32 -89.88 -89.62 ... 89.62 89.88</span></span>
<span id="cb18-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#   * lon   (lon) float32 0.125 0.375 ... 359.6 359.9</span></span>
<span id="cb18-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Data variables:</span></span>
<span id="cb18-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     anom  (time, zlev, lat, lon) float64 191MB ...</span></span>
<span id="cb18-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     err   (time, zlev, lat, lon) float64 191MB ...</span></span>
<span id="cb18-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     ice   (time, zlev, lat, lon) float64 191MB ...</span></span>
<span id="cb18-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     sst   (time, zlev, lat, lon) float64 191MB ...</span></span>
<span id="cb18-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Attributes: (12/37)</span></span>
<span id="cb18-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     Conventions:   CF-1.6, ACDD-1.3</span></span>
<span id="cb18-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     title:         NOAA/NCEI 1/4 Degree Daily Optimum Interpolation ...</span></span>
<span id="cb18-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     metadata_link: https://doi.org/10.25921/RE9P-PT57</span></span>
<span id="cb18-20"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#     sensor:        Thermometer, AVHRR</span></span></code></pre></div></div>
<p>That virtual store is available here:</p>
<pre><code>https://raw.githubusercontent.com/mdsumner/quarto-blog/refs/heads/main/data/sst_virtual.zarr</code></pre>
<p>Open with xarray directly from the URL (note the trailing slash):</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb20-1">xr.open_dataset(</span>
<span id="cb20-2">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"https://raw.githubusercontent.com/mdsumner/quarto-blog/refs/heads/main/data/sst_virtual.zarr/"</span>,</span>
<span id="cb20-3">    engine<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"kerchunk"</span></span>
<span id="cb20-4">)</span></code></pre></div></div>
<p>Open with GDAL:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb21-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> osgeo <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> gdal</span>
<span id="cb21-2">ds <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> gdal.OpenEx(</span>
<span id="cb21-3">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ZARR:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">/vsicurl/https://raw.githubusercontent.com/mdsumner/quarto-blog/refs/heads/main/data/sst_virtual.zarr</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>,</span>
<span id="cb21-4">    gdal.OF_MULTIDIM_RASTER</span>
<span id="cb21-5">)</span></code></pre></div></div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb22" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb22-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">gdal</span> mdim info <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ZARR:</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">/vsicurl/https://raw.githubusercontent.com/mdsumner/quarto-blog/refs/heads/main/data/sst_virtual.zarr</span><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span></code></pre></div></div>
<p>Open with zaro:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb23" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb23-1">z <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">zaro</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"virtualizarr://https://raw.githubusercontent.com/mdsumner/quarto-blog/refs/heads/main/data/sst_virtual.zarr"</span>)</span>
<span id="cb23-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">zaro_meta</span>(z)</span>
<span id="cb23-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [zaro] found .zmetadata (Zarr V2 consolidated)</span></span>
<span id="cb23-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [zaro]   8 arrays: anom, err, ice, lat, lon, sst, time, zlev</span></span>
<span id="cb23-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># &lt;zaro::ZaroMeta&gt;</span></span>
<span id="cb23-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#  @ zarr_format    : int 2</span></span>
<span id="cb23-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#  @ node_type      : chr "group"</span></span>
<span id="cb23-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#  @ attributes     :List of 37</span></span>
<span id="cb23-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#  .. $ Conventions: chr "CF-1.6, ACDD-1.3"</span></span>
<span id="cb23-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#  .. $ title      : chr "NOAA/NCEI 1/4 Degree Daily Optimum Interpolation ..."</span></span>
<span id="cb23-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#  .. $ metadata_link: chr "https://doi.org/10.25921/RE9P-PT57"</span></span>
<span id="cb23-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#  ...</span></span></code></pre></div></div>
<p>Open with stars:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb24" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb24-1">stars<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">read_mdim</span>(</span>
<span id="cb24-2">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ZARR:</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">/vsicurl/https://raw.githubusercontent.com/mdsumner/quarto-blog/refs/heads/main/data/sst_virtual.zarr</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb24-3">)</span>
<span id="cb24-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># stars object with 4 dimensions and 4 attributes</span></span>
<span id="cb24-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># attribute(s), summary of first 1e+05 cells:</span></span>
<span id="cb24-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#            Min. 1st Qu. Median       Mean 3rd Qu. Max.   NAs</span></span>
<span id="cb24-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># anom [°C] -1.58   -0.61  -0.43 -0.3861891   -0.20 1.45 89635</span></span>
<span id="cb24-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># err [°C]   0.30    0.30   0.30  0.3042393    0.30 0.35 89635</span></span>
<span id="cb24-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ice [%]    0.16    0.62   0.87  0.7743456    0.96 1.00 93223</span></span>
<span id="cb24-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># sst [°C]  -1.80   -1.80  -1.66 -1.4256025   -1.29 1.19 89635</span></span>
<span id="cb24-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># dimension(s):</span></span>
<span id="cb24-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#      from   to                                 offset</span></span>
<span id="cb24-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># lon     1 1440                                      0</span></span>
<span id="cb24-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># lat     1  720                                    -90</span></span>
<span id="cb24-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># zlev    1    1                                  0 [m]</span></span>
<span id="cb24-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># time    1   23  17167 [days since 1978-01-01T12:00:00]</span></span>
<span id="cb24-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#                                    delta         refsys x/y</span></span>
<span id="cb24-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># lon                                 0.25 WGS 84 (CRS84) [x]</span></span>
<span id="cb24-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># lat                                 0.25 WGS 84 (CRS84) [y]</span></span>
<span id="cb24-20"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># zlev                                  NA        udunits</span></span>
<span id="cb24-21"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># time  16 [days since 1978-01-01T12:00:00]        udunits</span></span></code></pre></div></div>
<p>Oh, and turns out <code>terra::rast()</code> <em>does</em> support multidim (not our VRT file sadly):</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb25" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb25-1">r <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rast</span>(  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ZARR:</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">/vsicurl/https://raw.githubusercontent.com/mdsumner/quarto-blog/refs/heads/main/data/sst_virtual.zarr</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\"</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">md  =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb25-2">class       <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> SpatRaster</span>
<span id="cb25-3">size        <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">720</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1440</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">92</span>  (nrow, ncol, nlyr)</span>
<span id="cb25-4">resolution  <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span>  (x, y)</span>
<span id="cb25-5">extent      <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">360</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">90</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">90</span>  (xmin, xmax, ymin, ymax)</span>
<span id="cb25-6">coord. ref. <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> lon<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span>lat WGS <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">84</span> (CRS84) (OGC<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span>CRS84)</span>
<span id="cb25-7">sources     <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> sst_virtual.zarr</span>
<span id="cb25-8">varnames    <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">anom</span> (Daily sea surface temperature anomalies)</span>
<span id="cb25-9">              <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">err</span> (Estimated error standard deviation of analysed_sst)</span>
<span id="cb25-10">              <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ice</span> (Sea ice concentration)</span>
<span id="cb25-11">              <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sst</span> (Daily sea surface temperature)</span>
<span id="cb25-12">names       <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> anom_<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>v<span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>_1, anom_<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>v<span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>_2, anom_<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>v<span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>_3, anom_<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>v<span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>_4, anom_<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>v<span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>_5, anom_<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>v<span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>_6, ...</span>
<span id="cb25-13">unit        <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span>     Celsius,     Celsius,     Celsius,     Celsius,     Celsius,     Celsius, ...</span>
<span id="cb25-14">depth       <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb25-15"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">time</span> (days) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2025-01-01</span> to <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2025-12-19</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">23</span> steps)</span></code></pre></div></div>
<p>There’s a lot here.</p>
<hr>
<p><em>Michael Sumner is a research software engineer at the Australian Antarctic Division. Packages mentioned — <code>tidync</code> (rOpenSci reviewed), <code>vapour</code>, <code>vrtstack</code>, <code>zaro</code> — are part of the <a href="https://github.com/hypertidy">hypertidy</a> ecosystem.</em></p>


</section>
</section>

 ]]></description>
  <category>code</category>
  <category>news</category>
  <category>r-packages</category>
  <category>python</category>
  <category>zarr</category>
  <category>gdal</category>
  <category>netcdf</category>
  <guid>https://hypertidy.org/posts/2026-05-27_raster-stack-zarr-virtualization/</guid>
  <pubDate>Wed, 27 May 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Check it out: proj_trans_create and wk_transform</title>
  <dc:creator>Michael Sumner</dc:creator>
  <link>https://hypertidy.org/posts/2026-03-24_check-out-proj-wk-transform/</link>
  <description><![CDATA[ 






<p>The <a href="https://hypertidy.github.io/PROJ/">PROJ</a> R package provides <code>proj_trans_create()</code>, which creates a reusable coordinate transformation object that plugs directly into wk’s <code>wk_transform()</code>. This is the mechanism that connects the PROJ C library to any geometry type that speaks wk.</p>
<p>This post goes into some detail on what CRS representations are accepted, how R’s CRS objects get resolved to strings that PROJ understands, and how wk’s <code>wk_crs_proj_definition()</code> generic bridges the gap.</p>
<section id="what-proj-accepts-as-a-crs-definition" class="level2">
<h2 class="anchored" data-anchor-id="what-proj-accepts-as-a-crs-definition">What PROJ accepts as a CRS definition</h2>
<p>At the C level, PROJ’s <code>proj_create()</code> function accepts a remarkably wide range of CRS definition strings. The <a href="https://proj.org/en/stable/development/reference/functions.html#transformation-setup">PROJ C API documentation</a> lists the following as valid inputs:</p>
<ul>
<li>A <strong>proj-string</strong>, e.g.&nbsp;<code>"+proj=utm +zone=55 +south +datum=WGS84 +type=crs"</code></li>
<li>A <strong>WKT string</strong> (WKT2:2019 preferred, WKT1 accepted), e.g.&nbsp;<code>GEOGCRS["WGS 84", ...]</code></li>
<li>An <strong>object code</strong>, e.g.&nbsp;<code>"EPSG:4326"</code>, <code>"urn:ogc:def:crs:EPSG::4326"</code>, or <code>"urn:ogc:def:coordinateOperation:EPSG::1671"</code></li>
<li>An <strong>object name</strong>, e.g.&nbsp;<code>"WGS 84"</code> or <code>"WGS 84 / UTM zone 31N"</code> — matched by heuristics when the name is not unique</li>
<li>An <strong>OGC URN</strong> for compound CRS, e.g.&nbsp;<code>"urn:ogc:def:crs,crs:EPSG::2393,crs:EPSG::5717"</code> or the abbreviated <code>"EPSG:2393+5717"</code></li>
<li>An <strong>OGC URN</strong> for concatenated operations, e.g.&nbsp;<code>"urn:ogc:def:coordinateOperation,coordinateOperation:EPSG::3895,coordinateOperation:EPSG::1618"</code></li>
<li>A <strong>PROJJSON string</strong> (added in PROJ 6.2) — the JSON representation of a CRS or coordinate operation, schema at <a href="https://proj.org/schemas/v0.4/projjson.schema.json" class="uri">https://proj.org/schemas/v0.4/projjson.schema.json</a></li>
<li>A <strong>compound CRS</strong> from two names joined with <code>" + "</code>, e.g.&nbsp;<code>"WGS 84 + EGM96 height"</code> (added in PROJ 7.1)</li>
</ul>
<p>That’s a lot of ways to say the same thing. In R, the PROJ package wraps this directly — <code>proj_trans_create()</code> and <code>proj_crs_text()</code> accept all of these forms as character strings.</p>
<p>There is a critical distinction for proj-strings: if the string contains <code>+type=crs</code>, it’s interpreted as a CRS definition. Geographic CRS with <code>+type=crs</code> are assumed to have longitude-latitude axis order with degree units. Without <code>+type=crs</code>, the proj-string is interpreted as a <em>coordinate operation</em> (a transformation step, not a CRS). This matters in some contexts but many packages (PROJ, sf, terra) will detect this case and add the ‘+type=crs’ because it’s common and convenient.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(PROJ)</span>
<span id="cb1-2"></span>
<span id="cb1-3"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## this is a CRS ("+type=crs" present):</span></span>
<span id="cb1-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">proj_crs_text</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=laea +type=crs"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">format =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>L)</span>
<span id="cb1-5"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## returns a proper WKT2 CRS</span></span>
<span id="cb1-6"></span>
<span id="cb1-7"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## this is technically a transformation step, NOT a CRS (no "+type=crs") but workarounds are often used (as here)</span></span>
<span id="cb1-8"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">proj_crs_text</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=laea"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">format =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>L)</span></code></pre></div></div>
<p>The PROJ library documentation explicitly says: “The use of proj-string to describe a CRS is discouraged. It is a legacy means of conveying CRS descriptions: use of object codes (EPSG:XXXX typically) or WKT description is recommended for better expressivity.” This is absolutely true but if you want to define your own CRS, just a simple equal area project such as “+proj=laea +lon_0=-147 +lat_0=-42 +datum=WGS4” then you can easily convert from a proj string to WKT, but write the WKT from scratch?? That’s hard but don’t let it put you off, use map projections, specified in a few characters as PROJ strings - if you need a specific datum you’ll need to get a full-blown WKT string by some means.</p>
</section>
<section id="crs-representations-you-can-work-with-in-r" class="level2">
<h2 class="anchored" data-anchor-id="crs-representations-you-can-work-with-in-r">CRS representations you can work with in R</h2>
<p><code>proj_crs_text()</code> converts between CRS representations. The <code>format</code> argument selects the output:</p>
<ul>
<li><code>format = 0L</code> → WKT2 (the default, most expressive)</li>
<li><code>format = 1L</code> → proj-string</li>
<li><code>format = 2L</code> → PROJJSON</li>
</ul>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(PROJ)</span>
<span id="cb2-2"></span>
<span id="cb2-3"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## WKT2</span></span>
<span id="cb2-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cat</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">proj_crs_text</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">format =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>L))</span></code></pre></div></div>
<pre><code>GEOGCRS["WGS 84",
    ENSEMBLE["World Geodetic System 1984 ensemble",
        MEMBER["World Geodetic System 1984 (Transit)"],
        MEMBER["World Geodetic System 1984 (G730)"],
        MEMBER["World Geodetic System 1984 (G873)"],
        MEMBER["World Geodetic System 1984 (G1150)"],
        MEMBER["World Geodetic System 1984 (G1674)"],
        MEMBER["World Geodetic System 1984 (G1762)"],
        MEMBER["World Geodetic System 1984 (G2139)"],
        ELLIPSOID["WGS 84",6378137,298.257223563,
            LENGTHUNIT["metre",1]],
        ENSEMBLEACCURACY[2.0]],
    PRIMEM["Greenwich",0,
        ANGLEUNIT["degree",0.0174532925199433]],
    CS[ellipsoidal,2],
        AXIS["geodetic latitude (Lat)",north,
            ORDER[1],
            ANGLEUNIT["degree",0.0174532925199433]],
        AXIS["geodetic longitude (Lon)",east,
            ORDER[2],
            ANGLEUNIT["degree",0.0174532925199433]],
    USAGE[
        SCOPE["Horizontal component of 3D system."],
        AREA["World."],
        BBOX[-90,-180,90,180]],
    ID["EPSG",4326]]</code></pre>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## proj-string</span></span>
<span id="cb4-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">proj_crs_text</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">format =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L)</span>
<span id="cb4-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; "+proj=longlat +datum=WGS84 +no_defs +type=crs"</span></span></code></pre></div></div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## PROJJSON</span></span>
<span id="cb5-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cat</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">proj_crs_text</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">format =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>L))</span></code></pre></div></div>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode json code-with-copy"><code class="sourceCode json"><span id="cb6-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-2">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"$schema"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"https://proj.org/schemas/v0.7/projjson.schema.json"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-3">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"type"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"GeographicCRS"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-4">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"name"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"WGS 84"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-5">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"datum_ensemble"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-6">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"name"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"World Geodetic System 1984 ensemble"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-7">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"members"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb6-8">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-9">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"name"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"World Geodetic System 1984 (Transit)"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-10">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"id"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-11">          <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"authority"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-12">          <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"code"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1166</span></span>
<span id="cb6-13">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb6-14">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-15">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-16">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"name"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"World Geodetic System 1984 (G730)"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-17">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"id"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-18">          <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"authority"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-19">          <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"code"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1152</span></span>
<span id="cb6-20">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb6-21">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-22">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-23">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"name"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"World Geodetic System 1984 (G873)"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-24">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"id"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-25">          <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"authority"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-26">          <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"code"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1153</span></span>
<span id="cb6-27">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb6-28">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-29">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-30">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"name"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"World Geodetic System 1984 (G1150)"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-31">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"id"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-32">          <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"authority"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-33">          <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"code"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1154</span></span>
<span id="cb6-34">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb6-35">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-36">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-37">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"name"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"World Geodetic System 1984 (G1674)"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-38">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"id"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-39">          <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"authority"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-40">          <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"code"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1155</span></span>
<span id="cb6-41">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb6-42">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-43">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-44">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"name"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"World Geodetic System 1984 (G1762)"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-45">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"id"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-46">          <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"authority"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-47">          <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"code"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1156</span></span>
<span id="cb6-48">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb6-49">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-50">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-51">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"name"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"World Geodetic System 1984 (G2139)"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-52">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"id"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-53">          <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"authority"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-54">          <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"code"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1309</span></span>
<span id="cb6-55">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb6-56">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-57">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-58">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"name"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"World Geodetic System 1984 (G2296)"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-59">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"id"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-60">          <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"authority"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-61">          <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"code"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1383</span></span>
<span id="cb6-62">        <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb6-63">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb6-64">    <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-65">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"ellipsoid"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-66">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"name"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"WGS 84"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-67">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"semi_major_axis"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6378137</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-68">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"inverse_flattening"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">298.257223563</span></span>
<span id="cb6-69">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">},</span></span>
<span id="cb6-70">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"accuracy"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2.0"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-71">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"id"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-72">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"authority"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-73">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"code"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">6326</span></span>
<span id="cb6-74">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb6-75">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">},</span></span>
<span id="cb6-76">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"coordinate_system"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-77">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"subtype"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ellipsoidal"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-78">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"axis"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb6-79">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-80">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"name"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Geodetic latitude"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-81">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"abbreviation"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Lat"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-82">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"direction"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"north"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-83">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"unit"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"degree"</span></span>
<span id="cb6-84">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span><span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-85">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-86">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"name"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Geodetic longitude"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-87">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"abbreviation"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Lon"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-88">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"direction"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"east"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-89">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"unit"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"degree"</span></span>
<span id="cb6-90">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb6-91">    <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb6-92">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">},</span></span>
<span id="cb6-93">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"scope"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Horizontal component of 3D system."</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-94">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"area"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"World."</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-95">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"bbox"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-96">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"south_latitude"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">-90</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-97">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"west_longitude"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">-180</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-98">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"north_latitude"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">90</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-99">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"east_longitude"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180</span></span>
<span id="cb6-100">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">},</span></span>
<span id="cb6-101">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"id"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb6-102">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"authority"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb6-103">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"code"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4326</span></span>
<span id="cb6-104">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb6-105"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<p>PROJJSON is the most machine-friendly format — it’s the full CRS definition in a structure you can parse, manipulate, and round-trip without information loss. WKT2 is the canonical text representation and equally complete. Proj-strings are the most compact, but lose information (datum ensemble members, axis order details, usage scope). Every conversion to proj-string is potentially lossy.</p>
</section>
<section id="how-wk-bridges-crs-objects-to-proj-strings" class="level2">
<h2 class="anchored" data-anchor-id="how-wk-bridges-crs-objects-to-proj-strings">How wk bridges CRS objects to PROJ strings</h2>
<p>In the wk package, a CRS can be <em>any R object</em>. The <code>crs</code> attribute on a <code>wk::wkt()</code>, <code>wk::wkb()</code>, or <code>wk::xy()</code> vector is deliberately untyped — it could be a string like <code>"EPSG:4326"</code>, an integer like <code>4326</code>, an sf <code>crs</code> object, or something else entirely. wk doesn’t interpret the CRS, it just propagates it.</p>
<p>The bridge to PROJ is <code>wk_crs_proj_definition()</code>. This is an S3 generic that takes a CRS object and returns a character string that the PROJ library can understand. This is the function that <code>PROJ::proj_trans()</code> and <code>PROJ::proj_trans_create()</code> call internally to resolve CRS arguments.</p>
<p>The built-in methods handle the common cases:</p>
<ul>
<li><strong>character</strong> → returned as-is (assumed to be an authority:code string, WKT, PROJJSON, or proj-string already)</li>
<li><strong>integer</strong> or <strong>double</strong> → formatted as <code>"EPSG:&lt;code&gt;"</code> (e.g.&nbsp;<code>4326</code> → <code>"EPSG:4326"</code>)</li>
<li><strong>NULL</strong> → <code>NA_character_</code> (no CRS)</li>
<li><strong><code>wk_crs_inherit()</code></strong> → <code>NA_character_</code> (inherit from context)</li>
</ul>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb7-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(wk)</span>
<span id="cb7-2"></span>
<span id="cb7-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_crs_proj_definition</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>)</span>
<span id="cb7-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1] "EPSG:4326"</span></span>
<span id="cb7-5"></span>
<span id="cb7-6"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_crs_proj_definition</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4326</span>)</span>
<span id="cb7-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1] "EPSG:4326"</span></span>
<span id="cb7-8"></span>
<span id="cb7-9"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_crs_proj_definition</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4326</span>L)</span>
<span id="cb7-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1] "EPSG:4326"</span></span></code></pre></div></div>
<p>For sf’s <code>crs</code> class, sf registers its own method so that <code>wk_crs_proj_definition()</code> returns the correct string for the installed version of PROJ. Other packages can do the same — this is the extension point for custom CRS types.</p>
<p>The <code>verbose</code> argument controls how much detail is returned. With <code>verbose = FALSE</code> (the default), it returns the most compact form — typically the authority:code string. With <code>verbose = TRUE</code>, it returns PROJJSON or WKT2 for maximum expressivity.</p>
<section id="wk_crs_projjson" class="level3">
<h3 class="anchored" data-anchor-id="wk_crs_projjson">wk_crs_projjson()</h3>
<p>There’s also <code>wk_crs_projjson()</code>, which returns the PROJJSON representation of a CRS. For shortcut-style CRS values like <code>"EPSG:4326"</code> or <code>4326</code>, wk includes cached lookup tables — <code>wk_proj_crs_view</code> and <code>wk_proj_crs_json</code> — containing pre-rendered PROJJSON for all CRS definitions in the PROJ database (based on PROJ 9.3.0 as of wk 0.9.x). This means <code>wk_crs_projjson("EPSG:4326")</code> works without PROJ being installed — the lookup is pure R against shipped data.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb8-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## get PROJJSON for a known EPSG code, no PROJ library needed</span></span>
<span id="cb8-2">pj <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_crs_projjson</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>)</span>
<span id="cb8-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cat</span>(pj)</span>
<span id="cb8-4"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## full PROJJSON output</span></span></code></pre></div></div>
<p>This is the mechanism that allows wk-based packages to inspect CRS properties without linking to the PROJ C library.</p>
</section>
</section>
<section id="the-transformation-pipeline" class="level2">
<h2 class="anchored" data-anchor-id="the-transformation-pipeline">The transformation pipeline</h2>
<p>With all that context, here’s how <code>proj_trans_create()</code> works:</p>
<ol type="1">
<li>It calls <code>wk_crs_proj_definition()</code> on the <code>source_crs</code> and <code>target_crs</code> arguments to get PROJ-compatible strings</li>
<li>It passes those strings to the PROJ C library’s <code>proj_create_crs_to_crs()</code> function, which finds the best available transformation pipeline between the two CRS</li>
<li>It wraps the result in a <code>wk_trans</code> object — an external pointer with an <code>wk_trans_inverse()</code> method</li>
</ol>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb9-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(PROJ)</span>
<span id="cb9-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(wk)</span>
<span id="cb9-3"></span>
<span id="cb9-4"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## create a transformation object</span></span>
<span id="cb9-5">trans <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">proj_trans_create</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:3857"</span>)</span>
<span id="cb9-6"><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>proj_trans at <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0x55800593fd60</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span></span>
<span id="cb9-7">type<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> Concatenated Operation</span>
<span id="cb9-8">id<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> pipeline</span>
<span id="cb9-9">description<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> axis order <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">change</span> (<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>D) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> Popular Visualisation Pseudo<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>Mercator</span>
<span id="cb9-10">definition<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> proj<span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span>pipeline step proj<span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span>unitconvert xy_in<span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span>deg xy_out<span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span>rad step proj<span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span>webmerc lat_0<span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> lon_0<span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> x_0<span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> y_0<span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span> ellps<span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">=</span>WGS84</span>
<span id="cb9-11">area_of_use<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb9-12">  name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> World</span>
<span id="cb9-13">  bounds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> [<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">90</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">90</span>]</span>
<span id="cb9-14">source_crs<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb9-15">  type<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> Geographic <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>D CRS</span>
<span id="cb9-16">  id<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> EPSG<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4326</span></span>
<span id="cb9-17">  name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> WGS <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">84</span></span>
<span id="cb9-18">  area_of_use<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb9-19">    name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> World.</span>
<span id="cb9-20">    bounds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> [<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">90</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">90</span>]</span>
<span id="cb9-21">target_crs<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb9-22">  type<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> Projected CRS</span>
<span id="cb9-23">  id<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> EPSG<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3857</span></span>
<span id="cb9-24">  name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> WGS <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">84</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> Pseudo<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>Mercator</span>
<span id="cb9-25">  area_of_use<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span></span>
<span id="cb9-26">    name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> World between <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">85.06</span>°S and <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">85.06</span>°N.</span>
<span id="cb9-27">    bounds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span> [<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180</span> <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">85.06</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">180</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">85.06</span>]</span></code></pre></div></div>
<p>The source and target can be any form PROJ accepts — EPSG codes, WKT2, PROJJSON, proj-strings with <code>+type=crs</code>, object names:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb10-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## all of these create equivalent transformations:</span></span>
<span id="cb10-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">proj_trans_create</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:3857"</span>)</span>
<span id="cb10-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">proj_trans_create</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4326</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3857</span>)</span>
<span id="cb10-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">proj_trans_create</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"WGS 84"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"WGS 84 / Pseudo-Mercator"</span>)</span>
<span id="cb10-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">proj_trans_create</span>(</span>
<span id="cb10-6">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=longlat +datum=WGS84 +type=crs"</span>,</span>
<span id="cb10-7">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +type=crs"</span></span>
<span id="cb10-8">)</span></code></pre></div></div>
</section>
<section id="applying-the-transformation-with-wk_transform" class="level2">
<h2 class="anchored" data-anchor-id="applying-the-transformation-with-wk_transform">Applying the transformation with wk_transform</h2>
<p><code>wk::wk_transform()</code> takes any wk-handleable geometry and applies the transformation through wk’s streaming handler API. This means the transformation works on <em>any</em> geometry type — points, lines, polygons, collections — without materializing intermediate representations:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb11-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## transform point coordinates</span></span>
<span id="cb11-2">pts <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">xy</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">147</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">148</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">149</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">42</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">43</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">44</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>)</span>
<span id="cb11-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_transform</span>(pts, trans)</span>
<span id="cb11-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; &lt;wk_xy[3] with CRS=EPSG:3857&gt;</span></span>
<span id="cb11-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1] (16362893 -5160980) (16474013 -5311971) (16585132 -5465442)</span></span>
<span id="cb11-6"></span>
<span id="cb11-7"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## transform well-known text geometries</span></span>
<span id="cb11-8">poly <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wkt</span>(</span>
<span id="cb11-9">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"POLYGON ((147 -42, 148 -42, 148 -43, 147 -43, 147 -42))"</span>,</span>
<span id="cb11-10">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span></span>
<span id="cb11-11">)</span>
<span id="cb11-12"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_transform</span>(poly, trans)</span>
<span id="cb11-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; &lt;wk_wkt[1] with CRS=EPSG:3857&gt;</span></span>
<span id="cb11-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1] POLYGON ((16362893 -5160980, ...))</span></span>
<span id="cb11-15"></span>
<span id="cb11-16"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## transform well-known binary</span></span>
<span id="cb11-17">wkb_geom <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_wkb</span>(poly)</span>
<span id="cb11-18"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_transform</span>(wkb_geom, trans)</span>
<span id="cb11-19"></span>
<span id="cb11-20"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## transform sf geometries (sf implements wk_handle)</span></span>
<span id="cb11-21"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## wk_transform(sf_sfc_object, trans) — just works</span></span></code></pre></div></div>
<p>The CRS of the output is updated automatically. The output carries the target CRS from the transformation.</p>
<p>The transformation object is reusable — create it once, apply it to many geometry vectors. This avoids paying the PROJ pipeline setup cost for each call.</p>
</section>
<section id="inverse-transformations" class="level2">
<h2 class="anchored" data-anchor-id="inverse-transformations">Inverse transformations</h2>
<p><code>wk_trans_inverse()</code> flips a transformation:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb12-1">inv <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_trans_inverse</span>(trans)</span>
<span id="cb12-2"></span>
<span id="cb12-3"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## round-trip</span></span>
<span id="cb12-4">pts_proj <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_transform</span>(pts, trans)</span>
<span id="cb12-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_transform</span>(pts_proj, inv)</span>
<span id="cb12-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; &lt;wk_xy[3] with CRS=EPSG:4326&gt;</span></span>
<span id="cb12-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1] (147 -42) (148 -43) (149 -44)</span></span></code></pre></div></div>
</section>
<section id="the-convenience-wrapper-projproj_trans" class="level2">
<h2 class="anchored" data-anchor-id="the-convenience-wrapper-projproj_trans">The convenience wrapper: PROJ::proj_trans()</h2>
<p>If you don’t need a reusable transformation object, <code>PROJ::proj_trans()</code> combines the create and transform steps. It accepts matrices, data frames, wk geometry vectors, and sf <code>sfc</code> columns:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb13-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## matrix form — requires explicit source</span></span>
<span id="cb13-2">PROJ<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">proj_trans</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cbind</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">147</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">42</span>), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=laea +type=crs"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>)</span>
<span id="cb13-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt;          x        y</span></span>
<span id="cb13-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1,] 5969744 -9803200</span></span>
<span id="cb13-5"></span>
<span id="cb13-6"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## wk type with CRS — source is inferred from the data</span></span>
<span id="cb13-7">PROJ<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">proj_trans</span>(wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">xy</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">147</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">42</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>), <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=laea +type=crs"</span>)</span>
<span id="cb13-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; &lt;wk_xy[1] with CRS=+proj=laea +type=crs&gt;</span></span>
<span id="cb13-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1] (5969744 -9803200)</span></span>
<span id="cb13-10"></span>
<span id="cb13-11"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## full WKT geometries work too</span></span>
<span id="cb13-12">PROJ<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">proj_trans</span>(</span>
<span id="cb13-13">  wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wkt</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"POLYGON ((1 1, 0 1, 0 0, 1 0, 1 1))"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>),</span>
<span id="cb13-14">  <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3112</span></span>
<span id="cb13-15">)</span>
<span id="cb13-16"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; &lt;wk_wkt[1] with CRS=EPSG:3112&gt;</span></span>
<span id="cb13-17"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1] POLYGON ((-1.351177e+07 -7779443, ...))</span></span></code></pre></div></div>
<p>When the input has a CRS (via <code>wk_crs()</code>), the <code>source_crs</code> argument can be omitted — <code>proj_trans()</code> reads it from the data using <code>wk_crs_proj_definition()</code>.</p>
</section>
<section id="the-typecrs-distinction" class="level2">
<h2 class="anchored" data-anchor-id="the-typecrs-distinction">The +type=crs distinction</h2>
<p>This is worth emphasizing because it catches people. In PROJ 6+, a bare proj-string like <code>"+proj=laea"</code> is a <em>coordinate operation</em>, not a CRS. The library draws a hard line between “a description of a coordinate reference system” and “a description of a mathematical operation to apply to coordinates.”</p>
<p>To specify a CRS using a proj-string, you <em>must</em> include <code>+type=crs</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb14-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## CRS — works as source/target for transformations</span></span>
<span id="cb14-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">proj_crs_text</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=laea +type=crs"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">format =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>L)</span>
<span id="cb14-3"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## returns proper WKT2</span></span>
<span id="cb14-4"></span>
<span id="cb14-5"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## coordinate operation — not a CRS</span></span>
<span id="cb14-6"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">proj_crs_text</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=laea"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">format =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>L)</span>
<span id="cb14-7"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## NOT a CRS definition</span></span></code></pre></div></div>
<p>Object codes (<code>"EPSG:4326"</code>), WKT2, and PROJJSON are unambiguous — they always describe a CRS. Proj-strings need the <code>+type=crs</code> disambiguation. The recommendation from the PROJ documentation is to prefer authority:code strings or WKT2 for CRS definitions. But, use proj strings because they are easy and expressive, when you need a formal WKT2 for a particular operation define it carefully.</p>
</section>
<section id="how-this-connects-to-reproj" class="level2">
<h2 class="anchored" data-anchor-id="how-this-connects-to-reproj">How this connects to reproj</h2>
<p>The <a href="https://hypertidy.github.io/reproj/">reproj</a> package (see the <a href="https://www.hypertidy.org/posts/2026-03-24_check-out-reproj/">reproj</a> post in this series) uses <code>PROJ::proj_trans()</code> under the hood.</p>
<p>The relationship is:</p>
<ul>
<li><strong>reproj</strong> — high-level generic, format-agnostic, delegates to PROJ</li>
<li><strong>PROJ</strong> — mid-level wrapper around the PROJ C library, returns wk types, uses <code>wk_crs_proj_definition()</code> for CRS resolution</li>
<li><strong>wk</strong> — the geometry representation, streaming protocol, and CRS bridge generic</li>
<li><strong>PROJ C library</strong> (<code>libproj</code>) — the actual math, <code>proj_create()</code> does the parsing</li>
</ul>
<p>If you want maximum control and reusable transformation objects, use <code>proj_trans_create()</code> + <code>wk_transform()</code> directly. If you want a quick one-liner for a matrix of coordinates, use <code>reproj()</code>. They’re different interfaces to the same underlying capability.</p>
</section>
<section id="further-reading" class="level2">
<h2 class="anchored" data-anchor-id="further-reading">Further reading</h2>
<ul>
<li><a href="https://proj.org/en/stable/development/reference/functions.html#transformation-setup">PROJ C API: Transformation setup</a> — the canonical reference for what <code>proj_create()</code> accepts</li>
<li><a href="https://proj.org/schemas/v0.4/projjson.schema.json">PROJJSON schema</a></li>
<li><a href="https://paleolimbot.github.io/wk/reference/wk_crs_proj_definition.html">wk <code>wk_crs_proj_definition()</code> docs</a></li>
<li><a href="https://hypertidy.github.io/PROJ/reference/proj_trans.html">PROJ R package <code>proj_trans()</code> docs</a></li>
<li><a href="https://paleolimbot.github.io/wk/reference/wk_transform.html">wk <code>wk_transform()</code> docs</a></li>
</ul>
<p>Install from CRAN:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb15-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">install.packages</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"PROJ"</span>)</span>
<span id="cb15-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">install.packages</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"wk"</span>)</span></code></pre></div></div>
</section>
<section id="check-it-out-series" class="level2">
<h2 class="anchored" data-anchor-id="check-it-out-series">Check it out series</h2>
<p>This is part of a series of posts for foundational spatial support in R. These posts include:</p>
<ul>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-wk/">wk</a></li>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-reproj/">reproj</a></li>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-proj-wk-transform/">PROJ/wk transform</a> (this one!)</li>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-gdalraster/">gdalraster</a></li>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-geos/">geos</a></li>
</ul>


</section>

 ]]></description>
  <category>r-packages</category>
  <category>projections</category>
  <guid>https://hypertidy.org/posts/2026-03-24_check-out-proj-wk-transform/</guid>
  <pubDate>Tue, 24 Mar 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Check it out: gdalraster</title>
  <dc:creator>Michael Sumner</dc:creator>
  <link>https://hypertidy.org/posts/2026-03-24_check-out-gdalraster/</link>
  <description><![CDATA[ 






<p>The <a href="https://firelab.github.io/gdalraster/">gdalraster</a> package provides a comprehensive interface to the GDAL library for R. It handles raster I/O, vector I/O, the virtual filesystem, coordinate transformation, and a large collection of GDAL utilities — all through a direct, low-level API that doesn’t impose a high-level spatial data model.</p>
<section id="raster-io" class="level2">
<h2 class="anchored" data-anchor-id="raster-io">Raster I/O</h2>
<p>The core raster interface is <code>GDALRaster</code>, an Rcpp module class that wraps a GDAL dataset handle:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(gdalraster)</span>
<span id="cb1-2"></span>
<span id="cb1-3">f <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">system.file</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"extdata/storm_lake.lcp"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">package =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gdalraster"</span>)</span>
<span id="cb1-4">ds <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">new</span>(GDALRaster, f, <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb1-5"></span>
<span id="cb1-6">ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">info</span>()</span>
<span id="cb1-7">ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dim</span>()       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># c(ncol, nrow, nbands)</span></span>
<span id="cb1-8">ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">res</span>()       <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># c(xres, yres)</span></span>
<span id="cb1-9">ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">bbox</span>()      <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># c(xmin, ymin, xmax, ymax)</span></span>
<span id="cb1-10">ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">getProjection</span>()</span>
<span id="cb1-11"></span>
<span id="cb1-12"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## read a band</span></span>
<span id="cb1-13">data <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">read</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">band =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,</span>
<span id="cb1-14">                <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">xoff =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">yoff =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,</span>
<span id="cb1-15">                <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">xsize =</span> ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">getRasterXSize</span>(),</span>
<span id="cb1-16">                <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ysize =</span> ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">getRasterYSize</span>(),</span>
<span id="cb1-17">                <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">out_xsize =</span> ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">getRasterXSize</span>(),</span>
<span id="cb1-18">                <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">out_ysize =</span> ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">getRasterYSize</span>())</span>
<span id="cb1-19"></span>
<span id="cb1-20">ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">close</span>()</span></code></pre></div></div>
<p>The <code>read()</code> method maps directly to GDAL’s <code>RasterIO</code> — you specify the source window and the output size. This gives full control over decimation, oversampling, and partial reads. No intermediate abstraction, no automatic resampling decisions you didn’t ask for.</p>
</section>
<section id="vector-io" class="level2">
<h2 class="anchored" data-anchor-id="vector-io">Vector I/O</h2>
<p>gdalraster provides vector read/write through OGR:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## list layers in a data source</span></span>
<span id="cb2-2">dsn <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">system.file</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"extdata/ynp_fires_1984_2022.gpkg"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">package =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gdalraster"</span>)</span>
<span id="cb2-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ogr_ds_layer_names</span>(dsn)</span>
<span id="cb2-4"></span>
<span id="cb2-5"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## feature count and extent</span></span>
<span id="cb2-6"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ogr_ds_layer_count</span>(dsn)</span>
<span id="cb2-7"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ogr_layer_field_names</span>(dsn, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mtbs_perims"</span>)</span>
<span id="cb2-8"></span>
<span id="cb2-9"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## read features as a data frame with WKB geometry</span></span>
<span id="cb2-10">lyr <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">new</span>(GDALVector, dsn, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mtbs_perims"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">read_only =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb2-11">feat <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> lyr<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">fetch</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># all features</span></span>
<span id="cb2-12">lyr<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">close</span>()</span></code></pre></div></div>
<p>The geometry comes back as WKB — directly compatible with wk. You can pass it to <code>wk::as_wkt()</code>, <code>geos::as_geos_geometry()</code>, or <code>sf::st_as_sfc()</code> as needed. gdalraster reads the data; what you do with the geometry is up to you and whatever packages you choose.</p>
</section>
<section id="virtual-filesystem" class="level2">
<h2 class="anchored" data-anchor-id="virtual-filesystem">Virtual filesystem</h2>
<p>GDAL’s <code>/vsicurl/</code>, <code>/vsizip/</code>, <code>/vsimem/</code> and other virtual filesystem handlers are all available:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## read a remote file without downloading</span></span>
<span id="cb3-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vsi_read_dir</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsicurl/https://geodata.ucdavis.edu/gadm/gadm4.1/gpkg/"</span>)</span>
<span id="cb3-3"></span>
<span id="cb3-4"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## open a GeoPackage over HTTP</span></span>
<span id="cb3-5">dsn <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsicurl/https://geodata.ucdavis.edu/gadm/gadm4.1/gpkg/gadm41_AUS.gpkg"</span></span>
<span id="cb3-6"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ogr_ds_layer_names</span>(dsn)</span>
<span id="cb3-7"></span>
<span id="cb3-8"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## read inside a remote zip</span></span>
<span id="cb3-9"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vsi_read_dir</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsizip//vsicurl/https://github.com/OSGeo/gdal/raw/refs/heads/master/autotest/gcore/data/byte.tif.zip"</span>)</span>
<span id="cb3-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#[1] "byte.tif"</span></span></code></pre></div></div>
<p>This is essential for cloud-native workflows. The same dataset string that works locally works over HTTP, S3, or Azure — GDAL handles the transport.</p>
</section>
<section id="the-warper" class="level2">
<h2 class="anchored" data-anchor-id="the-warper">The warper</h2>
<p><code>warp()</code> provides access to GDAL’s warper API for raster reprojection and resampling:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## warp to a new projection</span></span>
<span id="cb4-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">warp</span>(</span>
<span id="cb4-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">src_filename =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"input.tif"</span>,</span>
<span id="cb4-4">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">dst_filename =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"output.tif"</span>,</span>
<span id="cb4-5">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">t_srs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:3577"</span>,</span>
<span id="cb4-6">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">tr =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>),           <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># target resolution</span></span>
<span id="cb4-7">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">r =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bilinear"</span>              <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># resampling method</span></span>
<span id="cb4-8">)</span></code></pre></div></div>
<p>This is the same warper that <code>gdalwarp</code> uses on the command line. For building VRTs, mosaics, and complex raster pipelines, gdalraster provides <code>buildVRT()</code>, <code>translate()</code>, and <code>rasterFromRaster()</code>.</p>
</section>
<section id="vrt-construction" class="level2">
<h2 class="anchored" data-anchor-id="vrt-construction">VRT construction</h2>
<p>One of the most powerful features is programmatic VRT (Virtual Raster) construction:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## create a VRT from multiple source files</span></span>
<span id="cb5-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">buildVRT</span>(</span>
<span id="cb5-3">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">vrt_filename =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mosaic.vrt"</span>,</span>
<span id="cb5-4">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">input_rasters =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tile1.tif"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tile2.tif"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tile3.tif"</span>)</span>
<span id="cb5-5">)</span>
<span id="cb5-6"></span>
<span id="cb5-7"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## add sources to a VRT programmatically</span></span>
<span id="cb5-8">ds <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">new</span>(GDALRaster, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"output.vrt"</span>, <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">FALSE</span>)</span>
<span id="cb5-9">ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">addSimpleSource</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">band =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">src_filename =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"source.tif"</span>, ...)</span>
<span id="cb5-10">ds<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">close</span>()</span></code></pre></div></div>
<p>VRTs are GDAL’s answer to lazy raster operations — the XML file describes the operation but nothing is computed until you read pixels.</p>
</section>
<section id="configuration-and-utilities" class="level2">
<h2 class="anchored" data-anchor-id="configuration-and-utilities">Configuration and utilities</h2>
<p>gdalraster exposes GDAL configuration options:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb6-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## set GDAL config for performance tuning</span></span>
<span id="cb6-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">set_config_option</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"GDAL_CACHEMAX"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"512"</span>)</span>
<span id="cb6-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">set_config_option</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"GDAL_HTTP_MULTIRANGE"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"YES"</span>)</span>
<span id="cb6-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">set_config_option</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"GDAL_NUM_THREADS"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ALL_CPUS"</span>)</span>
<span id="cb6-5"></span>
<span id="cb6-6"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## check what's set</span></span>
<span id="cb6-7"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">get_config_option</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"GDAL_CACHEMAX"</span>)</span>
<span id="cb6-8"></span>
<span id="cb6-9"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## GDAL/PROJ/GEOS version info</span></span>
<span id="cb6-10"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">gdal_version</span>()</span></code></pre></div></div>
<p>Plus a full set of geometry utilities (<code>g_buffer()</code>, <code>g_intersection()</code>, etc.) that use GEOS through GDAL’s OGR layer, and coordinate transformation via <code>transform_xy()</code>.</p>
<p>Install from CRAN:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb7-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">install.packages</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gdalraster"</span>)</span></code></pre></div></div>
<p>Links:</p>
<ul>
<li><a href="https://CRAN.R-project.org/package=gdalraster">gdalraster on CRAN</a></li>
<li><a href="https://usdaforestservice.github.io/gdalraster/">gdalraster documentation</a></li>
<li><a href="https://github.com/USDAForestService/gdalraster">GitHub</a></li>
</ul>
</section>
<section id="check-it-out-series" class="level2">
<h2 class="anchored" data-anchor-id="check-it-out-series">Check it out series</h2>
<p>This is part of a series of posts for foundational spatial support in R. These posts include:</p>
<ul>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-wk/">wk</a></li>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-reproj/">reproj</a></li>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-proj-wk-transform/">PROJ/wk transform</a></li>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-gdalraster/">gdalraster</a> (this one!)</li>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-geos/">geos</a></li>
</ul>


</section>

 ]]></description>
  <category>r-packages</category>
  <category>gdal</category>
  <category>raster</category>
  <category>vector</category>
  <guid>https://hypertidy.org/posts/2026-03-24_check-out-gdalraster/</guid>
  <pubDate>Tue, 24 Mar 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Check it out: geos</title>
  <dc:creator>Michael Sumner</dc:creator>
  <link>https://hypertidy.org/posts/2026-03-24_check-out-geos/</link>
  <description><![CDATA[ 






<p>The <a href="https://paleolimbot.github.io/geos/">geos</a> package provides a direct R interface to the GEOS library. It operates on a <code>geos_geometry</code> vector type and speaks wk natively — any wk-handleable geometry can be passed in, and the results can flow back out to wk, sf, or anything else.</p>
<p>This is the geometry engine for the wk ecosystem. Where wk handles representation and PROJ handles transformation, geos handles <em>computation</em>: buffers, intersections, unions, predicates, distances, triangulations.</p>
<section id="geometry-vectors" class="level2">
<h2 class="anchored" data-anchor-id="geometry-vectors">Geometry vectors</h2>
<p>geos stores geometries in an external pointer vector class:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(geos)</span>
<span id="cb1-2"></span>
<span id="cb1-3">g <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_geos_geometry</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(</span>
<span id="cb1-4">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))"</span>,</span>
<span id="cb1-5">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"POINT (5 5)"</span>,</span>
<span id="cb1-6">  <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"LINESTRING (0 0, 10 10)"</span></span>
<span id="cb1-7">))</span>
<span id="cb1-8">g</span>
<span id="cb1-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; &lt;geos_geometry[3]&gt;</span></span>
<span id="cb1-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1] &lt;POLYGON [0 0...10 10]&gt;</span></span>
<span id="cb1-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [2] &lt;POINT [5 5]&gt;</span></span>
<span id="cb1-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [3] &lt;LINESTRING [0 0...10 10]&gt;</span></span></code></pre></div></div>
<p>The input can be WKT strings, WKB, wk vectors, or sf objects — anything with a <code>wk_handle()</code> method gets converted automatically:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## from wk::xy()</span></span>
<span id="cb2-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_geos_geometry</span>(wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">xy</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>))</span>
<span id="cb2-3"></span>
<span id="cb2-4"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## from sf</span></span>
<span id="cb2-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(sf)</span>
<span id="cb2-6">nc <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_read</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">system.file</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"shape/nc.shp"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">package =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sf"</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">quiet =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb2-7">nc_geos <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_geos_geometry</span>(nc)</span></code></pre></div></div>
</section>
<section id="operations" class="level2">
<h2 class="anchored" data-anchor-id="operations">Operations</h2>
<p>geos provides vectorized access to the GEOS C API. Operations are functions, not methods — they take geometry vectors and return geometry vectors or values:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## buffer</span></span>
<span id="cb3-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_buffer</span>(g[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">distance =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb3-3"></span>
<span id="cb3-4"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## intersection</span></span>
<span id="cb3-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_intersection</span>(g[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_buffer</span>(g[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>], <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>))</span>
<span id="cb3-6"></span>
<span id="cb3-7"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## union</span></span>
<span id="cb3-8"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_unary_union</span>(g)</span>
<span id="cb3-9"></span>
<span id="cb3-10"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## simplify</span></span>
<span id="cb3-11"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_simplify</span>(g[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">tolerance =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb3-12"></span>
<span id="cb3-13"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## centroid</span></span>
<span id="cb3-14"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_centroid</span>(g[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span></code></pre></div></div>
<p>Binary predicates are also vectorized:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_intersects</span>(g[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], g[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>])</span>
<span id="cb4-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1] TRUE</span></span>
<span id="cb4-3"></span>
<span id="cb4-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_contains</span>(g[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], g[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>])</span>
<span id="cb4-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1] TRUE</span></span>
<span id="cb4-6"></span>
<span id="cb4-7"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_disjoint</span>(g[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], g[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>])</span>
<span id="cb4-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1] FALSE</span></span></code></pre></div></div>
</section>
<section id="spatial-indexing" class="level2">
<h2 class="anchored" data-anchor-id="spatial-indexing">Spatial indexing</h2>
<p>geos includes an STRtree implementation for fast spatial queries:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## build a spatial index</span></span>
<span id="cb5-2">pts <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_geos_geometry</span>(wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">xy</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">runif</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10000</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">runif</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10000</span>)))</span>
<span id="cb5-3">tree <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_strtree</span>(pts)</span>
<span id="cb5-4"></span>
<span id="cb5-5"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## query the index</span></span>
<span id="cb5-6">query_geom <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_geos_geometry</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"POLYGON ((0.4 0.4, 0.6 0.4, 0.6 0.6, 0.4 0.6, 0.4 0.4))"</span>)</span>
<span id="cb5-7"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_strtree_query</span>(tree, query_geom)</span></code></pre></div></div>
<p>The newer <code>geos_basic_strtree()</code> uses <code>wk::wk_envelope()</code> for index construction, which is extremely efficient for <code>wk::xy()</code> input — the envelope is just the point itself, computed without any geometry parsing overhead.</p>
</section>
<section id="construction-from-coordinates" class="level2">
<h2 class="anchored" data-anchor-id="construction-from-coordinates">Construction from coordinates</h2>
<p>You don’t need to go through WKT to build geometries. geos provides direct constructors:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb6-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## points from x, y</span></span>
<span id="cb6-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_make_point</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)</span>
<span id="cb6-3"></span>
<span id="cb6-4"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## linestring from coordinates</span></span>
<span id="cb6-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_make_linestring</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb6-6"></span>
<span id="cb6-7"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## polygon from coordinates (ring is closed automatically)</span></span>
<span id="cb6-8"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_make_polygon</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>))</span>
<span id="cb6-9"></span>
<span id="cb6-10"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## collections</span></span>
<span id="cb6-11"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_make_collection</span>(</span>
<span id="cb6-12">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"POINT (0 0)"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"POINT (1 1)"</span>),</span>
<span id="cb6-13">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">type_id =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"multipoint"</span></span>
<span id="cb6-14">)</span></code></pre></div></div>
<p>These constructors use <code>feature_id</code> and <code>ring_id</code> arguments for building multi-part and multi-ring geometries from flat coordinate vectors, similar to how data often arrives from databases or CSV files.</p>
</section>
<section id="wk-compatibility" class="level2">
<h2 class="anchored" data-anchor-id="wk-compatibility">wk compatibility</h2>
<p>geos implements <code>wk_handle()</code> for its geometry type, so all wk operations work:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb7-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## extract coordinates</span></span>
<span id="cb7-2">wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_coords</span>(nc_geos)</span>
<span id="cb7-3"></span>
<span id="cb7-4"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## compute bounding boxes</span></span>
<span id="cb7-5">wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_envelope</span>(nc_geos)</span>
<span id="cb7-6"></span>
<span id="cb7-7"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## convert to wk types</span></span>
<span id="cb7-8">wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_wkt</span>(nc_geos)</span>
<span id="cb7-9">wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_wkb</span>(nc_geos)</span>
<span id="cb7-10"></span>
<span id="cb7-11"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## convert to sf</span></span>
<span id="cb7-12">sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_as_sfc</span>(nc_geos)</span></code></pre></div></div>
<p>This means geos integrates into any workflow that uses wk as the interchange format. Read vector data with gdalraster (WKB output), convert to geos for geometric operations, transform with PROJ, write back with gdalraster — all through wk.</p>
</section>
<section id="geos-vs-sf-for-geometric-operations" class="level2">
<h2 class="anchored" data-anchor-id="geos-vs-sf-for-geometric-operations">geos vs sf for geometric operations</h2>
<p>sf uses GEOS internally for its geometry operations (<code>st_buffer()</code>, <code>st_intersection()</code>, etc.). The difference is:</p>
<ul>
<li><strong>sf</strong> wraps GEOS through its own C++ layer, with results returned as sfc geometry columns inside sf data frames. The operations are tightly integrated with the sf data model.</li>
<li><strong>geos</strong> wraps GEOS directly, with results as standalone geometry vectors. No data frame, no attributes — just geometry in, geometry out.</li>
</ul>
<p>If you’re working within sf, use sf’s functions. If you’re working outside sf — with wk vectors, plain data frames, or formats that don’t map to sf’s model — geos gives you the same geometric operations without the sf world view.</p>
<p>Workflows can deliberately separate I/O (gdalraster/vapour), geometry (wk/geos), transformation (PROJ/reproj), and even grid logic (vaster), geos is the geometric computation layer.</p>
</section>
<section id="measurement-and-properties" class="level2">
<h2 class="anchored" data-anchor-id="measurement-and-properties">Measurement and properties</h2>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb8-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_area</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))"</span>)</span>
<span id="cb8-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1] 100</span></span>
<span id="cb8-3"></span>
<span id="cb8-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_length</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"LINESTRING (0 0, 10 0, 10 10)"</span>)</span>
<span id="cb8-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1] 20</span></span>
<span id="cb8-6"></span>
<span id="cb8-7"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_is_valid</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))"</span>)</span>
<span id="cb8-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1] TRUE</span></span>
<span id="cb8-9"></span>
<span id="cb8-10"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_is_empty</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"POINT EMPTY"</span>)</span>
<span id="cb8-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1] TRUE</span></span>
<span id="cb8-12"></span>
<span id="cb8-13"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geos_num_coordinates</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))"</span>)</span>
<span id="cb8-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1] 5</span></span></code></pre></div></div>
<p>Install from CRAN:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb9-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">install.packages</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"geos"</span>)</span></code></pre></div></div>
<p>Links:</p>
<ul>
<li><a href="https://CRAN.R-project.org/package=geos">geos on CRAN</a></li>
<li><a href="https://paleolimbot.github.io/geos/">geos documentation</a></li>
<li><a href="https://github.com/paleolimbot/geos">GitHub</a></li>
</ul>
</section>
<section id="check-it-out-series" class="level2">
<h2 class="anchored" data-anchor-id="check-it-out-series">Check it out series</h2>
<p>This is part of a series of posts for foundational spatial support in R. These posts include:</p>
<ul>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-wk/">wk</a></li>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-reproj/">reproj</a></li>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-proj-wk-transform/">PROJ/wk transform</a></li>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-gdalraster/">gdalraster</a></li>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-geos/">geos</a> (this one!)</li>
</ul>


</section>

 ]]></description>
  <category>r-packages</category>
  <category>vector</category>
  <guid>https://hypertidy.org/posts/2026-03-24_check-out-geos/</guid>
  <pubDate>Tue, 24 Mar 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Check it out: reproj</title>
  <dc:creator>Michael Sumner</dc:creator>
  <link>https://hypertidy.org/posts/2026-03-24_check-out-reproj/</link>
  <description><![CDATA[ 






<p>The <a href="https://hypertidy.github.io/reproj/">reproj</a> package provides a single generic function, <code>reproj()</code>, for transforming coordinates between map projections. It works with matrices, data frames, and anything else that a package author cares to add a method for.</p>
<p>Coordinate transformation is a basic operation on numbers, it shouldn’t require a specific spatial data format.</p>
<section id="the-generic" class="level2">
<h2 class="anchored" data-anchor-id="the-generic">The generic</h2>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(reproj)</span>
<span id="cb1-2"></span>
<span id="cb1-3"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## matrix in, matrix out</span></span>
<span id="cb1-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">reproj</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cbind</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">147</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">42</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">target =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=laea +datum=WGS84"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">source =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"OGC:CRS84"</span>)</span>
<span id="cb1-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt;          [,1]     [,2] [,3]</span></span>
<span id="cb1-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1,] 5969744 -9803200    0</span></span></code></pre></div></div>
<p>The <code>source</code> and <code>target</code> arguments accept anything that the PROJ library understands: EPSG codes, proj-strings, WKT2, authority:code strings. The output is always a matrix — 2, 3, or 4 columns depending on input and the <code>four</code> argument.</p>
<p>Convenience helpers <code>reproj_xy()</code> and <code>reproj_xyz()</code> force 2- or 3-column output:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">reproj_xy</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cbind</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">147</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">42</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">target =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=laea +datum=WGS84"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">source =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"OGC:CRS84"</span>)</span>
<span id="cb2-2"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt;          [,1]     [,2]</span></span>
<span id="cb2-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1,] 5969744 -9803200</span></span></code></pre></div></div>
</section>
<section id="the-generic-method-model" class="level2">
<h2 class="anchored" data-anchor-id="the-generic-method-model">The generic method model</h2>
<p><code>reproj()</code> is an S3 generic. This means any package can import it and add a method for its own spatial type. The method extracts the source CRS from the object and the user only needs to supply the target:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## hypothetical method for a spatial type that carries its own CRS</span></span>
<span id="cb3-2">reproj.my_spatial_thing <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(x, target, ...) {</span>
<span id="cb3-3">  coords <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">extract_coords</span>(x)</span>
<span id="cb3-4">  source <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">get_crs</span>(x)</span>
<span id="cb3-5">  result <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">reproj</span>(coords, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">target =</span> target, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">source =</span> source, ...)</span>
<span id="cb3-6">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rebuild_my_thing</span>(x, result, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> target)</span>
<span id="cb3-7">}</span></code></pre></div></div>
<p>This pattern is already used by the silicate and quadmesh packages — their <code>reproj()</code> methods know where the coordinates live and what the source CRS is, so the user just says <code>reproj(my_mesh, target_crs)</code>.</p>
<p>The value of this design is that the generic lives in a small, dependency-light package. A downstream package can <code>@importFrom reproj reproj</code> without pulling in GDAL or any heavy spatial stack. The transformation itself is done by the PROJ library via the <code>PROJ</code> R package (preferred) or the <code>proj4</code> package as fallback.</p>
<p>For example we could write a <code>reproj.sf</code> and a <code>.sfc</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1">reproj.sfc <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(x, target, ..., <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">source =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span>) {</span>
<span id="cb4-2">  <span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## handle this format with wk</span></span>
<span id="cb4-3">  handled <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_wkb</span>(x)</span>
<span id="cb4-4">  crs <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_crs</span>(handled)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>wkt</span>
<span id="cb4-5">  <span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## stop if not a valid crs, we need source specified</span></span>
<span id="cb4-6">  <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">is.null</span>(source)) {</span>
<span id="cb4-7">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">stopifnot</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">is.na</span>(crs))</span>
<span id="cb4-8">  } <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> {</span>
<span id="cb4-9">    crs <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> source</span>
<span id="cb4-10"> }</span>
<span id="cb4-11"> trans <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> PROJ<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">proj_trans_create</span>(crs, target)</span>
<span id="cb4-12"> sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_as_sfc</span>(wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_transform</span>(handled, trans))</span>
<span id="cb4-13">}</span>
<span id="cb4-14"></span>
<span id="cb4-15">reproj.sf <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(x, target, ..., <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">source =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">NULL</span>) {</span>
<span id="cb4-16"> sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_set_geometry</span>(x, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">reproj.sfc</span>(sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_geometry</span>(x), target, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">source =</span> source))</span>
<span id="cb4-17">}</span></code></pre></div></div>
<p>That’s not very useful since <code>sf::st_transform</code> already exists but demonstrates the utility for any other object, and you can force input of a source if your object does not support that already. We could decompose our object to coordinates and transform those more directly, but anything handled by wk is already supported by this simpler mechanism.</p>
</section>
<section id="crs-preservation" class="level2">
<h2 class="anchored" data-anchor-id="crs-preservation">CRS preservation</h2>
<p>A key principle: when reproj transforms an object that has a method, the <em>output carries the new CRS</em>. The silicate method rebuilds the mesh with the target CRS attached. The quadmesh method does the same. The idea is that format-specific methods preserve the full object structure while updating both coordinates and CRS metadata.</p>
<p>For plain matrices and data frames, there’s no CRS to carry, and the target and source must be specified.</p>
</section>
<section id="under-the-hood-proj-package" class="level2">
<h2 class="anchored" data-anchor-id="under-the-hood-proj-package">Under the hood: PROJ package</h2>
<p>reproj delegates the actual math to the <a href="https://hypertidy.github.io/PROJ/">PROJ</a> R package, which wraps the PROJ C library. The PROJ package provides:</p>
<ul>
<li><code>PROJ::proj_trans()</code> — transform coordinates, accepting wk types</li>
<li><code>PROJ::proj_trans_create()</code> — create a reusable transformation object (a <code>wk_trans</code>)</li>
<li><code>PROJ::proj_crs_text()</code> — convert between CRS representations (WKT2, proj-string, PROJJSON)</li>
</ul>
</section>
<section id="reproj-vs-sfst_transform-vs-terraproject" class="level2">
<h2 class="anchored" data-anchor-id="reproj-vs-sfst_transform-vs-terraproject">reproj vs sf::st_transform vs terra::project</h2>
<p>sf and terra both have projection functions, but they’re tied to their respective data models. <code>sf::st_transform()</code> works on sf objects. <code>terra::project()</code> works on SpatVector and SpatRaster.</p>
<p>reproj works on <em>anything</em>. It’s deliberately format-agnostic: give it a matrix of coordinates, a source CRS, and a target CRS. This makes it useful for packages that don’t depend on sf or terra, and for workflows where spatial data isn’t in a standard spatial format — track data as data frames, coordinates pulled from a netCDF, vertices from an rgl mesh, cell centres computed from a grid specification.</p>
</section>
<section id="example-transforming-track-data" class="level2">
<h2 class="anchored" data-anchor-id="example-transforming-track-data">Example: transforming track data</h2>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(reproj)</span>
<span id="cb5-2"></span>
<span id="cb5-3"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## some GPS fixes as a plain data frame</span></span>
<span id="cb5-4">tracks <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">data.frame</span>(</span>
<span id="cb5-5">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lon =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">147.3</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">147.4</span>, <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">147.5</span>),</span>
<span id="cb5-6">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lat =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">42.9</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">42.8</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">42.7</span>),</span>
<span id="cb5-7">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">time =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.POSIXct</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2024-01-01 08:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2024-01-01 09:00"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"2024-01-01 10:00"</span>))</span>
<span id="cb5-8">)</span>
<span id="cb5-9"></span>
<span id="cb5-10"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## transform to a local projection</span></span>
<span id="cb5-11">xy <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">reproj_xy</span>(</span>
<span id="cb5-12">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">cbind</span>(tracks<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>lon, tracks<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>lat),</span>
<span id="cb5-13">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">target =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"+proj=utm +zone=55 +south +datum=WGS84"</span>,</span>
<span id="cb5-14">  <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">source =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span></span>
<span id="cb5-15">)</span>
<span id="cb5-16"></span>
<span id="cb5-17">tracks<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>x <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> xy[, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb5-18">tracks<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>y <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> xy[, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span></code></pre></div></div>
<p>Install from CRAN:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb6-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">install.packages</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"reproj"</span>)</span></code></pre></div></div>
<p>Links:</p>
<ul>
<li><a href="https://CRAN.R-project.org/package=reproj">reproj on CRAN</a></li>
<li><a href="https://hypertidy.github.io/reproj/">reproj documentation</a></li>
<li><a href="https://CRAN.R-project.org/package=PROJ">PROJ on CRAN</a></li>
<li><a href="https://github.com/hypertidy/reproj">GitHub (reproj)</a></li>
<li><a href="https://github.com/hypertidy/PROJ">GitHub (PROJ)</a></li>
</ul>
</section>
<section id="check-it-out-series" class="level2">
<h2 class="anchored" data-anchor-id="check-it-out-series">Check it out series</h2>
<p>This is part of a series of posts for foundational spatial support in R. These posts include:</p>
<ul>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-wk/">wk</a></li>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-reproj/">reproj</a> (this one!)</li>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-proj-wk-transform/">PROJ/wk transform</a></li>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-gdalraster/">gdalraster</a></li>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-geos/">geos</a></li>
</ul>


</section>

 ]]></description>
  <category>r-packages</category>
  <category>projections</category>
  <guid>https://hypertidy.org/posts/2026-03-24_check-out-reproj/</guid>
  <pubDate>Tue, 24 Mar 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Check it out: wk</title>
  <dc:creator>Michael Sumner</dc:creator>
  <link>https://hypertidy.org/posts/2026-03-24_check-out-wk/</link>
  <description><![CDATA[ 






<p>The <a href="https://paleolimbot.github.io/wk/">wk</a> package is a foundation for geometry handling in R. It’s a lightweight, dependency-free geometry column with CRS support, vctrs integration, and a handler-based streaming API. Any data frame with a wk column works as well as the column itself with supported functions.</p>
<section id="what-wk-provides" class="level2">
<h2 class="anchored" data-anchor-id="what-wk-provides">What wk provides</h2>
<p>wk defines several vector types that carry geometry and coordinate reference system information as first-class attributes:</p>
<ul>
<li><code>wk::wkt()</code> — well-known text, human-readable</li>
<li><code>wk::wkb()</code> — well-known binary, compact and fast</li>
<li><code>wk::xy()</code>, <code>wk::xyz()</code>, <code>wk::xym()</code>, <code>wk::xyzm()</code> — coordinate vectors (points)</li>
<li><code>wk::rct()</code> — rectangles (bounding boxes)</li>
<li><code>wk::crc()</code> — circles</li>
</ul>
<p>All of these carry a <code>crs</code> attribute and behave properly as vctrs types. They can live in data frame columns, get subset, combined, and compared — with CRS compatibility checked automatically.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(wk)</span>
<span id="cb1-2">pts <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">xy</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">147</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">148</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">149</span>), <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">42</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">43</span>, <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">44</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>)</span>
<span id="cb1-3">pts</span>
<span id="cb1-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; &lt;wk_xy[3] with CRS=EPSG:4326&gt;</span></span>
<span id="cb1-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1] (147 -42) (148 -43) (149 -44)</span></span></code></pre></div></div>
<p>You can put these directly in a data frame:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1">d <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">data.frame</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">name =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Hobart"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"South"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Further"</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">geometry =</span> pts)</span>
<span id="cb2-2">d</span>
<span id="cb2-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt;     name     geometry</span></span>
<span id="cb2-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; 1 Hobart  (147 -42)</span></span>
<span id="cb2-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; 2  South  (148 -43)</span></span>
<span id="cb2-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; 3 Further (149 -44)</span></span></code></pre></div></div>
<p>Subset, combine, and merge all work correctly. The CRS propagates through operations and mismatches are caught:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## combining vectors with different CRS will error</span></span>
<span id="cb3-2">pts_other <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">xy</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:3857"</span>)</span>
<span id="cb3-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">try</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(pts, pts_other))</span>
<span id="cb3-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; Error: CRS objects are not equal</span></span></code></pre></div></div>
</section>
<section id="crs-handling" class="level2">
<h2 class="anchored" data-anchor-id="crs-handling">CRS handling</h2>
<p>The CRS system in wk is deliberately general. At the C level, the PROJ library <code>proj_create()</code> function accepts a remarkably wide range of CRS definition strings. The <a href="https://proj.org/en/stable/development/reference/functions.html#transformation-setup">PROJ C API documentation</a> lists many valid types of inputs from ‘auth:code’, PROJJSON, WKT variants and PROJ4 strings. The CRS support in wk is similarly open to inputs, the key functions in wk are:</p>
<ul>
<li><code>wk_crs()</code> — get the CRS from any wk-compatible object</li>
<li><code>wk_set_crs()</code> — set or replace the CRS</li>
<li><code>wk_crs_equal()</code> — compare CRS values</li>
<li><code>wk_crs_proj_definition()</code> — extract a PROJ-compatible string (used by PROJ and other packages)</li>
</ul>
<p>The sentinel value <code>wk_crs_inherit()</code> signals “inherit from context” and is used for empty or zero-length geometry vectors. This means you can safely combine an empty geometry with a CRS-bearing one without triggering a mismatch.</p>
</section>
<section id="interchange" class="level2">
<h2 class="anchored" data-anchor-id="interchange">Interchange</h2>
<p>wk acts as a common interchange format. Other formats that are supported are ‘raw binary’, ‘raw text’, geos package vectors, sf data frames and geometry vectors. The gdalraster package can emit vector geometry in wk format. Conversions to and from other formats are built in:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## sf -&gt; wk</span></span>
<span id="cb4-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(sf)</span>
<span id="cb4-3">nc <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_read</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">system.file</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"shape/nc.shp"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">package =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sf"</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">quiet =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb4-4">wkb <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> wk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_wkb</span>(nc)</span>
<span id="cb4-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_crs</span>(wkb)</span>
<span id="cb4-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">#&gt; [1] "NAD27"</span></span>
<span id="cb4-7"></span>
<span id="cb4-8"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## wk -&gt; sf</span></span>
<span id="cb4-9">sf<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">st_as_sfc</span>(wkb)</span>
<span id="cb4-10"></span>
<span id="cb4-11"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## terra -&gt; wk</span></span>
<span id="cb4-12">geos<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as_geos_geometry</span>(terra<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vect</span>(<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span>src<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span>))</span></code></pre></div></div>
<p>The geos package accepts wk types directly. The PROJ package transforms them. The gdalraster package (via <code>ogr_layer_create()</code> and friends) can write them. This is an interchange layer that allows packages to stay independent of formats, just as the underlying libraries GDAL, PROJ, GEOS already allow.</p>
</section>
<section id="the-handler-api" class="level2">
<h2 class="anchored" data-anchor-id="the-handler-api">The handler API</h2>
<p>The real power of wk is its streaming handler API. Rather than materializing geometry in memory, wk can stream coordinates through a chain of handlers — read, transform, and write without ever building an intermediate object.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb5-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## count coordinates without materializing geometry</span></span>
<span id="cb5-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_count</span>(wkb)</span>
<span id="cb5-3"></span>
<span id="cb5-4"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## extract coordinates as a data frame</span></span>
<span id="cb5-5"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_coords</span>(wkb)</span>
<span id="cb5-6"></span>
<span id="cb5-7"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## compute bounding boxes per feature</span></span>
<span id="cb5-8"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_envelope</span>(wkb)</span>
<span id="cb5-9"></span>
<span id="cb5-10"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## transform coordinates (see the PROJ post) https://www.hypertidy.org/posts/2026-03-24_check-out-proj-wk-transform/</span></span>
<span id="cb5-11"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">wk_transform</span>(pts, PROJ<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">proj_trans_create</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:3857"</span>))</span></code></pre></div></div>
<p>The <code>wk_handle()</code> generic is what makes this extensible. Any package can implement a <code>wk_handle()</code> method for its geometry type and immediately plug into the wk ecosystem — filters, transforms, writers all just work.</p>
</section>
<section id="geometric-operations-via-wk" class="level2">
<h2 class="anchored" data-anchor-id="geometric-operations-via-wk">Geometric operations via wk</h2>
<p>wk itself includes several useful operations that don’t require GEOS:</p>
<ul>
<li><code>wk_vertices()</code> — extract all vertices from a geometry</li>
<li><code>wk_coords()</code> / <code>wk_coords&lt;-()</code> — get/set coordinates</li>
<li><code>wk_flatten()</code> — flatten collections to simple geometries</li>
<li><code>wk_collection()</code> — collect simple geometries into multi/collection types</li>
<li><code>wk_linestring()</code> / <code>wk_polygon()</code> — construct from coordinates</li>
<li><code>wk_set_z()</code> / <code>wk_drop_z()</code> — add or remove Z dimension</li>
<li><code>wk_orient()</code> / <code>wk_clockwise()</code> / <code>wk_counterclockwise()</code> — rewind polygon rings</li>
</ul>
<p>For geometric predicates, buffering, intersection and the like try <a href="https://paleolimbot.github.io/geos/">geos</a> (see the <a href="https://www.hypertidy.org/posts/2026-03-24_check-out-geos/">geos</a> post in this series).</p>
<p>Install from CRAN:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb6-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">install.packages</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"wk"</span>)</span></code></pre></div></div>
<p>Links:</p>
<ul>
<li><a href="https://CRAN.R-project.org/package=wk">wk on CRAN</a></li>
<li><a href="https://paleolimbot.github.io/wk/">wk documentation</a></li>
<li><a href="https://github.com/paleolimbot/wk">GitHub</a></li>
</ul>
</section>
<section id="check-it-out-series" class="level2">
<h2 class="anchored" data-anchor-id="check-it-out-series">Check it out series</h2>
<p>This is part of a series of posts for foundational spatial support in R. These posts include:</p>
<ul>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-wk/">wk</a> (this one!)</li>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-reproj/">reproj</a></li>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-proj-wk-transform/">PROJ/wk transform</a></li>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-gdalraster/">gdalraster</a></li>
<li><a href="https://www.hypertidy.org/posts/2026-03-24_check-out-geos/">geos</a></li>
</ul>


</section>

 ]]></description>
  <category>r-packages</category>
  <category>vector</category>
  <guid>https://hypertidy.org/posts/2026-03-24_check-out-wk/</guid>
  <pubDate>Tue, 24 Mar 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>What’s on geodata.ucdavis.edu and how to access it</title>
  <dc:creator>Michael Sumner</dc:creator>
  <link>https://hypertidy.org/posts/2026-03-17_geodata-package-data/</link>
  <description><![CDATA[ 






<p>The R package <a href="https://CRAN.R-project.org/package=geodata">geodata</a> provides functions for downloading climate, elevation, soil, crop, species occurrence, and administrative boundary data. All of its raster and vector data is hosted on a single Apache web server at <code>geodata.ucdavis.edu</code>.</p>
<p>Every file on that server is directly accessible via HTTP. This means we can enumerate, inspect, and in some cases read them using GDAL’s virtual filesystem — without downloading anything first.</p>
<p>This post documents the full site structure, the file formats served, and which files can be streamed via <code>/vsicurl/</code> (they all can, but without efficient tiling it’s probably best to make local copies for large raster).</p>
<section id="helper-functions" class="level2">
<h2 class="anchored" data-anchor-id="helper-functions">Helper functions</h2>
<p>The code snippets below use a small set of helper functions that wrap <code>gdalraster::vsi_read_dir()</code> and related GDAL calls. Source them with:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">source</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"https://github.com/mdsumner/quarto-blog/releases/download/latest/geodata-ucdavis-tools.R"</span>)</span></code></pre></div></div>
</div>
<p>This provides: <code>vsi_list()</code>, <code>geodata_files()</code>, <code>gdalinfo()</code>, <code>check_block_size()</code>, and <code>streamability_report()</code>.</p>
</section>
<section id="site-structure" class="level2">
<h2 class="anchored" data-anchor-id="site-structure">Site structure</h2>
<p>The server has three top-level paths that contain spatial data:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 37%">
<col style="width: 62%">
</colgroup>
<thead>
<tr class="header">
<th style="text-align: left;">Path</th>
<th style="text-align: left;">Contents</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: left;"><code>/climate/</code></td>
<td style="text-align: left;">WorldClim 2.1 and 1.4, CMIP5 legacy data</td>
</tr>
<tr class="even">
<td style="text-align: left;"><code>/cmip6/</code></td>
<td style="text-align: left;">Downscaled CMIP6 projections</td>
</tr>
<tr class="odd">
<td style="text-align: left;"><code>/geodata/</code></td>
<td style="text-align: left;">Soil, landcover, crops, elevation, travel time, OSM, buildings</td>
</tr>
<tr class="even">
<td style="text-align: left;"><code>/gadm/</code></td>
<td style="text-align: left;">GADM administrative boundaries (versions 2.8, 3.6, 4.0, 4.1)</td>
</tr>
</tbody>
</table>
<p>The root URL itself and some intermediate directories serve an <code>index.html</code> that says “Hello World!” rather than an Apache directory listing. The data directories below these do have standard Apache autoindex enabled, which is what makes <code>vsi_read_dir()</code> work.</p>
</section>
<section id="enumerating-files" class="level2">
<h2 class="anchored" data-anchor-id="enumerating-files">Enumerating files</h2>
<p>GDAL’s <code>/vsicurl/</code> handler can parse Apache directory listings. The <code>gdalraster</code> package exposes this via <code>vsi_read_dir()</code>:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(gdalraster)</span></code></pre></div></div>
<div class="cell-output cell-output-stderr">
<pre><code>GDAL 3.13.0dev-a3b7b01d3e-dirty (released 2026-02-23), GEOS 3.12.1, PROJ 9.7.0</code></pre>
</div>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## flat directory — returns filenames</span></span>
<span id="cb4-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vsi_read_dir</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsicurl/https://geodata.ucdavis.edu/geodata/soil/soilgrids/"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code> [1] "agg"                           "bdod_0-5cm_mean_30s.tif"      
 [3] "bdod_15-30cm_mean_30s.tif"     "bdod_30-60cm_mean_30s.tif"    
 [5] "bdod_5-15cm_mean_30s.tif"      "cec_0-5cm_mean_30s.tif"       
 [7] "cec_15-30cm_mean_30s.tif"      "cec_30-60cm_mean_30s.tif"     
 [9] "cec_5-15cm_mean_30s.tif"       "cfvo_0-5cm_mean_30s.tif"      
[11] "cfvo_15-30cm_mean_30s.tif"     "cfvo_30-60cm_mean_30s.tif"    
[13] "cfvo_5-15cm_mean_30s.tif"      "clay_0-5cm_mean_30s.tif"      
[15] "clay_15-30cm_mean_30s.tif"     "clay_30-60cm_mean_30s.tif"    
[17] "clay_5-15cm_mean_30s.tif"      "files.txt"                    
[19] "fill"                          "nitrogen_0-5cm_mean_30s.tif"  
[21] "nitrogen_15-30cm_mean_30s.tif" "nitrogen_30-60cm_mean_30s.tif"
[23] "nitrogen_5-15cm_mean_30s.tif"  "ocd_0-5cm_mean_30s.tif"       
[25] "ocd_15-30cm_mean_30s.tif"      "ocd_30-60cm_mean_30s.tif"     
[27] "ocd_5-15cm_mean_30s.tif"       "phh2o_0-5cm_Q0.05_30s.tif"    
[29] "phh2o_0-5cm_Q0.95_30s.tif"     "phh2o_0-5cm_mean_30s.tif"     
[31] "phh2o_15-30cm_mean_30s.tif"    "phh2o_30-60cm_mean_30s.tif"   
[33] "phh2o_5-15cm_Q0.05_30s.tif"    "phh2o_5-15cm_Q0.95_30s.tif"   
[35] "phh2o_5-15cm_mean_30s.tif"     "raw"                          
[37] "sand_0-5cm_mean_30s.tif"       "sand_15-30cm_mean_30s.tif"    
[39] "sand_30-60cm_mean_30s.tif"     "sand_5-15cm_mean_30s.tif"     
[41] "silt_0-5cm_mean_30s.tif"       "silt_15-30cm_mean_30s.tif"    
[43] "silt_30-60cm_mean_30s.tif"     "silt_5-15cm_mean_30s.tif"     
[45] "soc_0-5cm_mean_30s.tif"        "soc_15-30cm_mean_30s.tif"     
[47] "soc_30-60cm_mean_30s.tif"      "soc_5-15cm_mean_30s.tif"      </code></pre>
</div>
</div>
<p>For structured enumeration with URLs and GDAL dataset names:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb6-1">soilgrids <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geodata_files</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"geodata/soil/soilgrids"</span>)</span>
<span id="cb6-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">head</span>(soilgrids)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>                                                                           url
1   https://geodata.ucdavis.edu/geodata/soil/soilgrids/bdod_0-5cm_mean_30s.tif
2 https://geodata.ucdavis.edu/geodata/soil/soilgrids/bdod_15-30cm_mean_30s.tif
3 https://geodata.ucdavis.edu/geodata/soil/soilgrids/bdod_30-60cm_mean_30s.tif
4  https://geodata.ucdavis.edu/geodata/soil/soilgrids/bdod_5-15cm_mean_30s.tif
5    https://geodata.ucdavis.edu/geodata/soil/soilgrids/cec_0-5cm_mean_30s.tif
6  https://geodata.ucdavis.edu/geodata/soil/soilgrids/cec_15-30cm_mean_30s.tif
                       file ext
1   bdod_0-5cm_mean_30s.tif tif
2 bdod_15-30cm_mean_30s.tif tif
3 bdod_30-60cm_mean_30s.tif tif
4  bdod_5-15cm_mean_30s.tif tif
5    cec_0-5cm_mean_30s.tif tif
6  cec_15-30cm_mean_30s.tif tif
                                                                                gdaldsn
1   /vsicurl/https://geodata.ucdavis.edu/geodata/soil/soilgrids/bdod_0-5cm_mean_30s.tif
2 /vsicurl/https://geodata.ucdavis.edu/geodata/soil/soilgrids/bdod_15-30cm_mean_30s.tif
3 /vsicurl/https://geodata.ucdavis.edu/geodata/soil/soilgrids/bdod_30-60cm_mean_30s.tif
4  /vsicurl/https://geodata.ucdavis.edu/geodata/soil/soilgrids/bdod_5-15cm_mean_30s.tif
5    /vsicurl/https://geodata.ucdavis.edu/geodata/soil/soilgrids/cec_0-5cm_mean_30s.tif
6  /vsicurl/https://geodata.ucdavis.edu/geodata/soil/soilgrids/cec_15-30cm_mean_30s.tif</code></pre>
</div>
</div>
<p>This returns a data.frame with columns <code>url</code>, <code>file</code>, <code>ext</code>, and <code>gdaldsn</code>. The <code>gdaldsn</code> column contains the <code>/vsicurl/</code> path for files GDAL can read, and <code>NA</code> for files it cannot (<code>.rds</code>, <code>.txt</code>, etc.).</p>
</section>
<section id="whats-there-a-complete-inventory" class="level2">
<h2 class="anchored" data-anchor-id="whats-there-a-complete-inventory">What’s there: a complete inventory</h2>
<section id="soilgrids-geodatasoilsoilgrids" class="level3">
<h3 class="anchored" data-anchor-id="soilgrids-geodatasoilsoilgrids">SoilGrids (<code>/geodata/soil/soilgrids/</code>)</h3>
<p>Global soil properties at 30 arc-second resolution. 48 TIF files covering 8 properties (bdod, cec, cfvo, clay, nitrogen, ocd, phh2o, sand, silt, soc) at 4 depth intervals, plus pH quantiles. Files range from 83 MB to 546 MB.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb8-1">soilgrids <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geodata_files</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"geodata/soil/soilgrids"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">pattern =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\\</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">.tif$"</span>)</span>
<span id="cb8-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nrow</span>(soilgrids)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] 44</code></pre>
</div>
</div>
<p><strong>Format</strong>: Single-band GeoTIFF, LZW compressed, <code>Block=43200x1</code> (row-striped). No overviews. No internal tiling.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb10-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">check_block_size</span>(soilgrids<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>gdaldsn[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>xblock yblock 
 43200      1 
attr(,"full_width")
[1] 43200
attr(,"is_row_striped")
[1] TRUE
attr(,"is_tiled")
[1] FALSE</code></pre>
</div>
</div>
<p><strong>Streamability</strong>: Row-striped layout means sub-region reads via <code>/vsicurl/</code> require decompressing entire rows spanning the full 43200 pixel width. Reading a small bounding box will transfer most of the file. These files are best downloaded locally.</p>
<p>The <code>geodata::soil_world_vsi()</code> function constructs <code>/vsicurl/</code> paths to these same files — the name suggests streaming is the intended use, but the row-striped layout makes it impractical for anything less than a large regional or global read.</p>
</section>
<section id="worldclim-2.1-tiles-climateworldclim2_1tilestile" class="level3">
<h3 class="anchored" data-anchor-id="worldclim-2.1-tiles-climateworldclim2_1tilestile">WorldClim 2.1 tiles (<code>/climate/worldclim/2_1/tiles/tile/</code>)</h3>
<p>30 arc-second resolution climate data, pre-tiled into 72 spatial tiles. 8 variables (bio, elev, prec, srad, tavg, tmax, tmin, wind). Bio tiles have 19 bands; monthly variables have 12 bands; elevation has 1. File sizes range from ~5 MB (ocean/ice tiles) to ~550 MB (land-dense tiles).</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb12-1">wc_tiles <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geodata_files</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"climate/worldclim/2_1/tiles/tile"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">pattern =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\\</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">.tif$"</span>)</span>
<span id="cb12-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nrow</span>(wc_tiles)  <span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## expect 576 = 72 tiles × 8 variables</span></span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] 576</code></pre>
</div>
</div>
<p><strong>Format</strong>: Multi-band GeoTIFF. Block layout should be checked:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb14-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">check_block_size</span>(wc_tiles<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>gdaldsn[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>xblock yblock 
  3600      1 
attr(,"full_width")
[1] 3600
attr(,"is_row_striped")
[1] TRUE
attr(,"is_tiled")
[1] FALSE</code></pre>
</div>
</div>
</section>
<section id="worldclim-2.1-global-climateworldclim2_1base" class="level3">
<h3 class="anchored" data-anchor-id="worldclim-2.1-global-climateworldclim2_1base">WorldClim 2.1 global (<code>/climate/worldclim/2_1/base/</code>)</h3>
<p>Global grids at 10m, 5m, 2.5m, and 30s resolution. All stored as <code>.zip</code> files containing one or more TIF files per variable.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 32%">
<col style="width: 29%">
<col style="width: 37%">
</colgroup>
<thead>
<tr class="header">
<th style="text-align: left;">Resolution</th>
<th style="text-align: left;">Variables</th>
<th style="text-align: left;">Example size</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: left;">10 minute</td>
<td style="text-align: left;">bio, elev, prec, srad, tavg, tmax, tmin, vapr, wind</td>
<td style="text-align: left;">1.3 MB – 48 MB</td>
</tr>
<tr class="even">
<td style="text-align: left;">5 minute</td>
<td style="text-align: left;">same</td>
<td style="text-align: left;">4.6 MB – 171 MB</td>
</tr>
<tr class="odd">
<td style="text-align: left;">2.5 minute</td>
<td style="text-align: left;">same</td>
<td style="text-align: left;">17 MB – 628 MB</td>
</tr>
<tr class="even">
<td style="text-align: left;">30 second</td>
<td style="text-align: left;">same</td>
<td style="text-align: left;">323 MB – 9.7 GB</td>
</tr>
</tbody>
</table>
<p>GDAL can read inside these zips via <code>/vsizip//vsicurl/</code>:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb16-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## example: 10-minute bioclim, first file inside the zip</span></span>
<span id="cb16-2">dsn <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsizip//vsicurl/https://geodata.ucdavis.edu/climate/worldclim/2_1/base/wc2.1_10m_bio.zip"</span></span>
<span id="cb16-3"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## list the contents of the zip</span></span>
<span id="cb16-4"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vsi_read_dir</span>(dsn)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code> [1] "wc2.1_10m_bio_1.tif"  "wc2.1_10m_bio_10.tif" "wc2.1_10m_bio_11.tif"
 [4] "wc2.1_10m_bio_12.tif" "wc2.1_10m_bio_13.tif" "wc2.1_10m_bio_14.tif"
 [7] "wc2.1_10m_bio_15.tif" "wc2.1_10m_bio_16.tif" "wc2.1_10m_bio_17.tif"
[10] "wc2.1_10m_bio_18.tif" "wc2.1_10m_bio_19.tif" "wc2.1_10m_bio_2.tif" 
[13] "wc2.1_10m_bio_3.tif"  "wc2.1_10m_bio_4.tif"  "wc2.1_10m_bio_5.tif" 
[16] "wc2.1_10m_bio_6.tif"  "wc2.1_10m_bio_7.tif"  "wc2.1_10m_bio_8.tif" 
[19] "wc2.1_10m_bio_9.tif" </code></pre>
</div>
</div>
<p>The internal TIF filenames follow the pattern <code>wc2.1_{res}_{var}_{number}.tif</code>.</p>
</section>
<section id="worldclim-2.1-country-extracts-climateworldclim2_1tilesiso" class="level3">
<h3 class="anchored" data-anchor-id="worldclim-2.1-country-extracts-climateworldclim2_1tilesiso">WorldClim 2.1 country extracts (<code>/climate/worldclim/2_1/tiles/iso/</code>)</h3>
<p>30 arc-second data clipped to individual countries, identified by ISO 3166-1 alpha-3 codes.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb18-1">iso <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geodata_files</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"climate/worldclim/2_1/tiles/iso"</span>)</span>
<span id="cb18-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">head</span>(iso)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>                                                                                 url
1  https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/iso/ABW_wc2.1_30s_bio.tif
2 https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/iso/ABW_wc2.1_30s_elev.tif
3 https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/iso/ABW_wc2.1_30s_prec.tif
4 https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/iso/ABW_wc2.1_30s_srad.tif
5 https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/iso/ABW_wc2.1_30s_tavg.tif
6 https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/iso/ABW_wc2.1_30s_tmax.tif
                    file ext
1  ABW_wc2.1_30s_bio.tif tif
2 ABW_wc2.1_30s_elev.tif tif
3 ABW_wc2.1_30s_prec.tif tif
4 ABW_wc2.1_30s_srad.tif tif
5 ABW_wc2.1_30s_tavg.tif tif
6 ABW_wc2.1_30s_tmax.tif tif
                                                                                      gdaldsn
1  /vsicurl/https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/iso/ABW_wc2.1_30s_bio.tif
2 /vsicurl/https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/iso/ABW_wc2.1_30s_elev.tif
3 /vsicurl/https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/iso/ABW_wc2.1_30s_prec.tif
4 /vsicurl/https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/iso/ABW_wc2.1_30s_srad.tif
5 /vsicurl/https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/iso/ABW_wc2.1_30s_tavg.tif
6 /vsicurl/https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/iso/ABW_wc2.1_30s_tmax.tif</code></pre>
</div>
</div>
</section>
<section id="worldclim-1.4-climateworldclim1_4" class="level3">
<h3 class="anchored" data-anchor-id="worldclim-1.4-climateworldclim1_4">WorldClim 1.4 (<code>/climate/worldclim/1_4/</code>)</h3>
<p>Legacy WorldClim version in BIL (Band Interleaved by Line) format inside zip files. Three subdirectories: <code>cur/</code> (current), <code>fut/</code> (future projections), <code>pst/</code> (past / LGM / mid-Holocene).</p>
<p>BIL format is GDAL-readable but, like the row-striped TIFs, does not support efficient random access.</p>
</section>
<section id="cmip6-projections-cmip6" class="level3">
<h3 class="anchored" data-anchor-id="cmip6-projections-cmip6">CMIP6 projections (<code>/cmip6/</code>)</h3>
<p>Downscaled and bias-corrected CMIP6 future climate data. Organised as:</p>
<pre><code>/cmip6/{resolution}/{model}/{ssp}/</code></pre>
<p>Resolutions: <code>30s</code>, <code>2.5m</code>, <code>5m</code>, <code>10m</code>. 23 GCMs. 4 SSPs (ssp126, ssp245, ssp370, ssp585). 4 time periods (2021-2040, 2041-2060, 2061-2080, 2081-2100). Variables: bioc, prec, tmax, tmin.</p>
<p>At 30s resolution these are bare TIF files, some exceeding 20 GB.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb21-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## list models at 2.5 minute resolution</span></span>
<span id="cb21-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vsi_read_dir</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsicurl/https://geodata.ucdavis.edu/cmip6/2.5m/"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code> [1] "ACCESS-CM2"       "ACCESS-ESM1-5"    "AWI-CM-1-1-MR"    "BCC-CSM2-MR"     
 [5] "CMCC-ESM2"        "CNRM-CM6-1"       "CNRM-CM6-1-HR"    "CNRM-ESM2-1"     
 [9] "CanESM5"          "CanESM5-CanOE"    "EC-Earth3-Veg"    "EC-Earth3-Veg-LR"
[13] "FIO-ESM-2-0"      "GFDL-ESM4"        "GISS-E2-1-G"      "GISS-E2-1-H"     
[17] "HadGEM3-GC31-LL"  "INM-CM4-8"        "INM-CM5-0"        "IPSL-CM6A-LR"    
[21] "MIROC-ES2L"       "MIROC6"           "MPI-ESM1-2-HR"    "MPI-ESM1-2-LR"   
[25] "MRI-ESM2-0"       "UKESM1-0-LL"     </code></pre>
</div>
</div>
<p>There is also a <code>/cmip6/tiles/</code> directory with tiled versions.</p>
</section>
<section id="cmip5-projections-climatecmip5" class="level3">
<h3 class="anchored" data-anchor-id="cmip5-projections-climatecmip5">CMIP5 projections (<code>/climate/cmip5/</code>)</h3>
<p>Legacy CMIP5 data at 30s, 2.5m, 5m, 10m resolutions. Separate directories for <code>lgm/</code> (Last Glacial Maximum) and <code>mid/</code> (mid-Holocene).</p>
</section>
<section id="elevation" class="level3">
<h3 class="anchored" data-anchor-id="elevation">Elevation</h3>
<p>TODO: locate and document. The <code>geodata</code> package provides <code>elevation_3s()</code>, <code>elevation_30s()</code>, and <code>elevation_global()</code> — the URLs for these need to be traced from the source code.</p>
</section>
<section id="landcover-geodatalandcover" class="level3">
<h3 class="anchored" data-anchor-id="landcover-geodatalandcover">Landcover (<code>/geodata/landcover/</code>)</h3>
<p>Two subdirectories:</p>
<ul>
<li><code>/geodata/landcover/30s/</code> — 30 arc-second aggregated landcover fractions</li>
<li><code>/geodata/landcover/3deg_cogs/</code> — ESA WorldCover 10m data as 3-degree tiles, labelled as COGs in the directory name</li>
</ul>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb23" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb23-1">lc_cogs <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geodata_files</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"geodata/landcover/3deg_cogs"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">pattern =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\\</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">.tif$"</span>)</span>
<span id="cb23-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nrow</span>(lc_cogs)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] 2651</code></pre>
</div>
</div>
<p>The <code>3deg_cogs</code> files follow the naming convention <code>ESA_WorldCover_10m_2020_v100_{lat}{lon}_Map.tif</code>. If these are genuine COGs (internally tiled with overviews), they would be efficiently streamable via <code>/vsicurl/</code>.</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb25" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb25-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## check whether these are really COGs</span></span>
<span id="cb25-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">check_block_size</span>(lc_cogs<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">$</span>gdaldsn[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>xblock yblock 
  1024   1024 
attr(,"full_width")
[1] 36000
attr(,"is_row_striped")
[1] FALSE
attr(,"is_tiled")
[1] TRUE</code></pre>
</div>
</div>
</section>
<section id="crops-geodatacrops" class="level3">
<h3 class="anchored" data-anchor-id="crops-geodatacrops">Crops (<code>/geodata/crops/</code>)</h3>
<p>Subdirectories for SPAM and Monfreda crop data, plus cropland density and crop calendars. File formats and structure need to be enumerated.</p>
</section>
<section id="gadm-administrative-boundaries-gadmgadm4.1" class="level3">
<h3 class="anchored" data-anchor-id="gadm-administrative-boundaries-gadmgadm4.1">GADM administrative boundaries (<code>/gadm/gadm4.1/</code>)</h3>
<p>Per-country boundaries in multiple formats:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 31%">
<col style="width: 18%">
<col style="width: 34%">
<col style="width: 15%">
</colgroup>
<thead>
<tr class="header">
<th style="text-align: left;">Subdirectory</th>
<th style="text-align: left;">Format</th>
<th style="text-align: left;">GDAL-readable</th>
<th style="text-align: left;">Notes</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: left;"><code>gpkg/</code></td>
<td style="text-align: left;">GeoPackage</td>
<td style="text-align: left;">Yes</td>
<td style="text-align: left;">SQLite-based, supports HTTP range requests</td>
</tr>
<tr class="even">
<td style="text-align: left;"><code>shp/</code></td>
<td style="text-align: left;">Shapefile (zipped)</td>
<td style="text-align: left;">Yes, via /vsizip/</td>
<td style="text-align: left;">Multiple component files</td>
</tr>
<tr class="odd">
<td style="text-align: left;"><code>json/</code></td>
<td style="text-align: left;">GeoJSON</td>
<td style="text-align: left;">Yes</td>
<td style="text-align: left;"></td>
</tr>
<tr class="even">
<td style="text-align: left;"><code>pck/</code></td>
<td style="text-align: left;">RDS (R serialization)</td>
<td style="text-align: left;">No</td>
<td style="text-align: left;">Used by <code>geodata::gadm()</code></td>
</tr>
<tr class="odd">
<td style="text-align: left;"><code>kmz/</code></td>
<td style="text-align: left;">KMZ (zipped KML)</td>
<td style="text-align: left;">Yes, via /vsizip/</td>
<td style="text-align: left;"></td>
</tr>
</tbody>
</table>
<p>Whole-dataset archives:</p>
<ul>
<li><code>gadm_410-gpkg.zip</code> — 1.4 GB, all countries in one GeoPackage</li>
<li><code>gadm_410-gdb.zip</code> — 913 MB, FileGDB format</li>
<li><code>gadm_410-levels.zip</code> — 2.5 GB, separated by admin level</li>
<li><code>gadm_410-raw.gpkg</code> — 2.6 GB, bare GeoPackage on HTTP</li>
</ul>
<p>The <code>gadm_410-raw.gpkg</code> is directly streamable:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb27" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb27-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## open GADM as a single GeoPackage over HTTP</span></span>
<span id="cb27-2">dsn <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsicurl/https://geodata.ucdavis.edu/gadm/gadm4.1/gadm_410-raw.gpkg"</span></span>
<span id="cb27-3"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## list layers</span></span>
<span id="cb27-4">gdalraster<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ogr_ds_layer_names</span>(dsn)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] "gadm41_raw"</code></pre>
</div>
</div>
<p>Per-country GeoPackage files are also streamable:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb29" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb29-1">dsn <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsicurl/https://geodata.ucdavis.edu/gadm/gadm4.1/gpkg/gadm41_AUS.gpkg"</span></span>
<span id="cb29-2">gdalraster<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ogr_ds_layer_names</span>(dsn)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>[1] "ADM_ADM_0" "ADM_ADM_1" "ADM_ADM_2"</code></pre>
</div>
</div>
</section>
<section id="other-datasets" class="level3">
<h3 class="anchored" data-anchor-id="other-datasets">Other datasets</h3>
<table class="caption-top table">
<colgroup>
<col style="width: 17%">
<col style="width: 29%">
<col style="width: 52%">
</colgroup>
<thead>
<tr class="header">
<th style="text-align: left;">Path</th>
<th style="text-align: left;">Contents</th>
<th style="text-align: left;">geodata function</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: left;"><code>/geodata/travel/</code></td>
<td style="text-align: left;">Travel time rasters</td>
<td style="text-align: left;"><code>travel_time()</code></td>
</tr>
<tr class="even">
<td style="text-align: left;"><code>/geodata/osm/</code></td>
<td style="text-align: left;">OpenStreetMap extracts (highways, places)</td>
<td style="text-align: left;"><code>osm()</code></td>
</tr>
<tr class="odd">
<td style="text-align: left;"><code>/geodata/buildings/</code></td>
<td style="text-align: left;">Building footprint data</td>
<td style="text-align: left;">—</td>
</tr>
</tbody>
</table>
<p>These need further enumeration.</p>
</section>
<section id="datasets-not-on-this-server" class="level3">
<h3 class="anchored" data-anchor-id="datasets-not-on-this-server">Datasets not on this server</h3>
<p>Some <code>geodata</code> functions fetch data from external APIs rather than <code>geodata.ucdavis.edu</code>:</p>
<ul>
<li><code>sp_occurrence()</code> — queries the GBIF API</li>
<li><code>bio_oracle()</code> — queries the Bio-Oracle server at <code>bio-oracle.org</code></li>
</ul>
</section>
</section>
<section id="block-layout-and-streamability" class="level2">
<h2 class="anchored" data-anchor-id="block-layout-and-streamability">Block layout and streamability</h2>
<p>The central question for remote access is whether a file’s internal layout supports efficient HTTP range requests. GDAL’s <code>/vsicurl/</code> handler issues byte-range requests to read specific portions of a file. This works well when the file is internally tiled (e.g., COG format with <code>Block=256x256</code>) but poorly when the file is row-striped (<code>Block=width×1</code>).</p>
<p>For a row-striped file, reading a small spatial window requires decompressing one or more full rows, each spanning the entire raster width. For a 43200-pixel-wide global grid, every row is a complete scan line from -180° to +180°. A single HTTP range request per row, times the number of rows in the window, with full LZW decompression of each row. For a small bounding box this is extremely wasteful.</p>
<section id="what-we-know" class="level3">
<h3 class="anchored" data-anchor-id="what-we-know">What we know</h3>
<table class="caption-top table">
<colgroup>
<col style="width: 30%">
<col style="width: 35%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th style="text-align: left;">Collection</th>
<th style="text-align: left;">Block layout</th>
<th style="text-align: left;">Streamable?</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: left;">SoilGrids TIFs</td>
<td style="text-align: left;">43200×1 (row-striped)</td>
<td style="text-align: left;">No — download</td>
</tr>
<tr class="even">
<td style="text-align: left;">WorldClim 2.1 tiles</td>
<td style="text-align: left;">TODO: check</td>
<td style="text-align: left;">Probably row-striped</td>
</tr>
<tr class="odd">
<td style="text-align: left;">WorldClim 2.1 global zips</td>
<td style="text-align: left;">N/A (zip)</td>
<td style="text-align: left;">Via /vsizip/, but still row-striped inside</td>
</tr>
<tr class="even">
<td style="text-align: left;">CMIP6 30s TIFs</td>
<td style="text-align: left;">TODO: check</td>
<td style="text-align: left;">Unknown</td>
</tr>
<tr class="odd">
<td style="text-align: left;">Landcover 3deg COGs</td>
<td style="text-align: left;">TODO: check</td>
<td style="text-align: left;">Likely yes (name says COGs)</td>
</tr>
<tr class="even">
<td style="text-align: left;">Landcover 30s</td>
<td style="text-align: left;">TODO: check</td>
<td style="text-align: left;">Unknown</td>
</tr>
<tr class="odd">
<td style="text-align: left;">GADM GeoPackage</td>
<td style="text-align: left;">N/A (vector)</td>
<td style="text-align: left;">Yes — SQLite random access works over HTTP</td>
</tr>
<tr class="even">
<td style="text-align: left;">GADM raw GeoPackage</td>
<td style="text-align: left;">N/A (vector)</td>
<td style="text-align: left;">Yes</td>
</tr>
</tbody>
</table>
</section>
<section id="how-to-check" class="level3">
<h3 class="anchored" data-anchor-id="how-to-check">How to check</h3>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb31" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb31-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## for any TIF on the server</span></span>
<span id="cb31-2">bs <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">check_block_size</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsicurl/https://geodata.ucdavis.edu/path/to/file.tif"</span>)</span>
<span id="cb31-3">bs</span>
<span id="cb31-4"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## if is_row_striped is TRUE: download, don't stream</span></span>
<span id="cb31-5"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## if is_tiled is TRUE: streaming is efficient</span></span></code></pre></div></div>
</div>
</section>
</section>
<section id="generating-file-tables" class="level2">
<h2 class="anchored" data-anchor-id="generating-file-tables">Generating file tables</h2>
<p>For any collection, generate a complete table of URLs and GDAL dataset names:</p>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb32" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb32-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## SoilGrids: all TIFs</span></span>
<span id="cb32-2">sg <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geodata_files</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"geodata/soil/soilgrids"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">pattern =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\\</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">.tif$"</span>)</span>
<span id="cb32-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">head</span>(sg[, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"url"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gdaldsn"</span>)])</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>                                                                           url
1   https://geodata.ucdavis.edu/geodata/soil/soilgrids/bdod_0-5cm_mean_30s.tif
2 https://geodata.ucdavis.edu/geodata/soil/soilgrids/bdod_15-30cm_mean_30s.tif
3 https://geodata.ucdavis.edu/geodata/soil/soilgrids/bdod_30-60cm_mean_30s.tif
4  https://geodata.ucdavis.edu/geodata/soil/soilgrids/bdod_5-15cm_mean_30s.tif
5    https://geodata.ucdavis.edu/geodata/soil/soilgrids/cec_0-5cm_mean_30s.tif
6  https://geodata.ucdavis.edu/geodata/soil/soilgrids/cec_15-30cm_mean_30s.tif
                                                                                gdaldsn
1   /vsicurl/https://geodata.ucdavis.edu/geodata/soil/soilgrids/bdod_0-5cm_mean_30s.tif
2 /vsicurl/https://geodata.ucdavis.edu/geodata/soil/soilgrids/bdod_15-30cm_mean_30s.tif
3 /vsicurl/https://geodata.ucdavis.edu/geodata/soil/soilgrids/bdod_30-60cm_mean_30s.tif
4  /vsicurl/https://geodata.ucdavis.edu/geodata/soil/soilgrids/bdod_5-15cm_mean_30s.tif
5    /vsicurl/https://geodata.ucdavis.edu/geodata/soil/soilgrids/cec_0-5cm_mean_30s.tif
6  /vsicurl/https://geodata.ucdavis.edu/geodata/soil/soilgrids/cec_15-30cm_mean_30s.tif</code></pre>
</div>
</div>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb34" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb34-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## WorldClim tiles: all TIFs</span></span>
<span id="cb34-2">wc <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geodata_files</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"climate/worldclim/2_1/tiles/tile"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">pattern =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\\</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">.tif$"</span>)</span>
<span id="cb34-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">head</span>(wc[, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"url"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gdaldsn"</span>)])</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>                                                                                      url
1  https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/tile/tile_10_wc2.1_30s_bio.tif
2 https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/tile/tile_10_wc2.1_30s_elev.tif
3 https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/tile/tile_10_wc2.1_30s_prec.tif
4 https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/tile/tile_10_wc2.1_30s_srad.tif
5 https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/tile/tile_10_wc2.1_30s_tavg.tif
6 https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/tile/tile_10_wc2.1_30s_tmax.tif
                                                                                           gdaldsn
1  /vsicurl/https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/tile/tile_10_wc2.1_30s_bio.tif
2 /vsicurl/https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/tile/tile_10_wc2.1_30s_elev.tif
3 /vsicurl/https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/tile/tile_10_wc2.1_30s_prec.tif
4 /vsicurl/https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/tile/tile_10_wc2.1_30s_srad.tif
5 /vsicurl/https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/tile/tile_10_wc2.1_30s_tavg.tif
6 /vsicurl/https://geodata.ucdavis.edu/climate/worldclim/2_1/tiles/tile/tile_10_wc2.1_30s_tmax.tif</code></pre>
</div>
</div>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb36" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb36-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## GADM 4.1 GeoPackages</span></span>
<span id="cb36-2">gadm <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">geodata_files</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gadm/gadm4.1/gpkg"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">pattern =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">\\</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">.gpkg$"</span>)</span>
<span id="cb36-3"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">head</span>(gadm[, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"url"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gdaldsn"</span>)])</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>                                                            url
1 https://geodata.ucdavis.edu/gadm/gadm4.1/gpkg/gadm41_ABW.gpkg
2 https://geodata.ucdavis.edu/gadm/gadm4.1/gpkg/gadm41_AFG.gpkg
3 https://geodata.ucdavis.edu/gadm/gadm4.1/gpkg/gadm41_AGO.gpkg
4 https://geodata.ucdavis.edu/gadm/gadm4.1/gpkg/gadm41_AIA.gpkg
5 https://geodata.ucdavis.edu/gadm/gadm4.1/gpkg/gadm41_ALA.gpkg
6 https://geodata.ucdavis.edu/gadm/gadm4.1/gpkg/gadm41_ALB.gpkg
                                                                 gdaldsn
1 /vsicurl/https://geodata.ucdavis.edu/gadm/gadm4.1/gpkg/gadm41_ABW.gpkg
2 /vsicurl/https://geodata.ucdavis.edu/gadm/gadm4.1/gpkg/gadm41_AFG.gpkg
3 /vsicurl/https://geodata.ucdavis.edu/gadm/gadm4.1/gpkg/gadm41_AGO.gpkg
4 /vsicurl/https://geodata.ucdavis.edu/gadm/gadm4.1/gpkg/gadm41_AIA.gpkg
5 /vsicurl/https://geodata.ucdavis.edu/gadm/gadm4.1/gpkg/gadm41_ALA.gpkg
6 /vsicurl/https://geodata.ucdavis.edu/gadm/gadm4.1/gpkg/gadm41_ALB.gpkg</code></pre>
</div>
</div>
<div class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb38" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb38-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## CMIP6 models at 2.5 minute resolution</span></span>
<span id="cb38-2"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## first list the model directories, then enumerate within each</span></span>
<span id="cb38-3">models <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vsi_read_dir</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsicurl/https://geodata.ucdavis.edu/cmip6/2.5m/"</span>)</span>
<span id="cb38-4">models</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code> [1] "ACCESS-CM2"       "ACCESS-ESM1-5"    "AWI-CM-1-1-MR"    "BCC-CSM2-MR"     
 [5] "CMCC-ESM2"        "CNRM-CM6-1"       "CNRM-CM6-1-HR"    "CNRM-ESM2-1"     
 [9] "CanESM5"          "CanESM5-CanOE"    "EC-Earth3-Veg"    "EC-Earth3-Veg-LR"
[13] "FIO-ESM-2-0"      "GFDL-ESM4"        "GISS-E2-1-G"      "GISS-E2-1-H"     
[17] "HadGEM3-GC31-LL"  "INM-CM4-8"        "INM-CM5-0"        "IPSL-CM6A-LR"    
[21] "MIROC-ES2L"       "MIROC6"           "MPI-ESM1-2-HR"    "MPI-ESM1-2-LR"   
[25] "MRI-ESM2-0"       "UKESM1-0-LL"     </code></pre>
</div>
</div>
</section>
<section id="still-to-document" class="level2">
<h2 class="anchored" data-anchor-id="still-to-document">Still to document</h2>
<p>Several areas need further investigation:</p>
<ul>
<li>Elevation data paths (<code>elevation_3s</code>, <code>elevation_30s</code>, <code>elevation_global</code>)</li>
<li>Exact contents of <code>/geodata/crops/</code>, <code>/geodata/travel/</code>, <code>/geodata/osm/</code>, <code>/geodata/buildings/</code></li>
<li>Block layout of WorldClim tiles, CMIP6 30s TIFs, and landcover COGs</li>
<li>Contents of <code>/geodata/landcover/30s/</code></li>
<li>Contents of <code>/geodata/soil/soilgrids/agg/</code>, <code>fill/</code>, and <code>raw/</code> subdirectories</li>
<li>The <code>/cmip6/tiles/</code> tiled variant structure</li>
</ul>


</section>

 ]]></description>
  <category>code</category>
  <category>news</category>
  <category>raster</category>
  <category>vector</category>
  <category>gdal</category>
  <category>spatial</category>
  <guid>https://hypertidy.org/posts/2026-03-17_geodata-package-data/</guid>
  <pubDate>Tue, 17 Mar 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Map Reprojection and Map Edges</title>
  <dc:creator>Michael Sumner</dc:creator>
  <link>https://hypertidy.org/posts/2026-02-20_map-extent-projection/</link>
  <description><![CDATA[ 






<section id="map-reprojection" class="level1">
<h1>Map Reprojection</h1>
<p>A recent <a href="https://github.com/r-tmap/tmap/issues/1136">issue on the tmap package</a> reports a white patch appearing at the top of a map rendered in an Albers Equal Area projection. The basemap tiles don’t fill the whole plot area. It’s tempting to call this a tmap bug, but the underlying problem is more general. It affects any tool that needs to compute the geographic footprint of a projected extent: tile-fetching packages, GDAL utilities, raster warping workflows, and vector reprojection. An almost identical issue was <a href="https://github.com/OSGeo/gdal/issues/12100">reported against GDAL’s gdal_translate</a> and <a href="https://gdal.org/en/stable/programs/gdal_translate.html">fixed in GDAL 3.11</a> by densifying the <code>projwin</code> boundary instead of transforming only corner points.</p>
<p>The core question is: when you have a rectangular region in a map, what area does it cover in another map projection? If you only transform the four corners, you get a quadrilateral. But the actual footprint is a curved shape, because straight lines in the projected CRS trace curves through geographic space. For conic projections like Albers, the top edge of the rectangle sweeps a wider arc in longitude than the corners alone suggest. The difference between the four-corner bounding box and the true footprint is the missing area in the map.</p>
<section id="a-picture-of-the-problem" class="level2">
<h2 class="anchored" data-anchor-id="a-picture-of-the-problem">A picture of the problem</h2>
<p>The fix is to <em>densify</em> the boundary of the projected rectangle before transforming it. Add intermediate vertices along each edge, then reproject the resulting polygon, and compute the bounding box of that. The more vertices you add, the better the approximation.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(terra)</span>
<span id="cb1-2">vdsn <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsizip//vsicurl/https://github.com/user-attachments/files/20692539/AEA_polygon.zip"</span></span>
<span id="cb1-3">v <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vect</span>(vdsn)</span>
<span id="cb1-4"></span>
<span id="cb1-5"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## The extent as a polygon in projected space</span></span>
<span id="cb1-6">box_proj <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.polygons</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ext</span>(v))</span>
<span id="cb1-7"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">crs</span>(box_proj) <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">crs</span>(v)</span>
<span id="cb1-8"></span>
<span id="cb1-9"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## Transform with sparse corners only</span></span>
<span id="cb1-10">box_ll_sparse <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">project</span>(box_proj, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>)</span>
<span id="cb1-11"></span>
<span id="cb1-12"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## Densify first (add vertices every 10 km), then transform</span></span>
<span id="cb1-13">box_dense <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">densify</span>(box_proj, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">interval =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10000</span>)</span>
<span id="cb1-14">box_ll_dense <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">project</span>(box_dense, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:4326"</span>)</span>
<span id="cb1-15"></span>
<span id="cb1-16"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## Compare the extents</span></span>
<span id="cb1-17"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ext</span>(box_ll_sparse)</span>
<span id="cb1-18"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ext</span>(box_ll_dense)</span>
<span id="cb1-19"></span>
<span id="cb1-20"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## Plot them together</span></span>
<span id="cb1-21"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(box_ll_dense, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">border =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"blue"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lwd =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">main =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Sparse corners vs. densified boundary"</span>)</span>
<span id="cb1-22"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(box_ll_sparse, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">border =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"red"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lwd =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">add =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb1-23"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">legend</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"bottomright"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">legend =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Densified"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Sparse (4 corners)"</span>),</span>
<span id="cb1-24">       <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">col =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"blue"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"red"</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">lwd =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span></code></pre></div></div>
<p>The gap between the red quadrilateral and the blue curve is exactly the area that goes missing when software transforms only the corner points.</p>
</section>
<section id="how-this-relates-to-map-tiles" class="level2">
<h2 class="anchored" data-anchor-id="how-this-relates-to-map-tiles">How this relates to map tiles</h2>
<p>Web map tile services (TMS, WMTS, XYZ) serve imagery in tiles at fixed zoom levels, typically in Web Mercator (EPSG:3857). Packages like <a href="https://github.com/riatelab/maptiles">maptiles</a>, <a href="https://github.com/hypertidy/ceramic">ceramic</a>, and <a href="https://github.com/paleolimbot/rosm">rosm</a> do an excellent job of fetching, compositing, and caching these tiles for use in R. Their workflow is roughly: determine the geographic extent needed, request the right tiles at the right zoom level, stitch them, and return a raster in the desired CRS.</p>
<p>The extent determination step is where the densification problem bites. If the user’s map is in a conic projection and the package computes the longlat extent by transforming only the viewport corners, the requested tiles won’t cover the full area.</p>
<p>There is an alternative approach that sidesteps tile-fetching entirely, by treating the tile server as a GDAL data source and using <code>gdalwarp</code> to produce an image directly on a target grid. GDAL’s warper handles extent computation internally via <code>GDALSuggestedWarpOutput()</code>, which densifies the source boundary as part of its calculation. This means the “white patch” problem doesn’t occur.</p>
</section>
<section id="using-gdal-warp-with-a-tile-server" class="level2">
<h2 class="anchored" data-anchor-id="using-gdal-warp-with-a-tile-server">Using GDAL warp with a tile server</h2>
<p>A TMS or WMTS endpoint can be opened by GDAL as a raster data source, given the right connection string. The <code>esri()</code> helper function below constructs a WMTS connection string from an ArcGIS REST service URL:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb2-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## Helper functions:</span></span>
<span id="cb2-2"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## esri() builds a WMTS connection string from an ArcGIS REST Services URL</span></span>
<span id="cb2-3"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## fit_dims() computes aspect-ratio-preserving pixel dimensions</span></span>
<span id="cb2-4"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## (fit_dims is available in the hypertidy/vaster package)</span></span>
<span id="cb2-5"></span>
<span id="cb2-6">esri <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(url) {</span>
<span id="cb2-7">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sprintf</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"WMTS:%s/WMTS/1.0.0/WMTSCapabilities.xml"</span>, url)</span>
<span id="cb2-8">}</span>
<span id="cb2-9"></span>
<span id="cb2-10"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## fit_dims is also available from vaster::fit_dims()</span></span>
<span id="cb2-11">fit_dims <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">size =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1024</span>L, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">wh =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(size, size)) {</span>
<span id="cb2-12">  wh <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rep</span>(wh, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">length.out =</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>L)</span>
<span id="cb2-13">  w <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> wh[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L]; h <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> wh[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>L]</span>
<span id="cb2-14">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.integer</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ceiling</span>(size <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(w, h) <span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">max</span>(w, h)))</span>
<span id="cb2-15">}</span></code></pre></div></div>
<p>With these in hand, the workflow is:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">library</span>(terra)</span>
<span id="cb3-2"></span>
<span id="cb3-3">dsn <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">esri</span>(<span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">url =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"https://services.arcgisonline.com/arcgis/rest/services/Ocean/World_Ocean_Base/MapServer"</span>)</span>
<span id="cb3-4">vdsn <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/vsizip//vsicurl/https://github.com/user-attachments/files/20692539/AEA_polygon.zip"</span></span>
<span id="cb3-5"></span>
<span id="cb3-6">v <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">vect</span>(vdsn)</span>
<span id="cb3-7"></span>
<span id="cb3-8"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## Compute pixel dimensions that preserve the aspect ratio of the extent</span></span>
<span id="cb3-9">dm <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">fit_dims</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1024</span>, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">diff</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.vector</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ext</span>(v)))[<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">c</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>L)])</span>
<span id="cb3-10"></span>
<span id="cb3-11">source <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rast</span>(dsn)</span>
<span id="cb3-12"></span>
<span id="cb3-13"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## Build the target grid: same CRS and extent as the input vector data,</span></span>
<span id="cb3-14"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## with the computed dimensions, and matching number of layers (RGB)</span></span>
<span id="cb3-15">target <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rast</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ext</span>(v), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ncols =</span> dm[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">nrows =</span> dm[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>L],</span>
<span id="cb3-16">               <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">crs</span>(v), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">nlyr =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nlyr</span>(source))</span>
<span id="cb3-17"></span>
<span id="cb3-18"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## Warp the tile server imagery onto the target grid</span></span>
<span id="cb3-19"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## by_util = TRUE calls gdalwarp rather than terra's internal resampler</span></span>
<span id="cb3-20">im <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">project</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rast</span>(dsn), target, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">by_util =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb3-21"></span>
<span id="cb3-22"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plotRGB</span>(im)</span>
<span id="cb3-23"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(v, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">add =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span></code></pre></div></div>
<p>The <code>project()</code> call with <code>by_util = TRUE</code> invokes <code>gdalwarp</code> under the hood, which reads tiles from the server as needed to fill the target grid. GDAL computes the correct source extent internally, including the densification step.</p>
<p>A few details worth noting about this code:</p>
<ul>
<li>The <code>nlyr = nlyr(source)</code> argument on the target raster is necessary so that all bands (RGB) are included. Without it, the target would default to a single band.</li>
<li>The <code>by_util = TRUE</code> flag is essential. Without it, <code>project()</code> uses terra’s internal logic driving a lower level part of the GDAL api, which does not automatically target the appropriate resolution level (it will take forever to process pixels that are not needed).</li>
<li>The <code>dsn</code> variable is a WMTS connection string that GDAL opens as a raster source. The same approach works with TMS (XYZ) endpoints, though the connection string format is different (it requires a small XML file or an inline XML specification; a good reference is the <a href="https://gdal.org/en/stable/drivers/raster/wms.html">GDAL TMS driver documentation</a>).</li>
</ul>
</section>
<section id="reprojecting-the-target-grid-definition" class="level2">
<h2 class="anchored" data-anchor-id="reprojecting-the-target-grid-definition">Reprojecting the target grid definition</h2>
<p>If the desired output CRS is different from the input vector CRS, the key insight is to reproject the <em>target grid definition</em>, not the image data. Build the target grid in the source CRS first (where the extent is known), then reproject the empty grid to the desired output CRS. GDAL’s warp step will then fill it correctly.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## Build the target in the vector's native CRS</span></span>
<span id="cb4-2">target0 <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rast</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ext</span>(v), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">ncols =</span> dm[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>L], <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">nrows =</span> dm[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>L],</span>
<span id="cb4-3">                <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">crs =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">crs</span>(v), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">nlyr =</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">nlyr</span>(source))</span>
<span id="cb4-4"></span>
<span id="cb4-5"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## Reproject the empty grid definition to Web Mercator</span></span>
<span id="cb4-6">target <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">project</span>(target0, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:3857"</span>)</span>
<span id="cb4-7"></span>
<span id="cb4-8"><span class="do" style="color: #5E5E5E;
background-color: null;
font-style: italic;">## Warp the source imagery onto the reprojected target</span></span>
<span id="cb4-9">im <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">project</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">rast</span>(dsn), target, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">by_util =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span>
<span id="cb4-10"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plotRGB</span>(im)</span>
<span id="cb4-11"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">plot</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">project</span>(v, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"EPSG:3857"</span>), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">add =</span> <span class="cn" style="color: #8f5902;
background-color: null;
font-style: inherit;">TRUE</span>)</span></code></pre></div></div>
<p>Reprojecting an empty raster grid with <code>project()</code> (or equivalently, <code>gdalwarp</code> on a small raster) computes the appropriate output extent and resolution by densifying the source boundary before transforming. This is a lightweight way to answer the question “what does this extent look like in another CRS?”</p>
</section>
<section id="extent-reprojection-as-a-general-operation" class="level2">
<h2 class="anchored" data-anchor-id="extent-reprojection-as-a-general-operation">Extent reprojection as a general operation</h2>
<p>The task of computing a bounding box in one CRS from a bounding box in another CRS appears in many contexts. Several tools address it:</p>
<ul>
<li><p><code>reproj::reproj_extent()</code> in the <a href="https://hypertidy.github.io/reproj/">reproj</a> package does this directly. It densifies the input extent boundary, transforms the points, and returns the bounding box. The documentation describes it as a simple version of what GDAL’s <code>GDALSuggestedWarpOutput()</code> does.</p></li>
<li><p><code>terra::project()</code> applied to a <code>SpatExtent</code> or an empty <code>SpatRaster</code> achieves the same result, using GDAL internally.</p></li>
<li><p>In sf and stars, <code>sf::st_transform()</code> on an <code>sfc_POLYGON</code> representing the bounding box will reproject the boundary (sf applies segmentization as part of its transformation pipeline when working with s2). <code>st_bbox()</code> of the result gives the target extent.</p></li>
<li><p>In gdalraster, <code>gdalraster::warp()</code> with appropriate <code>-te</code> and <code>-te_srs</code> arguments lets you specify the target extent in a different CRS from the output. The <code>-te_srs</code> option tells <code>gdalwarp</code> to interpret the <code>-te</code> coordinates in the given CRS and transform them (with densification) to the output CRS.</p></li>
<li><p>Similarly, <code>gdal_translate</code> has <code>-projwin_srs</code>, which specifies the CRS of the <code>-projwin</code> coordinates. As of <a href="https://gdal.org/en/stable/programs/gdal_translate.html">GDAL 3.11</a>, the projwin boundary is fully densified before transformation. In earlier versions, only the two corner points were transformed, leading to the same class of error described here.</p></li>
</ul>
<p>All of these ultimately rely on the same principle: you cannot determine the geographic footprint of a projected rectangle from its corners alone. The boundary must be sampled densely enough to capture the curvature of the transformation.</p>
</section>
<section id="the-vector-side-densifying-features" class="level2">
<h2 class="anchored" data-anchor-id="the-vector-side-densifying-features">The vector side: densifying features</h2>
<p>The same issue applies to vector geometries. A polygon or line with sparse vertices in one CRS will have incorrect geometry after reprojection if the segments are long relative to the curvature of the transformation. This is not about datum shifts or projection accuracy at individual points — point reprojection is exact. It is about the fact that a straight line segment in one CRS is not, in general, a straight line in another.</p>
<p>Consider a coastline polygon with vertices every 100 km, stored in an Albers Equal Area CRS. Those long straight edges are reasonable approximations of the coastline <em>in that projection</em>. But when reprojected to longitude/latitude, each straight edge should trace a curve through geographic space. Without densification, the reprojected polygon connects the transformed endpoints with straight lines in the target CRS, which follow different paths than the original segments.</p>
<p><code>sf::st_segmentize()</code> and <code>terra::densify()</code> add intermediate vertices along existing edges. The critical point is that this must happen <em>before</em> reprojection, in the source CRS, so that the added vertices capture the geometry as it was defined. After reprojection, the denser vertex set produces a polygon that faithfully represents the original shape in the new CRS.</p>
<p>Neither sf nor terra applies densification automatically during reprojection. It is the caller’s responsibility to decide whether it is needed and to choose an appropriate segment length. For small-area, high-resolution data the effect is negligible, but for continental extents or coarse geometries it can be significant.</p>
</section>
<section id="summary" class="level2">
<h2 class="anchored" data-anchor-id="summary">Summary</h2>
<p>The white patch in the tmap basemap, the missing data in <code>gdal_translate -projwin_srs</code> before GDAL 3.11, and the distortion of coarse polygons after reprojection are all the same problem. A rectangular extent in a projected CRS is not a rectangle in geographic coordinates. The boundary curves, and capturing that curvature requires sampling it with enough points.</p>
<p>This applies whenever software needs to answer the question “what geographic area does this projected region cover?” The answer is to densify before transforming — whether the input is a viewport boundary for fetching tiles, a bounding box for subsetting, or the edges of a polygon for display.</p>
<p>The GDAL warp approach to tile servers avoids the problem for raster imagery by letting GDAL handle extent computation internally. For vector data, <code>st_segmentize()</code> or <code>densify()</code> should be part of any reprojection workflow involving coarse geometries.</p>
<p>Code and helper functions used in this post are available at <a href="https://gist.github.com/mdsumner/264b80cae0bb1e72d93b22b6edd19a27">this gist</a>.</p>
<hr>
<p><em>Possible future additions: equivalent workflows using sf::gdal_utils(), gdalraster::warp(), and vapour::vapour_warp_raster() to show the same operations without terra.</em></p>


</section>
</section>

 ]]></description>
  <category>code</category>
  <category>projections</category>
  <category>maps</category>
  <category>spatial</category>
  <guid>https://hypertidy.org/posts/2026-02-20_map-extent-projection/</guid>
  <pubDate>Fri, 20 Feb 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Detecting hidden grids in curvilinear coordinates</title>
  <dc:creator>Michael Sumner</dc:creator>
  <link>https://hypertidy.org/posts/2026-02-07_cryptic-curvilinear-detection/cryptic-curvilinear.html</link>
  <description><![CDATA[ 






<section id="the-problem" class="level2">
<h2 class="anchored" data-anchor-id="the-problem">The problem</h2>
<p>A lot of gridded data arrives with two-dimensional longitude and latitude arrays: one value per pixel, stored explicitly. This is the standard representation for curvilinear grids in NetCDF and HDF — satellite swaths, model output on rotated grids, polar data. The coordinates describe the geographic location of every cell, and in many cases they genuinely need to: the grid may be irregular, oblique, or follow a sensor’s scan geometry.</p>
<p>But sometimes those coordinates are hiding something. The data was originally produced on a regular projected grid — polar stereographic, Lambert conformal conic, Mercator — and at some point in the processing chain the projection was discarded and replaced with lon/lat arrays computed from it. The regularity is gone. The CRS is gone. What was four numbers and a coordinate reference system is now two arrays of floating-point values, one per pixel, carrying no more information than the originals but consuming orders of magnitude more storage and, critically, obscuring the actual structure of the data.</p>
<p>This is an entropy problem. A 316×332 grid in EPSG:3412 at 25 km resolution is fully specified by a CRS string and four extent values. Storing lon/lat arrays for every pixel replaces those with 209,824 coordinate values — a 52,000:1 expansion — and the extra values are <em>noisier</em> than the originals because they’ve accumulated floating-point error from the forward projection.</p>
<p>It would be useful to be able to detect this automatically.</p>
</section>
<section id="detecting-regularity" class="level2">
<h2 class="anchored" data-anchor-id="detecting-regularity">Detecting regularity</h2>
<p>The in-development R package <a href="https://github.com/mdsumner/gproj">gproj</a> includes tools for characterising coordinate geometry and selecting appropriate projections. Among these is a regularity detection pipeline that can identify “cryptic curvilinear” grids — data that presents as irregular lon/lat arrays but is in fact a regular projected grid with lost metadata.</p>
<p>The approach is layered.</p>
<section id="step-1-is-it-rectilinear-in-lonlat" class="level3">
<h3 class="anchored" data-anchor-id="step-1-is-it-rectilinear-in-lonlat">Step 1: Is it rectilinear in lon/lat?</h3>
<p>The simplest hidden structure is a rectilinear grid: longitude varies only along one axis, latitude only along the other. This is tested by checking the standard deviation of each coordinate along each matrix dimension.</p>
<p><code>detect_rectilinear()</code> classifies these into subtypes. If both marginal sequences have uniform spacing, it’s a regular lon/lat grid. If longitude is uniform but latitude spacing follows the Mercator y-formula — <code>y = ln(tan(π/4 + φ/2))</code> — then it’s a Mercator grid that was “devolved” to lon/lat.</p>
<p>Running this on an AVISO altimetry product with 1080×915 lon/lat arrays:</p>
<pre><code>Rectilinear:        TRUE
Type:               mercator_devolved
Lon regular:        TRUE (0.3333° spacing)
Mercator test:      TRUE (max deviation: 1e-12)
Mercator resolution: 37106 m</code></pre>
<p>The deviation from perfect Mercator spacing is at machine epsilon — the coordinates were computed analytically. The 1,976,400 stored values can be replaced by a CRS and an extent.</p>
</section>
<section id="step-2-is-it-regular-in-a-projected-crs" class="level3">
<h3 class="anchored" data-anchor-id="step-2-is-it-regular-in-a-projected-crs">Step 2: Is it regular in a projected CRS?</h3>
<p>When the grid isn’t rectilinear in lon/lat, it may be regular in some projected coordinate system. The test:</p>
<ol type="1">
<li>Characterise the data geometry — centre, extent, hemisphere — to select candidate projections.</li>
<li>Project the lon/lat arrays into each candidate CRS.</li>
<li>Check whether the projected coordinates form a regular grid: constant spacing along rows and columns, with rows horizontal in projected space.</li>
</ol>
<p>The <code>params()</code> function in gproj computes a geometric characterisation from any set of lon/lat coordinates:</p>
<pre><code>&lt;params: 104912 points&gt;
  centre:     -0.0000, -88.1448
  extent:     [0.17, 359.84] x [-89.84, -39.36]
  span:       359.67° lon x 50.47° lat
  hemisphere: polar_south
  aspect:     wide
  axis:       azimuth=180.0°, tilt=90.0°, var_ratio=0.51/0.46/0.02</code></pre>
<p>This is a CryoSat-2 sea ice product distributed as a NetCDF file with 316×332 lon/lat arrays. The <code>params()</code> output immediately tells us it’s circumpolar Antarctic data with no dominant axis orientation (variance split evenly between two components — characteristic of polar data rather than an elongated swath).</p>
<p>Projecting the coordinates into EPSG:3412 (NSIDC Sea Ice Polar Stereographic South) and testing regularity:</p>
<pre><code>Max row-y SD:      13.42 m
Median dx:         25000 m
Max dx deviation:  74.8 m
Median dy:         -25000 m
Max dy deviation:  71.0 m</code></pre>
<p>Row-level y standard deviation of 13 metres on a 25 km grid means rows are horizontal to within 0.05% of a cell width. The x and y spacing deviations of ~70 m (0.3%) are the accumulated floating-point noise from the trig functions in the lon/lat → projection → lon/lat → projection roundtrip.</p>
<p>These numbers are unambiguous. A genuinely curvilinear grid would show deviations of 10–100% of the cell size. The gap between 0.3% and “not regular” is enormous.</p>
</section>
<section id="step-3-recover-the-grid-specification" class="level3">
<h3 class="anchored" data-anchor-id="step-3-recover-the-grid-specification">Step 3: Recover the grid specification</h3>
<p>Once regularity is confirmed, the resolution is snapped to the nearest “round” value (25000 rather than 24999.7) and the extent is recovered from the cell centres plus half-cell margins:</p>
<pre><code>Recovered extent:  -3950030  3950000  -3949995  4349967
Known NSIDC extent: -3950000  3950000  -3950000  4350000
Difference:         -30       0        5        -33</code></pre>
<p>Within 33 metres of the exact grid specification. The extent values snap to exact multiples of the resolution, completing the reconstruction:</p>
<pre><code>CRS:          EPSG:3412
Resolution:   25000 × 25000 m
Dimensions:   316 × 332
Extent:       -3950000  3950000  -3950000  4350000</code></pre>
<p>The 209,824 coordinate values have been replaced by four numbers and a CRS string. The reconstructed grid is not an approximation — it is the original grid, recovered (near enough to) exactly.</p>
</section>
</section>
<section id="the-mercator-case" class="level2">
<h2 class="anchored" data-anchor-id="the-mercator-case">The Mercator case</h2>
<p>A subtler variant of this problem occurs with global ocean products. A Mercator projection produces a regular grid in eastings and northings, but when projected to lon/lat the latitude spacing becomes non-uniform: cells are compressed toward the poles following the inverse Mercator formula. The longitude spacing remains uniform.</p>
<p>This is easy to miss because the grid <em>looks</em> rectilinear — it has separable 1D coordinate axes — and many tools will happily treat it as a lon/lat grid with variable spacing. But it’s not: it’s a Mercator grid with specific metric properties that are lost when treated as plain lon/lat.</p>
<p>The detection is straightforward: convert the latitude values to Mercator y-coordinates and check whether the spacing becomes uniform. For the AVISO product tested above, the deviation from perfect Mercator spacing is 10⁻¹² — indistinguishable from the analytical formula.</p>
</section>
<section id="swath-geometry-and-projection-selection" class="level2">
<h2 class="anchored" data-anchor-id="swath-geometry-and-projection-selection">Swath geometry and projection selection</h2>
<p>Not all lon/lat arrays hide regular grids. Satellite swath data from sensors like VIIRS or MODIS is genuinely curvilinear — the coordinates follow the sensor’s scan geometry, and there is no single projected CRS that makes them regular. But the coordinates still have structure that can be exploited.</p>
<p>The <code>params()</code> function characterises this structure using PCA on the 3D Cartesian coordinates of the data points. Converting lon/lat to points on the unit sphere before PCA avoids the distortions that would arise from operating directly in longitude/latitude space (particularly near the poles or antimeridian).</p>
<p>For a VIIRS swath boundary:</p>
<pre><code>&lt;params: 32436 points&gt;
  centre:     -113.56, -20.81
  extent:     [-180, 180] x [-84.44, 57.66]
  hemisphere: polar_south
  axis:       azimuth=12.4°, tilt=77.6°, var_ratio=0.85/0.14/0.02</code></pre>
<p>The variance ratio of 0.85 in the first component indicates a strongly elongated dataset. The azimuth of 12.4° gives the bearing of the dominant axis — essentially the satellite ground track orientation.</p>
<p>This feeds directly into projection selection. The <code>proj()</code> function uses the geometric characterisation to generate appropriate CRS parameters:</p>
<ul>
<li>Polar data with no dominant axis → stereographic or Lambert azimuthal equal-area, centred on the pole</li>
<li>Elongated data with a dominant axis → oblique Mercator aligned to the data’s axis</li>
<li>Mid-latitude wide bands → conic projections with secant latitudes derived from the data extent</li>
<li>Small regions → stereographic centred on the data</li>
</ul>
<p>For the VIIRS swath, this produces an oblique Mercator projection aligned to the 12.4° ground track, which straightens the swath into a narrow strip in projected coordinates (aspect ratio ~11:1).</p>
</section>
<section id="the-opportunity" class="level2">
<h2 class="anchored" data-anchor-id="the-opportunity">The opportunity</h2>
<p>The detection of hidden regular grids is not a theoretical exercise. These grids exist throughout operational data archives — sea ice products, ocean altimetry, atmospheric reanalyses, satellite imagery. Every file that stores redundant lon/lat arrays for a regular projected grid is:</p>
<ul>
<li>Wasting storage (often 2× the data volume for the coordinate arrays alone)</li>
<li>Losing precision (floating-point coordinates are noisier than the exact grid specification)</li>
<li>Obscuring structure (downstream tools treat the data as irregular, triggering unnecessary resampling)</li>
<li>Creating friction (users must discover the projection through external documentation or domain expertise)</li>
</ul>
<p>The regularity test demonstrated here is simple — a few lines of linear algebra and some tolerance checks — and could be applied routinely during data ingest or quality assessment. The tolerance gap between “regular with floating-point noise” (0.3% deviation) and “genuinely irregular” is wide enough that false positives are unlikely.</p>
<p>For data producers, the message is: if your data is on a regular projected grid, store the grid specification, not the coordinates. A CRS string and four extent values are complete, exact, and universally understood.</p>
<p>For data consumers and tool developers, the message is: when you encounter 2D lon/lat arrays, test for regularity before treating them as curvilinear. The answer may save you considerable complexity.</p>
<p>The gproj package is in early development at <a href="https://github.com/mdsumner/gproj">github.com/mdsumner/gproj</a>.</p>
<p>A previous iteration of this discussion was published here <a href="https://www.hypertidy.org/posts/2025-09-04_broken-netcdf/#throw-all-that-away-ignore-everything-above.">hypertidy.org: Coordinates broken in NetCDF</a>.</p>


</section>

 ]]></description>
  <category>gdal</category>
  <category>raster</category>
  <category>netcdf</category>
  <category>projections</category>
  <guid>https://hypertidy.org/posts/2026-02-07_cryptic-curvilinear-detection/cryptic-curvilinear.html</guid>
  <pubDate>Sat, 07 Feb 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>R vs JavaScript Datetime Parsing</title>
  <dc:creator>Michael Sumner</dc:creator>
  <link>https://hypertidy.org/posts/2025-12-30_datetime-js-r/</link>
  <description><![CDATA[ 






<section id="r-vs-javascript-datetime-parsing-behaviours-and-gotchas" class="level1">
<h1>R vs JavaScript Datetime Parsing: Behaviours and Gotchas</h1>
<p>Datetime parsing across R and JavaScript has subtle but dangerous differences. Both languages make assumptions that can silently produce incorrect results, and critically, they fail in <em>opposite</em> ways.</p>
<section id="the-core-problem" class="level2">
<h2 class="anchored" data-anchor-id="the-core-problem">The Core Problem</h2>
<p>Both languages must decide: when given a datetime string without timezone information, should it be interpreted as <strong>local time</strong> or <strong>UTC</strong>?</p>
<table class="caption-top table">
<colgroup>
<col style="width: 23%">
<col style="width: 45%">
<col style="width: 30%">
</colgroup>
<thead>
<tr class="header">
<th>Language</th>
<th>Default assumption (no tz info)</th>
<th>ISO 8601 <code>Z</code> suffix</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>R</td>
<td>Local time</td>
<td><strong>Likely ignored</strong> (see below)</td>
</tr>
<tr class="even">
<td>JavaScript</td>
<td>Depends on format</td>
<td>Respected as UTC</td>
</tr>
</tbody>
</table>
</section>
<section id="javascript-behaviour" class="level2">
<h2 class="anchored" data-anchor-id="javascript-behaviour">JavaScript Behaviour</h2>
<section id="format-determines-interpretation" class="level3">
<h3 class="anchored" data-anchor-id="format-determines-interpretation">Format determines interpretation</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb1-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Space-separated → LOCAL time (trap!)</span></span>
<span id="cb1-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">new</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Date</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1970-01-01 00:00:00"</span>)</span>
<span id="cb1-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Thu Jan 01 1970 00:00:00 GMT+1100</span></span>
<span id="cb1-4"></span>
<span id="cb1-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// ISO 8601 with Z → UTC (correct)</span></span>
<span id="cb1-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">new</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Date</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1970-01-01T00:00:00Z"</span>)</span>
<span id="cb1-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Thu Jan 01 1970 11:00:00 GMT+1100</span></span>
<span id="cb1-8"></span>
<span id="cb1-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Space-separated with explicit UTC → UTC (correct)</span></span>
<span id="cb1-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">new</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Date</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1970-01-01 00:00:00 UTC"</span>)</span>
<span id="cb1-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Thu Jan 01 1970 11:00:00 GMT+1100</span></span>
<span id="cb1-12"></span>
<span id="cb1-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// GMT works the same as UTC</span></span>
<span id="cb1-14"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">new</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Date</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1970-01-01 00:00:00 GMT"</span>)</span>
<span id="cb1-15"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Thu Jan 01 1970 11:00:00 GMT+1100</span></span></code></pre></div></div>
</section>
<section id="javascript-gotchas" class="level3">
<h3 class="anchored" data-anchor-id="javascript-gotchas">JavaScript gotchas</h3>
<ol type="1">
<li><strong>Space-separated format silently assumes local time</strong> — this is the most dangerous default for portable code</li>
<li><strong>Only ISO 8601 format is in the ECMAScript spec</strong> — space-separated formats with <code>UTC</code>/<code>GMT</code> suffix work universally in practice but are technically implementation-dependent</li>
<li><strong>No <code>tz</code> parameter equivalent</strong> — you cannot specify a timezone during parsing; you must embed it in the string or use <code>Date.UTC()</code></li>
</ol>
</section>
<section id="javascript-solutions" class="level3">
<h3 class="anchored" data-anchor-id="javascript-solutions">JavaScript solutions</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode javascript code-with-copy"><code class="sourceCode javascript"><span id="cb2-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Option 1: Use ISO 8601 format (most robust)</span></span>
<span id="cb2-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">new</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Date</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1970-01-01T00:00:00Z"</span>)</span>
<span id="cb2-3"></span>
<span id="cb2-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Option 2: Append UTC (most readable)</span></span>
<span id="cb2-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">new</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Date</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1970-01-01 00:00:00 UTC"</span>)</span>
<span id="cb2-6"></span>
<span id="cb2-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Option 3: Convert space-separated to ISO</span></span>
<span id="cb2-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">new</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Date</span>(str<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">replace</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">' '</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'T'</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'Z'</span>)</span>
<span id="cb2-9"></span>
<span id="cb2-10"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Option 4: Parse components explicitly (verbose but unambiguous)</span></span>
<span id="cb2-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">// Note: month is 0-indexed!</span></span>
<span id="cb2-12"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">new</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Date</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">Date</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">UTC</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1970</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">,</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>))</span></code></pre></div></div>
</section>
</section>
<section id="r-behaviour" class="level2">
<h2 class="anchored" data-anchor-id="r-behaviour">R Behaviour</h2>
<section id="the-tz-parameter" class="level3">
<h3 class="anchored" data-anchor-id="the-tz-parameter">The <code>tz</code> parameter</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb3-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Explicit UTC → correct (epoch = 0)</span></span>
<span id="cb3-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.integer</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.POSIXct</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1970-01-01 00:00:00"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">tz =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"UTC"</span>))</span>
<span id="cb3-3"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [1] 0</span></span>
<span id="cb3-4"></span>
<span id="cb3-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># No tz specified → local time (AEST = UTC+11, so -39600 seconds)</span></span>
<span id="cb3-6"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.integer</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.POSIXct</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1970-01-01 00:00:00"</span>))</span>
<span id="cb3-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [1] -39600</span></span>
<span id="cb3-8"></span>
<span id="cb3-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># ISO 8601 with Z → likely parsed as local time (Z ignored)</span></span>
<span id="cb3-10"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.integer</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.POSIXct</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1970-01-01T00:00:00Z"</span>))</span>
<span id="cb3-11"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># [1] -39600  ← on a machine with local timezone set to AEST</span></span>
<span id="cb3-12"></span>
<span id="cb3-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># </span><span class="al" style="color: #AD0000;
background-color: null;
font-style: inherit;">DANGER</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">: On a server with system timezone set to UTC, this returns 0</span></span>
<span id="cb3-14"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># which makes it APPEAR that Z is being respected—but it's a coincidence!</span></span></code></pre></div></div>
</section>
<section id="r-gotchas" class="level3">
<h3 class="anchored" data-anchor-id="r-gotchas">R gotchas</h3>
<ol type="1">
<li><strong>The <code>Z</code> suffix is likely ignored</strong> — R’s parsing relies on the system’s C library (<code>strptime</code>), which may not interpret <code>Z</code> as a timezone indicator. The string is parsed as local time regardless of the <code>Z</code>.</li>
<li><strong>Tests can pass by accident</strong> — If your system timezone is configured to UTC (common on servers), parsing will <em>appear</em> to respect the <code>Z</code> suffix when it’s actually just a coincidence. The bug only surfaces when code runs on a machine with a different timezone configuration.</li>
<li><strong>Default is local time</strong> — omitting <code>tz</code> uses the system timezone, whatever that may be</li>
<li><strong>The <code>T</code> separator works</strong> — R accepts ISO 8601 format for the datetime portion, it just ignores the <code>Z</code></li>
</ol>
</section>
<section id="r-solutions" class="level3">
<h3 class="anchored" data-anchor-id="r-solutions">R solutions</h3>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode r code-with-copy"><code class="sourceCode r"><span id="cb4-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Always specify tz explicitly</span></span>
<span id="cb4-2"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.POSIXct</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1970-01-01 00:00:00"</span>, <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">tz =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"UTC"</span>)</span>
<span id="cb4-3"></span>
<span id="cb4-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># For ISO 8601 strings with Z, strip Z and specify tz</span></span>
<span id="cb4-5">s <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">&lt;-</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1970-01-01T00:00:00Z"</span></span>
<span id="cb4-6"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">as.POSIXct</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">gsub</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"T"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">" "</span>, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">gsub</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Z$"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span>, s)), <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">tz =</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"UTC"</span>)</span>
<span id="cb4-7"></span>
<span id="cb4-8"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Or use a package that respects ISO 8601 properly</span></span>
<span id="cb4-9"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># lubridate::ymd_hms() handles Z correctly</span></span>
<span id="cb4-10">lubridate<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">::</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">ymd_hms</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"1970-01-01T00:00:00Z"</span>)</span></code></pre></div></div>
</section>
</section>
<section id="cross-language-comparison" class="level2">
<h2 class="anchored" data-anchor-id="cross-language-comparison">Cross-Language Comparison</h2>
<p>Given the string <code>"1970-01-01T00:00:00Z"</code> representing the Unix epoch:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 16%">
<col style="width: 16%">
<col style="width: 16%">
<col style="width: 33%">
<col style="width: 16%">
</colgroup>
<thead>
<tr class="header">
<th>Language</th>
<th>System TZ</th>
<th>Code</th>
<th>Result (seconds since epoch)</th>
<th>Correct?</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>JavaScript</td>
<td>Any</td>
<td><code>new Date("1970-01-01T00:00:00Z").getTime() / 1000</code></td>
<td><code>0</code></td>
<td>✓</td>
</tr>
<tr class="even">
<td>R</td>
<td>UTC</td>
<td><code>as.integer(as.POSIXct("1970-01-01T00:00:00Z"))</code></td>
<td><code>0</code></td>
<td>✓ (by accident!)</td>
</tr>
<tr class="odd">
<td>R</td>
<td>AEST</td>
<td><code>as.integer(as.POSIXct("1970-01-01T00:00:00Z"))</code></td>
<td><code>-39600</code></td>
<td>✗</td>
</tr>
<tr class="even">
<td>R</td>
<td>Any</td>
<td><code>as.integer(as.POSIXct("1970-01-01 00:00:00", tz="UTC"))</code></td>
<td><code>0</code></td>
<td>✓</td>
</tr>
<tr class="odd">
<td>R</td>
<td>Any</td>
<td><code>as.integer(lubridate::ymd_hms("1970-01-01T00:00:00Z"))</code></td>
<td><code>0</code></td>
<td>✓</td>
</tr>
</tbody>
</table>
</section>
<section id="recommendations" class="level2">
<h2 class="anchored" data-anchor-id="recommendations">Recommendations</h2>
<section id="if-you-control-the-format" class="level3">
<h3 class="anchored" data-anchor-id="if-you-control-the-format">If you control the format</h3>
<p>Use ISO 8601 with <code>Z</code> for JavaScript, but always use explicit <code>tz = "UTC"</code> in R regardless of string format.</p>
</section>
<section id="if-exchanging-data-between-r-and-javascript" class="level3">
<h3 class="anchored" data-anchor-id="if-exchanging-data-between-r-and-javascript">If exchanging data between R and JavaScript</h3>
<p>There is no single string format that both languages interpret correctly as UTC without extra handling:</p>
<ul>
<li><strong>ISO 8601 with Z</strong> (<code>"1970-01-01T00:00:00Z"</code>): JavaScript ✓, R ✗ (may appear to work if system TZ is UTC)</li>
<li><strong>Space-separated with UTC</strong> (<code>"1970-01-01 00:00:00 UTC"</code>): JavaScript ✓, R ✗ (doesn’t parse “UTC” suffix)</li>
<li><strong>Space-separated plain</strong> (<code>"1970-01-01 00:00:00"</code>): Both interpret as local time (consistent but usually wrong)</li>
</ul>
<p>The safest approach is to use numeric Unix timestamps (seconds or milliseconds since epoch) when exchanging data, or always apply explicit timezone handling on both sides.</p>
</section>
<section id="summary-table" class="level3">
<h3 class="anchored" data-anchor-id="summary-table">Summary table</h3>
<table class="caption-top table">
<colgroup>
<col style="width: 36%">
<col style="width: 43%">
<col style="width: 20%">
</colgroup>
<thead>
<tr class="header">
<th>Scenario</th>
<th>JavaScript</th>
<th>R</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Parse as UTC</td>
<td>Append <code>Z</code> or <code>UTC</code></td>
<td>Use <code>tz = "UTC"</code> parameter</td>
</tr>
<tr class="even">
<td>ISO 8601 <code>Z</code> respected?</td>
<td>Yes</td>
<td>No (may appear to work if system TZ is UTC)</td>
</tr>
<tr class="odd">
<td>Space-separated default</td>
<td>Local time</td>
<td>Local time</td>
</tr>
<tr class="even">
<td>Explicit tz parameter?</td>
<td>No</td>
<td>Yes</td>
</tr>
<tr class="odd">
<td>Safe portable format</td>
<td>ISO 8601 + Z</td>
<td>N/A (always need <code>tz</code> param)</td>
</tr>
</tbody>
</table>
</section>
</section>
<section id="notes" class="level2">
<h2 class="anchored" data-anchor-id="notes">Notes</h2>
<ul>
<li>UTC and GMT are functionally equivalent for parsing purposes in both languages</li>
<li>JavaScript’s <code>Date.UTC()</code> function accepts numeric components and returns milliseconds since epoch, with the bizarre gotcha that months are 0-indexed</li>
<li>R’s <code>as.POSIXct()</code> relies on the system’s C library (<code>strptime</code>) for parsing; the <code>Z</code> suffix is likely ignored and the string is parsed as local time</li>
<li><strong>Testing is unreliable</strong>: if your development/CI server has system timezone set to UTC, datetime tests will pass by accident and the bug will only appear on machines with different timezone configuration</li>
<li>The only truly portable approach in base R is to always use <code>tz = "UTC"</code> explicitly, which has been available in R for 20+ years now</li>
<li>lubridate handles ISO 8601 consistently by doing its own parsing rather than delegating to the system</li>
</ul>


</section>
</section>

 ]]></description>
  <category>code</category>
  <category>news</category>
  <guid>https://hypertidy.org/posts/2025-12-30_datetime-js-r/</guid>
  <pubDate>Tue, 30 Dec 2025 00:00:00 GMT</pubDate>
</item>
</channel>
</rss>
