Cartopy map fill entire axis - python

I would like to plot data on a grid (which is in LCC projection) with Cartopy, so that the data fills the entire axis (and also axes, but that is not the issue here).
To make it clearer, here is what I do with Cartopy:
import cartopy.crs as ccrs
import numpy as np
import pyproj as p4
from mpl_toolkits.basemap import Basemap
lalo = #read latitudes and longitudes of my grid defined in a special LCC projection (below)
lat = np.reshape(lalo[:,1],(ny,nx))
lon = np.reshape(lalo[:,0],(ny,nx))
minlat = lat[0,0]
maxlat = lat[-1,-1]
minlon = lon[0,0]
maxlon = lon[-1,-1]
Z = np.ones((ny,nx)) #some data
#grid definition for cartopy:
myproj = ccrs.LambertConformal(central_longitude=13.3333, central_latitude=47.5,
false_easting=400000, false_northing=400000,
secant_latitudes=(46, 49))
fig = plt.figure()
ax = plt.axes(projection = myproj)
plt.contourf(lon, lat, Z)#, transform=myproj)
#no difference with transform option as lon,lat are already in myproj projection
The result is an image which does not fill the entire axis, but looks like this:
When using Basemap like this:
a=6377397.155
rf=299.1528128
b= a*(1 - 1/rf)
m = Basemap(projection='lcc', resolution='h', rsphere=(a,b),
llcrnrlon=minlon,llcrnrlat=minlat,urcrnrlon=maxlon,urcrnrlat=maxlat,
llcrnrx=400000, llcrnry=400000,
lat_1=46, lat_2=49, lat_0=47.5, lon_0=13.3333, ax=ax)
x,y = m(lon,lat)
m.contourf(x,y,Z)
I get the following (desired) image:
And finally, when using proj4 to convert lon and lat using this definition p4.Proj('+proj=lcc +lat_1=46N +lat_2=49N +lat_0=47.5N +lon_0=13.3333 +ellps=bessel +x_0=400000 +y_0=400000') I again get the desired image:
Is there any possibility to achieve this in cartopy as well?
In other words, I would like to have a plot where the data shows up in a perfect rectangle, and the background map is distorted accordingly, i.e. something like the opposite of this example (cannot install iris package, otherwise I would have tried with this example)
I have tried a few things like:
building a custom class for my projection as done here, just to be sure that the parameters are all set correctly (as in my proj4 definition).
played around with aspect ratios, but they only affect the axes not the axis,
and a few more things.
Any help is greatly appreciated!

The important piece of information that is missing here is that your data is in lats and lons, not in the Cartesian transverse Mercator coordinate system. As a result you will need to use a Cartesian coordinate system which speaks lats and lons (spherical contouring has not been implemented at this point). Such a coordinate system exists in the form of the PlateCarree crs - so simply passing this as the transform of the contoured data should put your data in the right place.
plt.contourf(lon, lat, Z, transform=ccrs.PlateCarree())
This really highlights the fact that the default coordinate system of your data, is the same as that of the map, which in most cases is not longitudes and latitudes - the only way to change the CRS of your data is by passing the transform keyword.
HTH

Related

Making cartopy figure apply transform to just one axis

I have data in x, z, where x is longitude in degrees, and z is an altitude in km.
I'd like to be able to use Cartopy's "intelligent" handling of the longitude axis (namely, the ability to center the data at any chosen point lon0, which handles the data wrapping across the periodic boundary).
if my coordinates were lat, lon, this is easy, and involves sending a projection argument to the axis object, and a transform argument to the plot object:
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
fig = plt.figure()
ax = fig.add_subplot(111, projection=ccrs.PlateCarree())
ax.plot(lat, lon, data, transform=ccrs.PlateCarree())
I think what I need to be able to do is effectively only apply the transform to the x-axis. In other words, I'd like to plot vertical cross sections with the power of Cartopy, rather than the standard use case of horizontal cross sections.
Edit: perhaps all I need to is use a polar transform on the data, where $x$ is interpreted as the angular coordinate. But I would need to do this transformation without doing the associated projection (I still want my plot to be a rectangle, I just want to the data to behave in a period manner)

Python: Overlaying shapefile with np.array data

