15

Code first:

import cartopy.crs as ccrs
import matplotlib.pyplot as plt

ax = plt.axes(projection=ccrs.Mercator())
ax.set_extent([72, 135, 18, 53])
ax.annotate('hello', xy=(100, 49), xycoords='data',
            transform=ccrs.PlateCarree(), zorder=12)
plt.show()

The result is not the expected one, and I have other doubts about my approach. So my questions are:

  1. If I want to plot a map looks like the web map (eg. google map). The map area maybe is as large as China, mostly not global. After googling around, those sites use a "web mercator" projection mostly. So I suppost I should use plt.axes(projection=ccrs.Mercator() here, am I right? Or what should I use if I'm wrong?

  2. The coords data I want to plot is like 121°E, 49°N(converted the degree to decimal before plotting of course), unprojected, WGS84 coords system, probably from a GPS. So am I right to use transform=ccrs.PlateCarree()? Or what should I use if I'm wrong?

  3. The annotate above shows nothing. After comment the ax.set_extent line, the "hello" text is plotted at zero(0, 0) point. What I want is at point (100°E, 49°N)How to correct this?

pelson
  • 21,252
  • 4
  • 92
  • 99
gepcel
  • 1,326
  • 11
  • 21
  • Just a suggestion based on the length of my answer - it might be worth separating your questions in future. For example, there is no reason why "How to create a Google Mercator in Cartopy" couldn't be its own question. Otherwise, these are great questions. :+1: – pelson Aug 21 '14 at 08:47
  • Thank you for answering the question in such great detail. And please forgive my bad expressing. I'm from China, and I have to put an English dictionary aside to ask these questions. For now I'm more focusing on choosing the right word, and typing it accurately, less on the appropriate expressing. But for both cartopy and the English language, I'm practising. And I'll take your advice to ask questions seperately. – gepcel Aug 21 '14 at 23:38
  • I think your English is good - I understood the questions which is the most important part. Best of luck on your journey with both English and cartopy. It is always interesting to hear what is being done with cartopy, so if you have some interesting results to share in the future, please feel free to get in touch (perhaps post a github gist and notify me @pelson) - it would be great to hear from you. – pelson Aug 22 '14 at 09:52

1 Answers1

35

First - thanks for the code - it makes it a lot easier to get going with the question.

To be honest, I don't think annotate has been used in earnest with Cartopy before, so that is probably why you're hitting this problem - you're trail blazing ;)

It looks like matplotlib's Axes.annotate method is to blame here - it nukes the transform passed through around https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/axes/_axes.py#L651. This is mostly because annotate has special keywords for defining the transform of both the coordinate and the text position independently (see xycoords and textcoords in http://matplotlib.org/users/annotations_intro.html#annotating-text).

When we dig down into the Annotate class, we will find that Annotate's _get_xy_transform (https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/text.py#L1446) can handle various (some undocumented) forms as values to textcoords, including transform instances.

Ok, so far, so good. It would seem you can just put through a coordinate system to xycoords and everything should be hunky-dory. Sadly though, annotate does not know how to convert a Cartopy coordinate system to a matplotlib transform in the way that most of the rest of matplotlib does, so we are going to have to do that for the annotate function up-front.

To create a matplotlib transform from any cartopy coordinate system, for any axes, we can simply do:

ax = plt.axes(projection=ccrs.Mercator())
crs = ccrs.PlateCarree()
transform = crs._as_mpl_transform(ax)

We can now pass this transform through to the annotate method, and we should end up with text and an arrow in the expected location. I've taken a few liberties to highlight some of the functionality of annotate while I'm at it:

import cartopy.feature
import cartopy.crs as ccrs
import matplotlib.pyplot as plt


ax = plt.axes(projection=ccrs.Mercator())

ax.set_extent([65, 125, 5, 40])

ax.add_feature(cartopy.feature.OCEAN)
ax.add_feature(cartopy.feature.LAND)
ax.add_feature(cartopy.feature.BORDERS, linestyle=':', edgecolor='gray')
ax.coastlines()

ax.plot(116.4, 39.95, 'ob', transform=ccrs.PlateCarree())

transform = ccrs.PlateCarree()._as_mpl_transform(ax)
ax.annotate('Beijing', xy=(116.4, 39.9), xycoords=transform,
            ha='right', va='top')

ax.annotate('Delhi', xy=(113, 40.5), xytext=(77.23, 28.61),
            arrowprops=dict(facecolor='gray',
                            arrowstyle="simple",
                            connectionstyle="arc3,rad=-0.2",
                            alpha=0.5),
            xycoords=transform,
            ha='right', va='top')

plt.show()

Example of annotate with cartopy

In answer to your other questions:

If I want to plot a map looks like the web map (eg. google map)

There is a new constant in cartopy.crs which defined the Google Mercator exactly (cartopy.crs.GOOGLE_MERCATOR). This is just an instance of a Mercator projection with a few tweaks to make it exactly like the Google Mercator (https://github.com/SciTools/cartopy/blob/master/lib/cartopy/crs.py#L889).

The coords data I want to plot is like 121°E, 49°N(converted the degree to decimal before plotting of course), unprojected, WGS84 coords system, probably from a GPS. So am I right to use transform=ccrs.PlateCarree()? Or what should I use if I'm wrong?

I would suggest you would be better placed using the Geodetic coordinate system - this coordinate system defaults to using a WGS84 datum which will give you the most accurate representation of your WGS84 latitudes and longitudes. Though, at the scale you are currently drawing them, I imagine you would struggle to notice the difference (maximum difference is about ~22Km in mid-latitudes).

HTH,

pelson
  • 21,252
  • 4
  • 92
  • 99
  • Great answer! For the annotation, is it possible to have multiple arrows emanating from the same point, e.g. Delhi - Beijing, Delhi - Cairo, Delhi - Tokyo? – Colman McMahon Jul 11 '16 at 12:21
  • @pelson, what you you mean specifically when you say _"I would suggest you would be better placed using the Geodetic coordinate system"_ -- what should I use instead of `ccrs.PlateCarree()` in `transform = ccrs.PlateCarree()._as_mpl_transform(ax)` to take your advice here? – Man Feb 21 '21 at 20:36
  • This is very useful! Unfortunately it doesn't work when you set `central_longitude=` in the CRS; I needed to adjust the `x` coordinates manually in that situation. – shadowtalker Aug 17 '22 at 07:37