Why the annotate worked unexpected here in cartopy? - matplotlib

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:
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?
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?
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?

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()
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,

Related

Draw an ordinary plot with the same style as in plt.hist(histtype='step')

The method plt.hist() in pyplot has a way to create a 'step-like' plot style when calling
plt.hist(data, histtype='step')
but the 'ordinary' methods that plot raw data without processing (plt.plot(), plt.scatter(), etc.) apparently do not have style options to obtain the same result. My goal is to plot a given set of points using that style, without making histogram of these points.
Is that achievable with standard library methods for plotting a given 2-D set of points?
I also think that there is at least one hack (generating a fake distribution which would have histogram equal to our data) and a 'low-level' solution to draw each segment manually, but none of these ways seems favorable.
Maybe you are looking for drawstyle="steps".
import numpy as np; np.random.seed(42)
import matplotlib.pyplot as plt
data = np.cumsum(np.random.randn(10))
plt.plot(data, drawstyle="steps")
plt.show()
Note that this is slightly different from histograms, because the lines do not go to zero at the ends.

Cartopy plot high/low sea level pressure on map

I'm migrating from basemap to cartopy. One thing I would like to do is plot high/low pressure on a map, such as in basemap. There is a good example on this page of how to do this: https://matplotlib.org/basemap/users/examples.html ("Plot sea-level pressure weather map with labelled highs and lows"). I'm not going to copy and paste the code from this site, but would like to know how to do the same in cartopy. The main thing I can't get my head around is how to do m.xmax and x > m.xmin and y < m.ymax and y > m.ymin in cartopy (some kind of vector transform I'd imagine.
I've had a good look and can't see this particular example translated into something compatible with cartopy. Any help would be welcome!
In order to write an equivalent program using cartopy you need to be able to translate two concepts. The first is finding the extent of a projection, this can be done with the get_extent() method of a GeoAxes:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
my_proj = ccrs.Miller(central_longitude=180)
ax = plt.axes(projection=my_proj)
xmin, xmax, ymin, ymax = ax.get_extent()
You also need to transform coordinate points from geographic to projection coordinates, which is the function of the transform_points() method of a coordinate reference system instance:
import numpy as np
lons2d, lats2d = np.meshgrid(lons, lats) # lons lats are in degrees
transformed = my_proj.transform_points(ccrs.Geodetic(), lons2d, lats2d)
x = transformed[..., 0] # lons in projection coordinates
y = transformed[..., 1] # lats in projection coordinates
Now you can use the same technique as in the basemap example to filter and plot points, where instead of m.xmin you use xmin etc.
There are of course alternate ways of doing this which have pros and cons relative to the basemap example. If you come up with something nice you can contribute it to the Cartopy gallery.

Cartopy AzimuthalEquidistant projection: zooming into a region and coastlines

I am trying to plot some data on an AzimuthalEquidistant projection using cartopy. However, it gives me a couple of problems. First the coastlines no longer show for this type of projection. Not sure if this is my code or a Cartopy problem. I also notice that if I use a ccrs.PlateCarree() transform in the pcolormesh command the coastlines do show but then, presumably, my data is on the wrong type of prejection?
Second I would prefer if the axis boarder was circular after plotting the data, is it possible to use set_extent or some similar function to do this?
The code below should reproduce the problems, the circle shows how I would like the boarder to look.
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import matplotlib.patches as mpatches
clat = 55.0
clon = -8.0
lons = np.arange(clon-15,clon+16,0.5)
lats = np.arange(clat-15,clat+16,0.5)
d = np.random.rand(lons.shape[0],lats.shape[0])
trans = ccrs.AzimuthalEquidistant(central_latitude=clat, central_longitude=clon)
ax = plt.axes(projection=trans)
ax.coastlines(resolution='10m')
CB=ax.pcolormesh(lons-0.25, lats-0.25, d.T,
cmap=plt.cm.viridis, alpha=0.5,
transform=trans)#ccrs.PlateCarree())
p1 = mpatches.Circle((clon,clat), radius=15, color='k', lw=5, fill=False,
transform=trans)
ax.add_patch(p1)
If the data you are plotting is in latitude/longitude coordinates then the correct value for the transform keyword is indeed ccrs.PlateCarree(). This is common gotcha for new users. The transform argument tells cartopy what coordinates your data are in, and is completely independent of the projection you want to plot onto.
To make the plot circular you'll need to set the boundary yourself. The Cartopy documentation have a couple of examples of this: http://scitools.org.uk/cartopy/docs/latest/examples/always_circular_stereo.html and http://scitools.org.uk/cartopy/docs/latest/examples/star_shaped_boundary.html.

