-
Notifications
You must be signed in to change notification settings - Fork 387
Spilhaus projection #2529
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Spilhaus projection #2529
Conversation
…s a http error when I first run the test
Some notes:
|
https://github.com/SciTools/cartopy/blob/1e1583334587ae16644ebb31f1b311b2ef36f23f/.github/workflows/ci-testing.yml#L43C9-L45C1 |
The minimum version specification only affects one of the test runners. The others should use the latest pyproj from PyPI, though it looks like that is older than Proj 9.6. So maybe we just need to wait for a pyproj release. Did you create your local environment with conda? Conda-forge does its own re-building independent of the package maintainers. |
@rcomer Thanks for clarifying. Yes, I guess we will have to wait for a pip release from pyproj. Yes, I built a new environment using OK, it seems like the only one I passed is the minimum requirement one... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Glad you're having fun with this one :) I've got a few suggestions to mostly make this simpler on the Cartopy maintenance side and then users can extend it later with the extra names if they want.
Co-authored-by: Greg Lucas <greg.m.lucas@gmail.com>
self.bounds = [ | ||
-11802684.083372328, | ||
11802683.949222516, | ||
-11801129.925928915, | ||
11801129.925928915 | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should these be an actual square and therefore the same value in all positions?
Is there a pyproj way to get these exactly with pyproj_crs.area_of_use.bounds
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, they should be a actual square, but it is off by about 0.01%. I think this slight inaccuracy stem from upstream. The feature I love the most about this projection is that it is a square and can be used to tile the entire surface.
The Spilhaus projection is a global projection, but (-180,-90) and (180,90) are not at the corners. The transformed bounds defined here also does not produce the correct result some how...Theoretically the points furthest away from the center should be (115,30) but it is actually slightly off. My strategy was to use scipy minimize to find the actual pole, but I don't think it make sense to run the optimization every time we open the projection object.
It is a bit unsatisfying indeed. I don't know the math going into the map projection well enough to calculate the correct lon_0,lat_0, and azimuth, but may be I could design a optimization process to find them. Either way, we will have to pass a hard coded number in to pyproj.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if you do the area_of_use
approach above?
CRS.from_proj4("+proj=...").area_of_use.bounds
I agree we don't want to run this on every initialization and hardcoding some values would be useful. My overall comment is that you've chosen some values that are close to being a square, so do we want to just make those a square arbitrarily since you are somewhat choosing arbitrary initial points anyways.
I wonder if the values being different from epsg.io are due to what you mention with the lon_0, lat_0 not being 0 as is typical in many other projections.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just get a None object if I pass in the proj4 string. We can make it a perfect square, but that either leave some empty space or make the projection of some points outside the bounds. If we were to make it a square, I suggest we use the largest absolute value (11802684.083372328). However, I still much prefer that we just leave it slightly imperfect.
It is unclear what the bounds in epsg.io comes from. In any case, the correct transformed value should look something like a square. This is what it looks like when I apply the bounds on epsg.io:
This is what it looks like when I set lon_0, and lat_0 to zero under the same bounds
Co-authored-by: Greg Lucas <greg.m.lucas@gmail.com>
Co-authored-by: Greg Lucas <greg.m.lucas@gmail.com>
Co-authored-by: Greg Lucas <greg.m.lucas@gmail.com>
Co-authored-by: Greg Lucas <greg.m.lucas@gmail.com>
Co-authored-by: Greg Lucas <greg.m.lucas@gmail.com>
Should we also add this projection to |
Thank you! @philippemiron Yeah, we can definitely do that. My only concern is that between we merge this PR and making a public release, users will see this projection in the documentation and no be able to use it without cloning the repo. I could open another PR just to add this to the documentation. Let me know what the preferred work flow is. |
The documentation is only released when we make a code release, so no problem there. You can run that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks good 👍 a few more doc updates.
Co-authored-by: Greg Lucas <greg.m.lucas@gmail.com>
Co-authored-by: Greg Lucas <greg.m.lucas@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Looks good to me. One more minor formatting change and a request to add a link to the ArcGIS story or something similar to explain the projection a little more.
lib/cartopy/crs.py
Outdated
@@ -3219,6 +3219,40 @@ def x_limits(self): | |||
def y_limits(self): | |||
return self._y_limits | |||
|
|||
class Spilhaus(Projection): | |||
""" | |||
Spilhaus World Ocean Map in a Square |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should add a link to the story about this projection which explains some of the ideas here too and is a nice historical reference.
https://storymaps.arcgis.com/stories/756bcae18d304a1eac140f19f4d5cb3d
In particular it explains why those two points you chose below are special. Maybe even put those antipodal points int he docstring here too?
Co-authored-by: Greg Lucas <greg.m.lucas@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks for incorporating this!
@greglucas do you know when/how new version of cartopy are released? Cheers and have a good weekend! |
We don't have any set schedule, but it has been every roughly 6 months - 1 year. It looks like we are about due for another release and have quite a few things ready to go. I opened this issue #2532 to track any features folks want to see get in. Feel free to leave a comment there if you have anything else you'd like to see in! |
@guidocioni is that a contour plot and did you use transform_first? If so it could be the issue discussed at #2233 |
That's the first thing I thought about, but no, it is a I've seen this behaviour before and it happens on non-standard projections sometimes, but I'm not sure what it is exactly. For reference, this is the code you can use to reproduce it (provided you have of course a cartopy version with this PR already inside). You can run it on your side because it automatically downloads (and cache) the data needed for the plot import xarray as xr
import fsspec
import pandas as pd
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt
import numpy as np
import time
from aiohttp.client_exceptions import ServerDisconnectedError
date = pd.Timestamp("2024-01-01")
url = f"https://www.star.nesdis.noaa.gov/pub/socd/mecb/crw/data/5km/v3.1_op/nc/v1.0/daily/sst/{date.year}/coraltemp_v3.1_{date.strftime('%Y%m%d')}.nc"
while True:
try:
file = fsspec.open_local(
f"simplecache::{url}", simplecache={"cache_storage": "/tmp/"}
)
ds = xr.open_dataset(file, engine="netcdf4").squeeze()
break
except ServerDisconnectedError:
time.sleep(0.5)
# Set up the figure and the projection for the plot
fig, ax = plt.subplots(
figsize=(10, 10),
subplot_kw={
"projection": ccrs.Spilhaus()
},
)
ax.add_feature(
cfeature.LAND.with_scale("110m"), edgecolor="black", linewidth=0.25, zorder=2
)
lon2d, lat2d = np.meshgrid(ds["lon"].values, ds["lat"].values)
ax.pcolormesh(
lon2d,
lat2d,
ds["analysed_sst"].values,
transform=ccrs.PlateCarree(),
)
|
@guidocioni I suggest posting your example as a new issue. |
I wanted to but does it make sense as this has not yet been published into a release? Otherwise I'm happy to do so :) |
@guidocioni I ran into this issue as well. It is essential the same issue when you try to plot across datelines. Since the border here is quite a bit harder to define than “180W”, this makes the plotting tricky. I have a solution that involves figuring out the boundaries and masking the boundary grid points with NaN. It is a pretty ad hoc solution, but it works OK for high resolution simulations. I can share an example in your new issue. |
Yes, that would definitely help! We can move the discussion into #2542 |
Rationale
Implementing a cool-looking map projection for global ocean called the Spilhaus Adams world in a square II map projection.
This is in response to projection request #1376
Example
Many thanks to @greglucas.