I have a shapefile of the United states, and I have an m x n array of Cartesian data that represents temperature at each pixel. I am able to load in the shapefile and plot it:
import shapefile as shp
import matplotlib.pyplot as plt
sf = shp.Reader("/path/to/USA.shp")
plt.figure()
for shape in sf.shapeRecords():
for i in range(len(shape.shape.parts)):
i_start = shape.shape.parts[i]
if i==len(shape.shape.parts)-1:
i_end = len(shape.shape.points)
else:
i_end = shape.shape.parts[i+1]
x = [i[0] for i in shape.shape.points[i_start:i_end]]
y = [i[1] for i in shape.shape.points[i_start:i_end]]
plt.plot(x,y, color = 'black')
plt.show()
And I am able to read in my data and plot it:
import pickle
from matplotlib import pyplot as mp
Tfile = '/path/to/file.pkl'
with open(Tfile) as f:
reshapeT = pickle.load(f)
mp.matshow(reshapeT)
The problem is reshapeT has dimensions of 536 x 592, and is a subdomain of the US. However, I do have information about the top-left corner of the reshapeT grid (lat / long) as well as the spacing between each pixel (0.01)
My question is: How do I overlay the reshapeT data ontop of the shapefile domain?
If I understand you correctly you would like to overlay a 536x592 numpy array over a specifc part of a plotted shapefile. I would suggest you use Matplotlib's imwshow() method, with the extent parameter, which allows you to place the image within the plot.
Your way of plotting the shapefile is fine, however, if you have the possibility to use geopandas, it will dramatically simplify things. Plotting the shapefile will reduce to the following lines:
import geopandas as gpd
sf = gpd.read_file("/path/to/USA.shp")
ax1 = sf.plot(edgecolor='black', facecolor='none')
As you have done previously, let's load the array data now:
import pickle
Tfile = '/path/to/file.pkl'
with open(Tfile) as f:
reshapeT = pickle.load(f)
Now in order to be able to plot numpy array in the correct position, we first need to calculate its extent (the area which it will cover expressed in coordinates). You mentioned that you have information about the top-left corner and the resolution (0.01) - that's all we need. In the following I'm assuming that the lat/lon information about the top-left corner is saved in the the top_left_lat and top_left_lon variables. The extent needs to be passed in a tuple with a value for each of the edges (in the order left, right, bottom, top).
Hence, our extent can be calculated as follows:
extent_mat = (top_left_lon, top_left_lon + reshapeT.shape[1] * 0.01, top_left_lat - reshapeT.shape[0] * 0.01, top_left_lat)
Finally, we plot the matrix onto the same axes object, ax1, on which we already plotted the shape file to the calculated extent:
# Let's turn off autoscale first. This prevents
# the view of the plot to be limited to the image
# dimensions (instead of the entire shapefile). If you prefer
# that behaviour, just remove the following line
ax1.autoscale(False)
# Finally, let's plot!
ax1.imshow(reshapeT, extent=extent_mat)

Plotting projected data in other projectons using cartopy

