central longitude for NorthPolarStereo - cartopy

I'd like to plot a polar stereographic plot of the Northern Hemisphere with 180 at the bottom of the plot so I can emphasize the Pacific region. I'm using the latest cartopy from git, and can make a polar stereographic plot no problem, but I can't work out how to change which longitude is at the bottom of the plot. I tried setting the longitude extent to [-180, 180] but this doesn't help, and the NorthPolarStereo() doesn't accept any keyword arguments like central_longitude. Is this possible currently?

This feature has now been implemented in Cartopy (v0.6.x). The following example produces two subplots in Northern Hemisphere polar stereographic projections, one with the default settings and one with the central longitude changed:
"""Stereographic plot with adjusted central longitude."""
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from cartopy.examples.waves import sample_data
# read sample data
x, y, z = sample_data(shape=(73, 145))
fig = plt.figure(figsize=(8, 4))
# first plot with default settings
ax1 = fig.add_subplot(121, projection=ccrs.NorthPolarStereo())
cs1 = ax1.contourf(x, y, z, 50, transform=ccrs.PlateCarree(),
cmap='gist_ncar')
ax1.set_extent([0, 360, 0, 90], crs=ccrs.PlateCarree())
ax1.coastlines()
ax1.set_title('Centred on 0$^\circ$ (default)')
# second plot with 90W at the bottom of the plot
ax2 = fig.add_subplot(
122, projection=ccrs.NorthPolarStereo(central_longitude=-90))
cs2 = ax2.contourf(x, y, z, 50, transform=ccrs.PlateCarree(),
cmap='gist_ncar')
ax2.set_extent([0, 360, 0, 90], crs=ccrs.PlateCarree())
ax2.coastlines()
ax2.set_title('Centred on 90$^\circ$W')
plt.show()
The output of this script is:

For Cartopy 0.17 and matplotlib 3.1.1 (Python 3.7), I got an error in set_extent() with the above solution.
It seems that set_extent() only works this way:
ax1.set_extent([-180, 180, 0, 90], crs=ccrs.PlateCarree())
ax2.set_extent([-179, 179, 0, 90], crs=ccrs.PlateCarree())
So, the rotated image needs some weird longitude boundaries..

Related

setting cartopy set_extent for pacific ocean (160E ~ -90W or 160 ~ 270) not working

I'm trying to project the map only in the tropical pacific region which ranges from 160E to -90W (or 160 to 270) like the following example
paco_region = plt.axes(projection=ccrs.PlateCarree())
paco_region.coastlines()
paco_region.set_extent([160,-90,-20,20],crs=ccrs.PlateCarree())
paco_region.gridlines(crs=ccrs.PlateCarree(), draw_labels=True)
plt.show()
The problem is that cartopy is refusing to show the map from 160E (as set to left boundary) to -90W (as set to right boundary). It just shows the map from -90W to 160W (like figure below)
How do I fix this?
You need some coordinate transformation and a little trick to get it done.
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
# define CRS's for our use case
crs0 = ccrs.PlateCarree(central_longitude=0) #for coding data, same as ccrs.PlateCarree()
crs180 = ccrs.PlateCarree(central_longitude=180) #for plotting map in pacific area
# For all plotting, use `crs180`
fig, paco_region = plt.subplots(figsize=(9,5), subplot_kw={'projection': crs180})
#paco_region.stock_img() # background image check-plot
paco_region.coastlines()
paco_region.set_extent([160, 270, -20, 20], crs=crs0)
# Sample plot of users' data
# Just use regular long/lat
lons = [175, 185, 195, 220, 250]
lats = [15, 0, -15, 12, 18]
# ... but specify `transform = crs0` when plot the data.
paco_region.scatter(lons, lats, transform=crs0, color="r")
# For grid-line labelling, use `crs0`
paco_region.gridlines(crs=crs0, draw_labels=True)
plt.show()

Cartopy, set_extent with NorthPoleStereo not working

