Using cartopy-extlon
Producing world maps beyond 360 degrees
I shall write a more thorough article on this later, but for now this is just a short primer on the modifications I've made to Cartopy in order to get it to produce maps which continue on beyond 360 degrees. Maps like this using the Plate Carrée projection
Figure 1: Sea level anomaly extended beyond [-180, 180] degrees. The data are Copernicus level 4 near-real-time sea level anomalies for the 25th of November, 2024 (E.U. Copernicus Marine Service Information (CMEMS). Marine Data Store (MDS). n.d.).
and this
Figure 2: Geostrophic eddy kinetic energy anomaly extended beyond [-180, 180] degrees derived from sea level anomalies (as for 1); other features were drawn by a modified Cartopy; the dots represent individual coordinates. In particular, notice that the longitudes on the grid lines repeat, however the blue dot, representing Chicago, had to be explicitly plotted with 360 degrees added to the longitude.
or using Mercator
Figure 3: As for figure 2 but using the Mercator projection.
are now possible by supplying the argument over=True when instantiating a Cartopy CRS, like so
proj = ccrs.PlateCarree(over=True) ax = plt.axes(projection=proj)
This implements the +over parameter in PROJ, as suggested by this comment.
For this to be at all useful, the data must be manually extended, for which a simple routine suffices, and I have added two to cartopy.util. The functions are called extend_lons_np and extend_lons_xr, being for Numpy ndarrays and Xarray datasets respectively. These can be used without Cartopy, for all they do is repeat the data beyond 360 degrees.
I thought about adding code to automatically extend the data, and that might be worthwhile doing, but generating extended data manually has the advantage of allowing different data to be plotted beyond the 360 degree confines of a complete global map. In particular, this feature request was so that the original poster could plot non-cyclic routes over more than one rotation of the Earth.
Because of the way Cartopy works, and its baked-in assumptions of 360-degreeness, I adapted some of its routines to produce things like Tissot indicatrices, night shading and coastlines. Adding night shading works just as before, with the addition of the over=True parameter:
ax.add_feature(Nightshade(date, over=True))
For the Tissot indicatrices and coastlines, I created separate routines, coastlines_ext() and tissot_ext(). I have tried integrating these into the original routines (see branch natural_earth) but for now I prefer to separate them, mainly to reduce clutter in the routines themselves. I also modified stock_img(), but it works transparently. Note that coastlines_ext() does not generate its feature from cartopy.feature.COASTLINE, but instead directly calls a modified version of cartopy.feature.NaturalEarthFeature() called, unsurprisingly, cartopy.feature.NaturalEarthFeature_ext().
The separation of functionality into new routines came after I realised that the best way to reproduce features was not in FeatureArtist but by reworking the feature generating code itself. This is mainly because different features behave differently, and while simply replicating features with coordinates at plus or minus 360 degrees might work for coastlines, it does not work for polygons which, in the original code, were often left unclosed at the map limits (e.g. night shading). Nor do bitmap images get extended, since they are not treated by FeatureArtist. Just as with the input data, the data to be plotted should themselves be extended as required. But likewise, making sure that the same feature generating code works for both over=True and over=False requires extra work. As mentioned, I have tested this and it can be done, but it is messier.
Something to note is that by default the axis ticks for longitude repeat, i.e. the longitude is still limited to [-180, 180]. This is cosmetic, however—internally, if you plot something at 370 degrees longitude, it will not be present at 10 degrees, nor at -350 degrees. Likewise, if you plot something at 10 degrees, it will not be present anywhere else on the map.
There are of course limitations. PROJ's +over parameter is only intended for cylindrical projections, so I have only applied it to cylindrical projections. Indeed, I have only really implemented it in PlateCarree and Mercator at this point. I also have not gone through every Cartopy method or feature, and testing has been limited to whatever I could think of.
A more unexpected limitation is that longitudes are limited to between [-572.95, 572.95]. This is a limitation in PROJ itself, though I have, for safety, implemented it as a hard limit in the code where it made sense to. For my purposes this is not an issue, as I only need to extend longitudes a little to cover every ocean basin continuously. For the OP who needs to plot routes, I hope that just over three revolutions suffices for their needs!
Suggestions for improvement are of course welcome, you can send them to me at malektronica at icloud dooooot cooooom, or better yet initiate a PS. If you can improve the code, so much the better—I'd really like to see this implemented in Cartopy!
The code is here: https://github.com/malektronic/cartopy-extlon. Check out the branch extended_maps (the default).