This question is in regards to plotting some data I have that uses the Lambert Conformal (LCC) CRS. While these questions specifically pertain to plotting LCC data in multiple projections, it also applies to the use of cartopy in general in that I would like to better understand the logic/process of plotting using cartopy.
Below are some code examples of what I am trying to do. The first example is simply plotting some LCC data. The data I used are available in the link here.
import cartopy.crs as ccrs
import cartopy.feature as cf
import matplotlib.pyplot as plt
import numpy as np
proj = ccrs.LambertConformal(central_latitude = 25,
central_longitude = 265,
standard_parallels = (25, 25))
# Data and coordinates (from download link above)
with np.load('nam_218_20120414_1200_006.npz') as nam:
dat = nam['dpc']
lat = nam['lat']
lon = nam['lon']
ax = plt.axes(projection = proj)
ax.pcolormesh(lon, lat, dat, transform = ccrs.PlateCarree())
ax.add_feature(cf.NaturalEarthFeature(
category='cultural',
name='admin_1_states_provinces_lines',
scale='50m',
facecolor='none'))
ax.coastlines('50m')
ax.add_feature(cf.BORDERS)
plt.show()
The plot produced can be seen here:
US Dewpoints on LCC Map
My first confusion when using cartopy was why I always have to transform to PlateCarree when plotting? My initial thought was the transform keyword of the pcolormesh call needed the LCC projection information and not PlateCarree.
Next, if I want to plot my LCC data in another projection, e.g. Orthographic, would I go about doing so like below?
# First, transform from LCC to Orthographic
transform = proj.transform_points(ccrs.Orthographic(265,25), lon, lat)
x = transform[..., 0]
y = transform[..., 1]
ax = plt.axes(projection = ccrs.Orthographic(265,25))
ax.pcolormesh(x, y, dat, transform = ccrs.PlateCarree())
ax.add_feature(cf.NaturalEarthFeature(
category='cultural',
name='admin_1_states_provinces_lines',
scale='50m',
facecolor='none'))
ax.coastlines('50m')
ax.add_feature(cf.BORDERS)
ax.set_global()
The plot produced can be seen here:
US Dewpoints on Orthographic Map
I think the Orthographic map looks right, but I'd like to be sure that I understand the process of re-projection with cartopy correctly.
In summary, I would like to know the following things:
Do you always have to transform to PlateCarree when plotting? Why or why not?
Does re-projecting simply require a call to the transform_points method or are there other steps involved?
Update 1
Based on the answer from #swatchai, it seems as though the answer to my Question 2 is that transform_points is not required. One can simply use the transform keyword argument in many matplotlib plotting methods. This is what I thought originally. However, skipping the transform_points has not worked for me. See example below:
ax = plt.axes(projection = ccrs.Orthographic(265,25))
ax.pcolormesh(lon, lat, dat, transform = proj)
ax.add_feature(cf.NaturalEarthFeature(
category='cultural',
name='admin_1_states_provinces_lines',
scale='50m',
facecolor='none'))
ax.coastlines('50m')
ax.add_feature(cf.BORDERS)
ax.set_global()
Which produces this plot:
Orthographic Plot Without transform_points Step
The problem appears to be that the lat and lon input does not get transformed into the grid coordinates so they only get plotted in an extremely small area of the plot. So, to expand upon Question 2, if you are supposed to skip transform_points is there a bug in cartopy's plotting methods based on my above example? Or am I still missing a step?
An important distinction needs to be made between geographic and projected (or grid) coordinates. A more detailed description of those can be found here. The important thing, and what helps to answer Question 1, is that latitude and longitude are geographic coordinates whereas points that have units in meters are projected coordinates.
The numerical weather model where the example data came from uses the Lambert Conformal projection in its calculations (more here). However, the coordinates that get output are latitude and longitude. If you are inexperienced with spatial data, you can end up thinking that the lat/lon pairs are LCC projected coordinates when they are in fact geogrphic coordinates; the LCC stuff is used during model integration.
To answer Question 1, no, you do not always have to use PlateCarree as the source CRS. You do, however, always use PlateCarree for latitude and longitude data (which was the case here). This way cartopy will correctly transform your lat/lon values into projected coordinates (in meters) and be able to easily transform your data to other projections during plotting. This issue is ultimately the reason for the seemingly blank plot in Update 1. By saying the source data have LCC projected coordinates in transform, cartopy took the lat/lon input and interpreted them as having units of meters. The data did get plotted, but the extent was so small that it was impossible to see them without changing the plot extent to be the same as the data.
With regard to Question 2, no, using transform_points is not a requirement. cartopy was set up in such a way to make is easy to plot in multiple projections with minimal intermediary steps. As #swatchai mentioned, sometimes you may want to use the actual projected coordinates and using the transform_points method will allow you to do that. When transform_points was used to produce the second plot in the original post it essentially was manually doing what would have automatically been done had the input coordinates been handled properly with PlateCarree in transform.
Finally, an important clarification was made by #ajdawson with regard to how to use projection and transform when plotting. Once you understand what you have for source coordinates, this information is also useful. The comment is quoted below:
In general, projection tells cartopy what the drawn map should look like, and transform tells cartopy what coordinate system your data is represented in. You can set projection to any projection you like, but transform needs to match whatever coordinate system your data uses.
In Cartopy, ccrs.PlateCarree() is the most basic map projection, sometimes called un-projected projection, that is, a geo position (lat,long) in degrees -> becomes grid values y=lat; x=long on a PlateCarree map.
This snippet code:
import cartopy.crs as ccrs
axm = plt.axes( projection = ccrs.xxxx() )
creates an axis axm for plotting map in xxxx projection. When you plot data on axm, the default coordinates are grid (x,y) of that projection (usually in meters unit). That is why you need transform=ccrs.PlateCarree() to declare that your input (x,y) are indeed in (long,lat) degrees, or in other words, in (x,y) of PlateCarree grid coordinates.
If your target projection is Orthographic while data is LambertConformal,
ax = plt.axes(projection = ccrs.Orthographic(265,25))
lccproj = ccrs.LambertConformal(central_latitude = 25,
central_longitude = 265,
standard_parallels = (25, 25))
You can plot the data with
ax.pcolormesh(x, y, dat, transform = lccproj)
No need to use transform_points() at all when you do the plotting. But it is useful when you want access to the transformed coordinates in some situation.

Astropy matplotlib and plot galactic coordinates