Using set_extent on Polar Stereo Graphic maps doesn't seem to be working #in a predictable fashion. I am following this Answered StackOverflow example but neither rotation #yields a map. I have set ax1.set_global() to get the data displayed.
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from cartopy.examples.waves import sample_data
# read sample data
x, y, z = sample_data(shape=(73, 145))
fig = plt.figure(figsize=(8, 8))
# first plot with default rotation. Global extent, works fine
ax1 = fig.add_subplot(221, projection=ccrs.NorthPolarStereo())
cs1 = ax1.contourf(x, y, z, 50, transform=ccrs.PlateCarree(),
cmap='gist_ncar')
ax1.set_title('Global')
#next plot setting extent to 0,360,40,90, no display
ax2 = fig.add_subplot(222,
projection=ccrs.NorthPolarStereo())
cs2 = ax2.contourf(x, y, z, 50,
transform=ccrs.PlateCarree(),
cmap='gist_ncar')
ax2.set_extent([0,360,40,90],crs=ccrs.PlateCarree())
ax2.coastlines()
ax2.set_title('Centred on 0$^\circ$ (default)')
#Now resetting set_extent to [-180,180,40,90] strangely works!
ax3 = fig.add_subplot(
223, projection=ccrs.NorthPolarStereo())
cs3 = ax3.contourf(x, y, z, 50, transform=ccrs.PlateCarree(),
cmap='gist_ncar')
ax3.set_extent([-180, 180, 40, 90], crs=ccrs.PlateCarree())
ax3.coastlines()
ax3.set_title('Using -180,180 $^\circ$W')
#but now rotating projection yields just a corner of the map
ax4 = fig.add_subplot(
224,projection=ccrs.NorthPolarStereo(central_longitude=-45))
cs4 = ax4.contourf(x, y, z, 50, transform=ccrs.PlateCarree(),
cmap='gist_ncar')
ax4.set_extent([-180, 180, 40, 90], crs=ccrs.PlateCarree())
ax4.coastlines()
ax4.set_title('Rotated on -45 $^\circ$W')
plt.show()
I was expecting the set_extent to work as documented but there seems to be #a strange interaction between the rotation and the extent
Output
It seems to be an artifact of how CartoPy calculates its bounding boxes. What it does, when given extents in lon/lat, is calculate the 4 corners of the box based on the extent, then project those to the native projection, then figure out the extent from that. The problem is that in the stereographic projection, some of those combinations end of up with no separation in x or y--due to the periodic nature of (especially) longitude.
I can reproduce the math issues with just PyProj, so it's not a problem with the projection math in CartoPy, just a limitation of how it calculates bounds. You can override by using projected coordinates in in set_extent:
ax.set_extent((0, 500000, 0, 500000), crs=ccrs.NorthPolarStereo())
I know that's not ideal, but I can't think of any good ways to calculate what the appropriate bounds is based on box in lon/lat space.

longitude off by 180 degrees with cartopy Orthographic and RotatedPole

I'm trying to place blue dots from the North pole down the prime meridian (longitude=0) but instead see the dots going down the dateline (longitude=180).
The code:
#!/usr/bin/env python
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
Lon = 0
ax = plt.axes( projection=ccrs.Orthographic( central_latitude=70,
central_longitude=Lon) )
ax.set_global()
vector_crs = ccrs.RotatedPole( pole_latitude=90, pole_longitude=Lon )
ax.plot([Lon, Lon, Lon, Lon, Lon, Lon], # longitude
[ 90, 80, 70, 60, 50, 40], # latitude
'bo', markersize=5, transform=vector_crs)
ax.stock_img()
plt.show()
Probably something related to the transforms but I haven't figured out what. Cartopy version 0.14.2, Python 3.6.
I think the problem is coming from the transform projection that you've defined:
ax = plt.axes(projection=vector_crs)
ax.coastlines()
plt.show()
Note that this simple coastlines plot in your transform projection looks like a Plate Carree plot with a central longitude of 180°. With this in mind, let's take a look at plotting your sample data points on a plot with a Plate Carree projection in order to also try simplifying the map you're plotting to:
ax = plt.axes(projection=ccrs.PlateCarree())
ax.plot([Lon, Lon, Lon, Lon, Lon, Lon],
[ 90, 80, 70, 60, 50, 40],
'bo', markersize=5, transform=vector_crs)
ax.stock_img()
plt.show()
As with your example, the points do not appear where we might expect. Finally, let's try using a Plate Carree projection as the transform for your points when plotting them onto an Orthographic map:
ax = plt.axes(projection=ccrs.Orthographic(central_latitude=70,
central_longitude=Lon))
ax.set_global()
ax.plot([Lon, Lon, Lon, Lon, Lon, Lon],
[ 90, 80, 70, 60, 50, 40],
'bo', markersize=5, transform=ccrs.PlateCarree())
ax.stock_img()
plt.show()
This seems to provide more the plot you were looking for.

Compact horizontal guage Matplotlib

How to create a compact horizontal gauge like for example a thermometer for temperature, barometer for pressure using Matplotlib. The scale of the gauge will be split into ranges; each range denoting high-high, high. low and low-low and a pointer reading the value? Is it possible to create such a gauge in matplotlib?
You could use a colorbar.
For example:
import matplotlib.pyplot as plt
import matplotlib as mpl
fig = plt.figure(figsize=(8, 2))
ax = fig.add_axes([0.1, 0.4, 0.8, 0.2])
bounds = [-20, -10, 0, 10, 20]
labels = ('low-low', 'low', 'high', 'high-high')
cmap = mpl.cm.coolwarm
norm = mpl.colors.Normalize(vmin=bounds[0], vmax=bounds[-1])
cb = mpl.colorbar.ColorbarBase(
ax,
cmap=cmap,
norm=norm,
orientation='horizontal',
boundaries=bounds,
label='temperature (degrees celcius)',
)
for i, label in enumerate(labels):
xpos = float((2*i + 1))/(2*len(labels))
ax.annotate(label, xy=(xpos, 0.5), xycoords='axes fraction', ha='center', va='center')
plt.show()
Which produces something like this:
For more info see these examples in the matplotlib docs.

Straight line through the pole with Cartopy stereographic projection

