-
Notifications
You must be signed in to change notification settings - Fork 370
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
Handling ticks, ticklabels, labels, gridlines and curvilinear boundaries using mpl_toolkits.axisartist? #1773
Comments
Really nice. |
I agree, that does look like a nice approach. I know in the past I've played around with floating axes and then when you go to an interactive mode the axis moves around with pan/zoom, so I'm not sure we want to go that route. I think you can get the Can you then add the |
Sounds like a good idea, I will look into that. It might take some time as I am busying moving home recently. |
Great addition, @blazing216! Very much looking forward to this being available in Cartopy. It's a feature I've also been hoping for ever since I discovered M_Map for MATLAB, which produces similar maps: https://www.eoas.ubc.ca/~rich/map.html#2._Lambert_Conformal_Conic_projection_of |
Dear @blazing2016, I would like to thank you for your suggestions and proposals. At the beginning of your initial comment of this Pull Request, you mentioned the existence of two approaches to implement curvilinear projections using matplotlib: projection and axisartists. I have never heard of such approaches. Would you be so kind as to point to some references, and if possible, to some snip code examples supporting your statement? It would be most helpful for me (and everyone else) to be capable of following along with your suggestions. Not only that but it may also help us in assisting you in your new GeoAxes proposal. Sincerely, |
Dear @PhilipeRLeal, Projections: axisartists: Kind regards, |
Hi guys, I tried what @greglucas suggested: using import matplotlib
#%matplotlib widget
from mpl_toolkits.axisartist.grid_helper_curvelinear import GridHelperCurveLinear
from mpl_toolkits.axisartist import angle_helper, Axes, HostAxes
from collections import OrderedDict
import cartopy.crs as ccrs
import cartopy.mpl.geoaxes as cgeoaxes
import cartopy.mpl.patch as cpatch
from cartopy.mpl.feature_artist import FeatureArtist, _freeze, _GeomKey
tr = cgeoaxes.InterProjectionTransform(ccrs.PlateCarree(),
ccrs.LambertConformal(central_longitude=0))
class myFeatureArtist(FeatureArtist):
def __init__(self, feature, **kwargs):
super().__init__(feature, **kwargs)
def draw(self, renderer, *args, **kwargs):
"""
Draw the geometries of the feature that intersect with the extent of
the :class:`cartopy.mpl.GeoAxes` instance to which this
object has been added.
"""
if not self.get_visible():
return
ax = self.axes
feature_crs = self._feature.crs
# Get geometries that we need to draw.
extent = None
try:
extent = ax.get_extent(feature_crs)
except ValueError:
warnings.warn('Unable to determine extent. Defaulting to global.')
except:
#extent = ax.get_extent()
# hard-coded
extent = [-4036724.97527266, 4036724.97527266,
-3951198.60336765, 527223.8051583]
# print(extent)
geoms = self._feature.intersecting_geometries(extent)
# Combine all the keyword args in priority order.
prepared_kwargs = dict(self._feature.kwargs)
prepared_kwargs.update(self._kwargs)
prepared_kwargs.update(kwargs)
# Freeze the kwargs so that we can use them as a dict key. We will
# need to unfreeze this with dict(frozen) before passing to mpl.
prepared_kwargs = _freeze(prepared_kwargs)
# Project (if necessary) and convert geometries to matplotlib paths.
stylised_paths = OrderedDict()
#key = ax.projection
# hard-coded
key = ccrs.LambertConformal(central_longitude=0)
ax_projection = ccrs.LambertConformal(central_longitude=0)
for geom in geoms:
# As Shapely geometries cannot be relied upon to be
# hashable, we have to use a WeakValueDictionary to manage
# their weak references. The key can then be a simple,
# "disposable", hashable geom-key object that just uses the
# id() of a geometry to determine equality and hash value.
# The only persistent, strong reference to the geom-key is
# in the WeakValueDictionary, so when the geometry is
# garbage collected so is the geom-key.
# The geom-key is also used to access the WeakKeyDictionary
# cache of transformed geometries. So when the geom-key is
# garbage collected so are the transformed geometries.
geom_key = _GeomKey(geom)
FeatureArtist._geom_key_to_geometry_cache.setdefault(
geom_key, geom)
mapping = FeatureArtist._geom_key_to_path_cache.setdefault(
geom_key, {})
geom_paths = mapping.get(key)
if geom_paths is None:
if ax_projection != feature_crs:
projected_geom = ax_projection.project_geometry(
geom, feature_crs)
else:
projected_geom = geom
geom_paths = cpatch.geos_to_path(projected_geom)
mapping[key] = geom_paths
if not self._styler:
style = prepared_kwargs
else:
# Unfreeze, then add the computed style, and then re-freeze.
style = dict(prepared_kwargs)
style.update(self._styler(geom))
style = _freeze(style)
stylised_paths.setdefault(style, []).extend(geom_paths)
# transform = ax.projection._as_mpl_transform(ax)
transform = ax.transData
# Draw one PathCollection per style. We could instead pass an array
# of style items through to a single PathCollection, but that
# complexity does not yet justify the effort.
for style, paths in stylised_paths.items():
# Build path collection and draw it.
c = matplotlib.collections.PathCollection(
paths,
transform=transform,
**dict(style))
c.set_clip_path(ax.patch)
c.set_figure(ax.figure)
c.draw(renderer)
# n.b. matplotlib.collection.Collection.draw returns None
return None
import cartopy.feature as cfeature
land_50m = cfeature.NaturalEarthFeature('physical', 'land', '50m',
edgecolor='face',
facecolor=cfeature.COLORS['land'])
coastlines = cfeature.GSHHSFeature(scale='c', levels=[1], facecolor='gray')
class SimplestGeoAxesUsingGridHelper:
def __init__(self):
self.ax = fig.add_subplot(111, axes_class=HostAxes, grid_helper=GridHelperCurveLinear(tr))
self.ax2 = self.ax.get_aux_axes(tr)
self.ax.parasites.append(self.ax2)
def add_artist(self, artist):
self.ax.add_artist(artist)
def test1(fig):
ax = SimplestGeoAxesUsingGridHelper()
artist = myFeatureArtist(coastlines)
ax.add_artist(artist)
xy0 = tr.transform((-70,-20))
xy1 = tr.transform((60,70))
xy2 = tr.transform((60,-20))
ax.ax.set_aspect(1.0)
ax.ax.set_xlim(xy0[0], xy2[0])
ax.ax.set_ylim(xy0[1], xy1[1])
ax.ax.grid()
fig = plt.figure()
test1(fig)
Sorry I hard-coded the projection and map extent into |
any update for this tick feature ? |
Description
There seem two existing approaches to implement curvilinear projections using matplotlib,
projection
andaxisartist
. The current implementation of cartopy follows theprojection
approach, which shows a nice consistency with the general matplotlib workflows. However, handling ticks, ticklabels, gridlines with flexibility seem still a challenge. Although gridliner provides increasingly more features, plotting tick markers and using customed curvilinear boundaries are still difficult. The single issue has constantly driven me back to GMT to making the final version of figures for publications.After waiting for almost two years, I decided to implement the feature myself. The challenge is that using
axisartist
seems a completely different way from usingprojection
, so I ended up with an alternative GeoAxes class that initialize using theInterProjectionTransform
, andfloating axes
inaxisartist
. Good thing is, it can handle ticks and everything in a generic way (hopefully). But it lost all the plotting functions in GeoAxes. I just managed to add a function to addFeatures
(e.g.NaturalEarthFeature
).I wonder if there is a way to incorporate it into GeoAxes, or to exist as an alternative in cartopy.
Here is an example.
The text was updated successfully, but these errors were encountered: