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.
It was not a new projection
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.
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.
2020: PROJ gets halfway there in three months
Asger Petersen opened OSGeo/PROJ#1851 in January 2020, pointing at the Esri storymap and the parameters. By April, Kristian Evers had added adams_hemi, adams_ws1, adams_ws2, guyou and peirce_q to PROJ (PR #2148, “a first step towards implementing the Spilhaus projection as requested in #1851”), and Even Rouault had mapped Esri’s Adams_Square_II WKT to +proj=adams_ws2 and written an iterative inverse so gdalwarp could use it (PR #2157). Both shipped in PROJ 7.1.0.
The maths was there, the oblique/rotated aspect was not, and the issue sat for five years wearing the pinned label, whose stated purpose in the PROJ repo is to “prevent stale-bot from closing an issue”.
2019-2024: everyone else routes around
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.
- Cartopy issue #1376 (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.
- Torben Jansen published Observable notebooks for Adams World in a Square I and II, and for Spilhaus, on d3-geo. Joe Davies and others followed. This is where the “viral map” crowd already lived, so this is where most Spilhaus maps were actually being made.
- Ricardo Lemos published rtlemos/spilhaus, parallel R and Python scripts with
from_lonlat_to_spilhaus_xyand 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.” - Chen et al. (Scientific Data, 2023) 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.
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, st_transform() 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.
2025: three days in February
Javier Jimenez Shaw opened PR #4401 on 16 February 2025 and it was merged on 19 February. It implements +proj=spilhaus 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:
+proj=spilhaus
## equivalent to
+proj=spilhaus +lon_0=66.94970198 +lat_0=-49.56371678 +azi=40.17823482 +rot=45
The same day, Even Rouault’s PR #4402 wired ESRI:54099 and ESRI:54098 to +proj=spilhaus, which surfaced the last puzzle: Esri’s coordinates differ from the raw Adams maths by a factor of sqrt(2). PROJ carries that as +k_0=1.4142135... 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.
PROJ 9.6.0 was released on 15 March 2025 with “Add Spilhaus projection (#4401)” as one line in the release notes. The documentation page is a parameter table.
After: a very quiet arrival
What followed:
- Cartopy PR #2529 (May 2025) added a
Spilhausclass, gated on PROJ 9.6 and pyproj 3.7.1. But pyproj 3.7.1 wheels bundle PROJ 9.5.1, so apip installuser got nothing until 3.7.2 in August 2025. Conda users got it sooner. - A NetBSD user reported 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.
- The QGIS map gallery got one tagged post: “The beautiful Spilhaus projection (‘oceans in a square’) is new in PROJ 9.6.0.”
- The five-year issue closed with the PR and, as far as I can find, no announcement anywhere that ocean people read.
Meanwhile, all of this just works, if and only if your PROJ is 9.6 or newer:
library(sf)
x <- st_transform(st_as_sf(rnaturalearth::ne_countries()), "+proj=spilhaus")
library(terra)
r <- project(rast("/vsicurl/https://example.org/global.tif"), "+proj=spilhaus")gdalwarp -t_srs "+proj=spilhaus" global.tif spilhaus.tiffrom pyproj import Transformer
t = Transformer.from_crs(4326, "+proj=spilhaus", always_xy=True)
t.transform(147.3, -42.9)What this says about the landscape
The base layer is where the leverage is, and where the least attention goes. 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.
“It’s in PROJ” is not “it’s in your hands.” 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:
projinfo --versionsf::sf_extSoftVersion()["PROJ"]
terra::gdal(lib = "proj")import pyproj; pyproj.proj_version_str
from osgeo import osr; osr.GetPROJVersionMajor()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.
Provenance is lost by default. 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.
The vendor question is less interesting than it looks. 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.
Next
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.
Links
- OSGeo/PROJ#1851 Spilhaus projection
- PR #4401 Add Spilhaus projection
- PR #4402 Export ESRI:54099 as PROJ string
- PR #2148 Adams, Guyou, Peirce projections
- PR #2157 Adams Square II ESRI WKT and inverse
- PROJ Spilhaus documentation
- Esri: The Spilhaus map of the oceans will be supported in the next release of ArcGIS
- Chen et al. 2023, Ocean Sciences with the Spilhaus Projection
- Cartopy issue #1376 and PR #2529
- rtlemos/spilhaus
- pyproj changelog