I'm using cartopy to produce a map of the Arctic with stereographic projection and then plotting a line (to show the position of a cross-section) over the top. If I use the following code then the line doesn't go in a straight line through the pole but instead goes along a line of latitude.
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
x=[180,0]
y=[50,50]
ax = plt.axes(projection=ccrs.NorthPolarStereo())
ax.set_extent([0, 360, 50, 90], crs=ccrs.PlateCarree())
ax.plot(x,y,transform=ccrs.PlateCarree())
plt.gca().stock_img()
plt.gca().coastlines()
plt.show()
To get round this I have to change x and y to:
x=[180,180,0,0]
y=[50,90,90,50]
so that there are two data points at the North Pole. Is there a better solution for this?
Edit: Image attached
Thanks,
Tim
#ajdawson's answer is correct. Using the Geodetic transform, in this case, will do the trick.
To understand the reason the line wasn't as you expected it to look, we need to understand what the PlateCarree transform represents.
Firstly, lets observe that all lines drawn in the transform=<projection> form, using Cartopy, should pass through the same Geographic points irrespective of the projection that the line is being drawn on.
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
def main():
x=[180, 180, 0, 0]
y=[50, 90, 90, 50]
# plot2 - North Polar Stereographic
ax = plt.subplot(211, projection=ccrs.NorthPolarStereo())
ax.set_extent([0, 360, 50, 90], crs=ccrs.PlateCarree())
ax.plot(x, y, transform=ccrs.PlateCarree(), color='red', lw=2)
ax.stock_img()
ax.coastlines()
# plot2 - PlateCarree
ax = plt.subplot(212, projection=ccrs.PlateCarree(central_longitude=45))
ax.set_extent([0, 360, -45, 90], crs=ccrs.PlateCarree())
ax.plot(x, y, transform=ccrs.PlateCarree(), color='red', lw=2)
ax.stock_img()
ax.coastlines()
plt.show()
if __name__ == '__main__':
main()
So going back to drawing your original coordinates (which were in PlateCarree coordinates) on a PlateCarree map:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
def main():
x=[180, 0]
y=[50, 50]
ax = plt.axes(projection=ccrs.PlateCarree(central_longitude=45))
ax.set_extent([0, 360, -45, 90], crs=ccrs.PlateCarree())
ax.plot(x, y, transform=ccrs.PlateCarree(), color='red', lw=2)
ax.stock_img()
ax.coastlines()
plt.tight_layout()
plt.show()
if __name__ == '__main__':
main()
You will find that the line passes through the same geographic points as your bad line in the original question.
This should satisfy you that Cartopy is behaving rationally and it is not a bug, but it doesn't answer the question about how you would go about drawing the line you desire.
#ajdawson has already said that, in your case, drawing the line:
plt.plot([180, 0], [50, 50] , transform=ccrs.Geodetic())
will result in the desired output.
That is because the Geodetic coordinate reference system draws the line of shortest distance on the globe between two points. However, there will be a latitude which, when crossed, passing through the north pole does not provide the shortest distance:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
def main():
ax = plt.axes(projection=ccrs.PlateCarree(central_longitude=45))
ax.set_global()
ax.plot([180, 0], [20, 20], transform=ccrs.Geodetic(), color='red', lw=2, label='Latitude = 20')
ax.plot([180, 0], [0, 0], transform=ccrs.Geodetic(), color='blue', lw=2, label='Latitude = 0')
ax.plot([180, 0], [-20, -20], transform=ccrs.Geodetic(), color='yellow', lw=2, label='Latitude = -20')
ax.outline_patch.set_zorder(2)
plt.legend(loc=8, bbox_to_anchor=(0.65, -0.2), shadow=True, fancybox=True)
ax.stock_img()
ax.coastlines()
plt.tight_layout()
plt.show()
if __name__ == '__main__':
main()
Generally, if you wanted to draw a Geodetic line which always crosses the North Pole, then the north pole should be one of the coordinates of the line.
plt.plot([180, 0, 0], [-45, 90, -45] , transform=ccrs.Geodetic())
Finally, just to throw it into the mix, if you just wanted a vertical line in a North Polar Stereographic projection which crosses the North Pole, it is worth remembering that there exists a Cartesian coordinate system (in which it is worth remembering that the numbers are not latitude and longitudes), so simply doing:
ax = plt.axes(projection=ccrs.NorthPolarStereo())
plt.axvline()
Will also do the trick! (but is less transferable than the Geodetic approach)
Wow, my answer got long. I hope your still with me and that makes the whole PlateCarree thing clearer!
I think you need to use the Geodetic transform when plotting this section rather than Plate Carree:
<!-- language: lang-py -->
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
x=[180,0]
y=[50,50]
ax = plt.axes(projection=ccrs.NorthPolarStereo())
ax.set_extent([0, 360, 50, 90], crs=ccrs.PlateCarree())
ax.plot(x,y,transform=ccrs.Geodetic())
ax.stock_img()
ax.coastlines()
plt.show()
The result looks like this:
I think that is the correct way to handle this anyway!
Andrew