I'm trying to wrap my head around matplotlib's basemap API. What I'd like to do is plot latitude and longitude data of a very small area (couple of km in either direction) on a cylindrical projection.
The problem is, I'm unable to understand how llcrnrlon, llcrnrlat, urcrnrlon and urcrnrlat parameters to the Basemap constructor work.
As far as I understand it, the llcrnrlon is the west-most longitude and llcrnrlat is the south-most latitude and urcrnrlon and urcrnrlat are the east-most and north-most longitude and latitude respectively.
In all these cases, given a set of coordinates, the (numerically) smallest longitudes are the west-most and the smallest latitudes are the south-most and vice-versa. Is this understanding correct?
I'm able to get the plot working by setting xlim and ylim on the underlying Axes object, but using the same values in the basemap constructor seem to push my data off the plot.
EDIT: See code below for a reproduction of the problem:
from matplotlib import pyplot
from mpl_toolkits import basemap
import random
lat_bounds = 52.063443, 52.072587
long_bounds = 1.010408, 1.024502
ax1 = pyplot.subplot(121)
ax2 = pyplot.subplot(122)
ax1.set_title('With ll* rr*')
ax2.set_title('With default values')
my_map1 = basemap.Basemap(projection='cyl', llcrnrlat=lat_bounds[0], llcrnrlon=long_bounds[0],
urcrnrlat=lat_bounds[1], urcrnrlon=long_bounds[1], ax=ax1)
my_map2 = basemap.Basemap(projection='cyl', ax=ax2)
data_lats = [random.uniform(*lat_bounds) for i in xrange(50)]
data_lons = [random.uniform(*long_bounds) for i in xrange(50)]
my_map1.plot(data_lats, data_lons)
my_map2.plot(data_lats, data_lons)
pyplot.show()
In the figures below, the right hand side image is made by using Basemap(projection='cyl') and the left hand side image is made by using Basemap(projection='cyl', llcrnrlat=lat_bounds[0], llcrnrlon=long_bounds[0], urcrnrlat=lat_bounds[1], urcrnrlon=long_bounds[1], ax=ax1)
Notice the dot in the right hand side image, which when zoomed using the matplotlib toolbar becomes the second image.
The problem in your example code is that you are passing the arguments to Basemap.plot() the wrong way around. The arguments to Basemap.plot are exactly the same as those to matplotlib.pyplot.plot, i.e.:
plot(x,y,*args,**kwargs)
In cylindrical coordinates, longitude is the x-coordinate and latitude is the y-coordinate, so you should do mymap1.plot(data_lons, data_lats). The reason it seemed to work in your second example is that longitudes of ~52 and latitudes of ~1 make sense. The points were plotted, but somewhere far away from your domain. If you panned the window of ax1 far enough, you would have seen them (which is the same as doing ax.set_xlim(lat_bounds) and ax.set_ylim(lon_bounds)).
Related
I am using matplotlib.pyplot and astropy to build a plot in galactic coordinates and my goal is to show the density of stars in the sky.
For that, the only data I have is a two-column table with the coordinates of the stars in Right Ascension (RA) and Declination (Dec).
Right now my code is doing the following:
import astropy.coordinates as coord
import matplotlib.pyplot as plt
import astropy.units as u
coordinates = coord.SkyCoord(ra=RA*u.deg, dec=DEC*u.deg)
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection="aitoff")
ax.plot(coordinates.galactic.l.wrap_at('180d').radian,
coordinates.galactic.b.radian, 'k.', alpha=0.01, ms=1)
ax.grid(True)
So for now I am basically using plt.plot to plot all datapoints (which in the case is half-million datapoints) using a very low alpha and symbol size and the plot looks like this:
However, this isn't the plot I want, as the colour scale quickly saturates.
My question is: Is there a way of making a similar plot but properly reflecting the density of datapoint in the z-axis (color)? For example, I want to be able of controling the color table for a given number-density of sources.
I've seen some answers to similar questions are available.
For example, this question (Plotting a heatmap in galactic coordinates) does a similar thing, but for a specific z-axis described by some data.
I am also aware of this question (How can I make a scatter plot colored by density in matplotlib?) and I tried each solution in this post, but they all failed since I am using a subplot which already has a projection.
Any ideas?
For a project I need to create a visualization that draws a circle around some locations on a map. The visualization used Cartopy v.0.18.0 to render the map. It uses the GoogleTiles class to fetch and display the tiles in the relevant region, and the add_patch(Patch.Circle(..., transform=ccrs.PlateCarree())) method to draw the circle.
tiles = GoogleTiles()
fig = plt.figure(figsize=(15,15))
ax = fig.add_subplot(1, 1, 1, projection=tiles.crs)
ax.set_extent((-121.8,-122.55,37.25,37.85))
ax.add_image(tiles, 11)
ax.add_patch(Patch.Circle(xy=[-122.4015173428571, 37.78774634285715], radius = 0.021709041989311614 + 0.005, alpha=0.3, zorder=30, transform=ccrs.PlateCarree()))
plt.show()
However, although I tried several transform objects I either got a ellipse instead of a circle (e.g. using ccrs.PlateCarree()) or no circle at all (e.g. using ccrs.Mercator()).
I found several different solutions online (e.g. Drawing Circles with cartopy in orthographic projection), however, these were not for the Mercator projection and I sadly lack the projection/transformation knowledge to adapt these to my problem.
The only way I was able to produce a circular patch, was when I set the projection parameter on fig.add_subplot to ccrs.PlateCarree(). This, however, distorts the map and the labels become blured, so this is sadly not an acceptable solution.
As the project is due soon, a speedy reply would be much appreciated.
Thanks #swatchai this was the missing hint, so for those intested the code looks like this right now, and it does work! Hooray!
tiles = GoogleTiles()
fig = plt.figure(figsize=(15,15))
ax = fig.add_subplot(1, 1, 1, projection=tiles.crs)
ax.set_extent((-121.8,-122.55,37.25,37.85))
ax.add_image(tiles, 11)
# The diameter is in degrees in EPSG:4326 coordinates therefore, the degrees have
# to be converted to km. At 37N the degree latitude is 11.0977 km.
ax.tissot(rad_km=(0.021709041989311614 + 0.005) * 11.0977, lons=[-122.4015], lats=[37.7877], alpha=0.3)
plt.show()
When executing the above code the following warning is thrown but it has visible effect on the result:
/opt/conda/lib/python3.8/site-packages/cartopy/mpl/geoaxes.py:761: UserWarning: Approximating coordinate system <cartopy._crs.Geodetic object at 0x7fa4c7529770> with the PlateCarree projection.
warnings.warn('Approximating coordinate system {!r} with the '
So thanks again #swatchai you saved my day!
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.
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
I am trying to create a simple stereographic sun path diagram similar to these:
http://wiki.naturalfrequency.com/wiki/Sun-Path_Diagram
I am able to rotate a polar plot and set the scale to 90. How do I go about reversing the y-axis?
Currently the axis goes from 0>90, how do I reverse the axis to 90>0 to represent the azimuth?
I have tried:
ax.invert_yaxis()
ax.yaxis_inverted()
Further, how would I go about creating a stereographic projection as opposed to a equidistant?
My code:
import matplotlib.pylab as plt
testFig = plt.figure(1, figsize=(8,8))
rect = [0.1,0.1,0.8,0.8]
testAx = testFig.add_axes(rect,polar=True)
testAx.invert_yaxis()
testAx.set_theta_zero_location('N')
testAx.set_theta_direction(-1)
Azi = [90,180,270]
Alt= [0,42,0]
testAx.plot(Azi,Alt)
plt.show()
Currently my code doesn't seem to even plot the lines correctly, do I need need to convert the angle or degrees into something else?
Any help is greatly appreciated.
I finally had time to play around with matplotlib. After much searching, the correct way as Joe Kington points out is to subclass the Axes. I found a much quicker way utilising the excellent basemap module.
Below is some code I have adapted for stackoverflow. The sun altitude and azimuth were calculated with Pysolar with a set of timeseries stamps created in pandas.
import matplotlib.pylab as plt
from mpl_toolkits.basemap import Basemap
import numpy as np
winterAzi = datafomPySolarAzi
winterAlt = datafromPySolarAlt
# create instance of basemap, note we want a south polar projection to 90 = E
myMap = Basemap(projection='spstere',boundinglat=0,lon_0=180,resolution='l',round=True,suppress_ticks=True)
# set the grid up
gridX,gridY = 10.0,15.0
parallelGrid = np.arange(-90.0,90.0,gridX)
meridianGrid = np.arange(-180.0,180.0,gridY)
# draw parallel and meridian grid, not labels are off. We have to manually create these.
myMap.drawparallels(parallelGrid,labels=[False,False,False,False])
myMap.drawmeridians(meridianGrid,labels=[False,False,False,False],labelstyle='+/-',fmt='%i')
# we have to send our values through basemap to convert coordinates, note -winterAlt
winterX,winterY = myMap(winterAzi,-winterAlt)
# plot azimuth labels, with a North label.
ax = plt.gca()
ax.text(0.5,1.025,'N',transform=ax.transAxes,horizontalalignment='center',verticalalignment='bottom',size=25)
for para in np.arange(gridY,360,gridY):
x= (1.1*0.5*np.sin(np.deg2rad(para)))+0.5
y= (1.1*0.5*np.cos(np.deg2rad(para)))+0.5
ax.text(x,y,u'%i\N{DEGREE SIGN}'%para,transform=ax.transAxes,horizontalalignment='center',verticalalignment='center')
# plot the winter values
myMap.plot(winterX,winterY ,'bo')
Note that currently I am only plotting points, you will have to make sure that line points have a point at alt 0 at sunrise/sunset.