How do I match the projection of my cartopy map with that of a shapefile?

I am trying to synthesise the projections of a coastlines() map with that of a shapefile, whose .prj file says:
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",
SPHEROID["WGS_1984",6378137.0,298.257223563]],
PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]
My attempt is:
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from cartopy.io import shapereader
# set up a map with coastlines around Auckland:
plt.figure(figsize=(10, 10))
platecarree = ccrs.PlateCarree(globe=ccrs.Globe(datum='WGS84'))
ax = plt.axes(projection=platecarree)
extent = [174.25, 175.25, -37.5, -36.5]
ax.set_extent(extent)
ax.coastlines('10m',color='red')
# read in shapefile and plot the polygons:
shp2 = shapereader.Reader('auckland_geology_wgs84gcs.shp')
formations = shp2.records()
for formation in formations:
# plot water blue, and all other rocks yellow
if formation.attributes['MAIN_ROCK'] == b' ':
ax.add_geometries(formation.geometry, ccrs.PlateCarree(),facecolor='blue',alpha=.1)
else:
ax.add_geometries(formation.geometry, ccrs.PlateCarree(), facecolor='yellow',alpha=.1)
plt.show()
I tried giving the globe parameter in my platecarree definition the radius and inverse flattening from the prj file, but I didn't see any change to the output if I set or even varied those numbers.
In addition, with the defined "platecarree" projection (with the call to the globe with WGS84) as the crs in the add_geometries calls, my output is blank.
As is, the result looks to me like a projection mismatch
I've tried to reproduce your problem using QGIS and data downloaded from Natural Earth (10m coastlines) and from GADM (NZ adm0 level). It looks like the NE10m coastlines are the culprit ! The GADM aligns perfectly with your geology layer, while the NE10m is off (and deformed). screenshot of QGIS with Geological map & coastlines

Matplotlib annotate doesn't work on log scale?

I am making log-log plots for different data sets and need to include the best fit line equation. I know where in the plot I should place the equation, but since the data sets have very different values, I'd like to use relative coordinates in the annotation. (Otherwise, the annotation would move for every data set.)
I am aware of the annotate() function of matplotlib, and I know that I can use textcoords='axes fraction' to enable relative coordinates. When I plot my data on the regular scale, it works. But then I change at least one of the scales to log and the annotation disappears. I get no error message.
Here's my code:
plt.clf()
samplevalues = [100,1000,5000,10^4]
ax = plt.subplot(111)
ax.plot(samplevalues,samplevalues,'o',color='black')
ax.annotate('hi',(0.5,0.5), textcoords='axes fraction')
ax.set_xscale('log')
ax.set_yscale('log')
plt.show()
If I comment out ax.set_xcale('log') and ax.set_ycale('log'), the annotation appears right in the middle of the plot (where it should be). Otherwise, it doesn't appear.
Thanks in advance for your help!
It may really be a bug as pointed out by #tcaswell in the comment but a workaround is to use text() in axis coords:
plt.clf()
samplevalues = [100,1000,5000,10^4]
ax = plt.subplot(111)
ax.loglog(samplevalues,samplevalues,'o',color='black')
ax.text(0.5, 0.5,'hi',transform=ax.transAxes)
plt.show()
Another approach is to use figtext() but that is more cumbersome to use if there are already several plots (panels).
By the way, in the code above, I plotted the data using log-log scale directly. That is, instead of:
ax.plot(samplevalues,samplevalues,'o',color='black')
ax.set_xscale('log')
ax.set_yscale('log')
I did:
ax.loglog(samplevalues,samplevalues,'o',color='black')