-
Notifications
You must be signed in to change notification settings - Fork 359
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
Add support for all projections to lon/lat labelling #1117
Conversation
Labels are plotted only at the x/y plot boundaries, not at the map boundaries. This would implies some renaming for the sake of clarity, since x and y no longer always mean lon and lat: lon and lat labels may be plotted on both x and y axes.
Cool! I always thought something along these lines would be the way to go, but never got around to attempting it. |
New reference figures must be generated for pytest
Signed-off-by: Stephane Raynaud <stephane.raynaud@shom.fr>
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.
This is a really great feature that's been needed for a while, thank you @stefraynaud!
I do not have enough experience with this part of the cartopy code to provide a full code review (@pelson, could you help with this?).
I ran the code you provided above to reproduce the plots and found:
- I was not able to create some plots with the expected labels e.g. the Mercator plot of UK had no labels. Is this because this PR is waiting on Add inline grid meridian/parallel labeling #1089?
- I got a verbose output of all the labels which were produced. Is this intended?
I'm about to amend the PR with commits in which the distinction x/y and parralels/meridians is made. So it's gonna break a bit the API. These commits will also include a commit to ticker to add support for minutes and seconds to the formatters, and to add longitude and latitude locators that also work at very small scale. The changes will be added to this PR to make it cleaner and because they are used by the gridliner. |
Locator work from large to tiny scales. Formatters now display minutes and seconds when needed. Use decimal=True to get the old behavior.
It was failing with numpy arrays.
I'm working on a more universal way to plot labels, not only on plot axes limits, but also everywhere grid lines intersects the map boundary, whatever its shape. So I'll amend this PR in the future. |
Hi @stefraynaud, I see that you're working on a new PR to achieve this goal a little bit better, but I think this PR along with #1089 are incredibly valuable. Can you please let me know what's going on with this one now? I am happy to review it and to help progress it (because I really want this in cartopy, finally, and you've done a lot of work which I don't want to lose), but if it's dead and there is a replacement in progress then I will wait for that instead. Also, I noticed that this PR must be merged with #1089; is that because of something functional or something to do with the docs? What I want to know is; if I borrow this branch to test it locally, will it work? |
I'm still working actively on this PR and about to finish a version. However, an important aspect of the new algorithm is that the visibility of all labels must updated during draw events to make sure labels are not overlapping themselves and not overlaping the map patch, contrary to what happened when saving the above figure for northern labels. I still didn't tried to integrate #1089 because still trying to solve the above issue, but it will be very valuable to have the inline labels capability added too. |
- labels are now also plotted all around the map boundary - the "clip" keyword is accepted by GeoAxes.set_extent to limits map boundary to the given extent - the axes title position now takes the gridline labels into account
The following code produces the following figures: from __future__ import (absolute_import, division, print_function)
import matplotlib
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
matplotlib.rc('font', size=6)
pcar = ccrs.PlateCarree()
# Global projections
ncol = 4
sy = 3.5
sx = 3.5
global_projs = [pcar,
ccrs.Mercator(),
ccrs.AlbersEqualArea(),
ccrs.AzimuthalEquidistant(central_longitude=90),
# ccrs.EquidistantConic(),
ccrs.LambertConformal(),
ccrs.LambertCylindrical(),
ccrs.Miller(),
ccrs.Mollweide(),
ccrs.Orthographic(),
ccrs.Robinson(),
ccrs.Sinusoidal(),
ccrs.Stereographic(),
ccrs.TransverseMercator(),
ccrs.InterruptedGoodeHomolosine(),
ccrs.OSGB(),
ccrs.EuroPP(),
ccrs.Geostationary(),
ccrs.NearsidePerspective(central_latitude=50.72,
central_longitude=-3.53,
satellite_height=10000000.0),
# ccrs.EckertI(),
# ccrs.EckertIII(),
# ccrs.EqualEarth(),
ccrs.Gnomonic(),
ccrs.LambertAzimuthalEqualArea(),
ccrs.NorthPolarStereo(),
ccrs.OSNI(),
]
nrow = (len(global_projs)-1)//ncol + 1
fig = plt.figure(figsize=(sx*ncol, nrow * sy))
print('GLOBAL')
for ip, proj in enumerate(global_projs):
print(' '+proj.__class__.__name__)
ax = plt.subplot(*(nrow, ncol, ip+1), projection=proj)
ax.gridlines(pcar, draw_labels=True)
for feature in [cfeature.OCEAN, cfeature.LAND, cfeature.COASTLINE]:
ax.add_feature(feature)
ax.set_title(proj.__class__.__name__ + ' global')
fig.savefig(__file__[:-2]+'global.png') # to make tight_layout working
fig.tight_layout(h_pad=1.5, w_pad=1.5, pad=0.1)
fig.savefig(__file__[:-2]+'global.png')
# Special cases
ncol = 2
sy = 3
sx = 4
uk_extent = [-10, 3, 49, 59]
north_atlantic_extent = [-90, 0, -10, 70]
special_projs = [(pcar, uk_extent, False),
(ccrs.RotatedPole(pole_longitude=120.0, pole_latitude=45.0),
north_atlantic_extent, False),
(ccrs.RotatedPole(pole_longitude=120.0, pole_latitude=45.0),
north_atlantic_extent, True)]
nrow = (len(special_projs)-1)//ncol + 1
fig = plt.figure(figsize=(sx*ncol, nrow * sy))
for ip, (proj, extents, clip) in enumerate(special_projs):
ax = plt.subplot(*(nrow, ncol, ip+1), projection=proj)
ax.set_extent(extents, crs=pcar, clip=clip)
ax.gridlines(pcar, draw_labels=True)
for feature in [cfeature.OCEAN, cfeature.LAND, cfeature.COASTLINE]:
ax.add_feature(feature)
ax.set_title(proj.__class__.__name__ + ' with extent [clip={}]'.format(clip))
fig.savefig(__file__[:-2]+'special.png') # to make tight_layout working
fig.tight_layout(h_pad=1.5, w_pad=1.5, pad=0.1)
fig.savefig(__file__[:-2]+'special.png') With this update, the position of labels is extrapolated from the intersection of a gridline with the map boundary, wherever it occurs. The visibility of each labels is checked so that the do not overlap themselve and do not overlap the map boundary. When an overlapping occurs, the other label directions are plotted within a given limit. Despite the algorithm can be improved, it should provide a decent basis. |
Conflicts: lib/cartopy/tests/mpl/baseline_images/mpl/test_gridliner/gridliner1.png lib/cartopy/tests/mpl/test_gridliner.py
Hi @stefraynaud, I've been taking a look at your work this morning. Those are some very pretty plots you've produced. As you can see by the checks, I couldn't look at any test results because you need to resolve some merge conflicts before Appveyor can run them. I didn't find any test failures when I ran them on your branch locally though, so that bodes well. There are a couple of sections in your code additions which I would like some clarification on, but I'll dig into that when we get some tests running. Thank you for your continuing effort with this. |
👍 just awesome !! |
Any plans for a new release? This PR clears the ways for many developers to replace Basemap which will be EOL'ed just several months later. I think this PR alone can make a new release. |
@@ -427,10 +427,50 @@ def draw(self, renderer=None, inframe=False): | |||
self.imshow(img, extent=extent, origin=origin, | |||
transform=factory.crs, *args[1:], **kwargs) | |||
self._done_img_factory = True | |||
|
|||
self._cachedRenderer = renderer |
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.
This doesn't appear to be used for anything.
@@ -693,7 +733,7 @@ def _get_extent_geom(self, crs=None): | |||
|
|||
return geom_in_crs | |||
|
|||
def set_extent(self, extents, crs=None): | |||
def set_extent(self, extents, crs=None, clip=False): |
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.
There is no use of set_extent(..., clip=True)
, so how is this related to this PR? There are no examples or tests of it either, so I don't know what it's supposed to do either.
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.
You are right. It allows to adapt the map boundaries to specified lat/lon box, so you can make plots such as this one : #1117 (comment)
PR #1117 allows to add lon/lat labels even when map boundaries are not rectangular, like with clip=True if the projection is not rectangular.
Maybe this piece of code should be removed from master, and readded with another PR with a valid documentation.
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.
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.
Setting clip=True
makes two things
- compute de boundary according to the extent (here is the added value)
- apply the boundary set
set_boundary
.
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.
Seems valuable, but I think it really should go through its own PR with documentation, examples, etc.
@@ -1229,15 +1291,22 @@ def gridlines(self, crs=None, draw_labels=False, xlocs=None, | |||
xlocs: optional | |||
An iterable of gridline locations or a | |||
:class:`matplotlib.ticker.Locator` instance which will be | |||
used to determine the locations of the gridlines in the | |||
x-coordinate of the given CRS. Defaults to None, which | |||
used to determine the locations of the meridian gridlines in the |
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.
This change is incorrect; the CRS is not necessarily defined in terms of latitude and longitude.
I didn't know about the curvilinear grid support of matplotlib tick labelling. Maybe it should be appropriately used for the cartopy labelling system. However, some adaptation would be needed to support labels placed around the edges of the map that do not coincide with the axes edges. |
Hello, I installed the latest version But I still got the error:
I am wondering if I did something wrong? |
Unfortunately it appears this feature has not yet been published in a release :/. See the version 0.18 milestone. |
There is no 0.18.0b1 on https://anaconda.org/conda-forge/cartopy/labels, so you need to wait until they update. |
I'm on v0.18.0 and getting: is it suppose to be supported? |
|
Rationale
Longitude / latitude labelling is restricted to Mercator and PlateCarree projections.
This restriction can be removed by computing the position of labels as the intersection of grid lines with the map and plot boundaries. This makes it compatible with all projections.
In the current version, only labels that are the border of the plot are displayed, not those at the border of the map. This limitation can be overcome by better checking if a label is plotted over another object, like a label or the map.
The code below produces the attached figures.
Implications
Longitude and latitude labels can be plotted for all projections.
Another implication is that x/y labels are no longer associated with respectively longitudes/latitudes. This must be clarified in the internal API at least.
Note : This PR must be merged with PR #1089