I am trying to make a plot of galactic coordinates using python. Let's say that I have this data:
data = [(0.261, -7.123, 13.03, 'Unidentified'), (-0.326, 77, 13.03, 'Galaxies')]
Where each tuple is of the form (ra, dec, flux, type).
I am asked to use astropy + matplotlib, so:
c = SkyCoord(ra = ra*u.degree, dec = dec*u.degree)
galactic = c.galactic
Here is where my problem arises, I am using this code:
from mpl_toolkits.basemap import Basemap
import numpy as np
import matplotlib.pyplot as plt
# lon_0 is central longitude of projection.
# resolution = 'c' means use crude resolution coastlines.
m = Basemap(projection='hammer',lon_0=0,resolution='c')
m.drawcoastlines()
m.fillcontinents(color='coral',lake_color='aqua')
# draw parallels and meridians.
m.drawparallels(np.arange(-90.,120.,30.))
m.drawmeridians(np.arange(0.,420.,60.))
m.drawmapboundary(fill_color='aqua')
plt.title("Hammer Projection")
plt.show()
However I can't plot the data in galactic coordinates and I don't know why. Also I need that every point is of a different color depending on the type, and of different size depending on the value of flux. I need to achieve something like this (I am kind of new to python and I have never used astropy, I have not found good examples):
Hope someone could help.
Look at the examples at the bottom of http://www.astropy.org/astropy-tutorials/plot-catalog.html. A common problem I run into when plotting Galactic coordinates is that you want to plot from -180 to +180, but the default is to give coordinates from 0 to 360. You can change this with wrap_at, e.g.:
plot(galactic.l.wrap_at(180*u.deg), galactic.b.wrap_at(180*u.deg))

streamplot does not work with matplotlib basemap

I am trying to use streamplot function to plot wind field with basemap, projection "ortho". My test code is mainly based on this example:
Plotting wind vectors and wind barbs
Here is my code:
import numpy as np
import matplotlib.pyplot as plt
import datetime
from mpl_toolkits.basemap import Basemap, shiftgrid
from Scientific.IO.NetCDF import NetCDFFile as Dataset
# specify date to plot.
yyyy=1993; mm=03; dd=14; hh=00
date = datetime.datetime(yyyy,mm,dd,hh)
# set OpenDAP server URL.
URLbase="http://nomads.ncdc.noaa.gov/thredds/dodsC/modeldata/cmd_pgbh/"
URL=URLbase+"%04i/%04i%02i/%04i%02i%02i/pgbh00.gdas.%04i%02i%02i%02i.grb2" %\
(yyyy,yyyy,mm,yyyy,mm,dd,yyyy,mm,dd,hh)
data = Dataset(URL)
#data = netcdf.netcdf_file(URL)
# read lats,lons
# reverse latitudes so they go from south to north.
latitudes = data.variables['lat'][:][::-1]
longitudes = data.variables['lon'][:].tolist()
# get wind data
uin = data.variables['U-component_of_wind_height_above_ground'][:].squeeze()
vin = data.variables['V-component_of_wind_height_above_ground'][:].squeeze()
# add cyclic points manually (could use addcyclic function)
u = np.zeros((uin.shape[0],uin.shape[1]+1),np.float64)
u[:,0:-1] = uin[::-1]; u[:,-1] = uin[::-1,0]
v = np.zeros((vin.shape[0],vin.shape[1]+1),np.float64)
v[:,0:-1] = vin[::-1]; v[:,-1] = vin[::-1,0]
longitudes.append(360.); longitudes = np.array(longitudes)
# make 2-d grid of lons, lats
lons, lats = np.meshgrid(longitudes,latitudes)
# make orthographic basemap.
m = Basemap(resolution='c',projection='ortho',lat_0=60.,lon_0=-60.)
# create figure, add axes
fig1 = plt.figure(figsize=(8,10))
ax = fig1.add_axes([0.1,0.1,0.8,0.8])
# define parallels and meridians to draw.
parallels = np.arange(-80.,90,20.)
meridians = np.arange(0.,360.,20.)
# first, shift grid so it goes from -180 to 180 (instead of 0 to 360
# in longitude). Otherwise, interpolation is messed up.
ugrid,newlons = shiftgrid(180.,u,longitudes,start=False)
vgrid,newlons = shiftgrid(180.,v,longitudes,start=False)
# now plot.
lonn, latt = np.meshgrid(newlons, latitudes)
x, y = m(lonn, latt)
st = plt.streamplot(x, y, ugrid, vgrid, color='r', latlon='True')
# draw coastlines, parallels, meridians.
m.drawcoastlines(linewidth=1.5)
m.drawparallels(parallels)
m.drawmeridians(meridians)
# set plot title
ax.set_title('SLP and Wind Vectors '+str(date))
plt.show()
After running the code, I got a blank map with a red smear in the lower left corner (please see the figure). After zoom this smear out, I can see the wind stream in a flat projection (not in "ortho" projection) So I guess this is the problem of data projection on the map. I did tried function transform_vector but it does not solve the problem Can anybody tell me, what did I do wrong, please! Thank you.
A new map after updating code:
You are plotting lat/lon coordinates on a map with an orthographic projection. Normally you can fix this by changing your plotting command to:
m.streamplot(mapx, mapy, ugrid, vgrid, color='r', latlon=True)
But your coordinate arrays don't have the same dimensions, that needs to be fixed as well.

Categories

Resources