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)
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?
Good Afternoon All,
I'm attempting to create a contour map of surface elevation by using drilling data from a mineral exploration programme. I am new to programming, any feedback would be welcomed!
Each drill hole has a:
hole id
x co-ordinate (Easting)
y co-ordinate (Northing)
z value (surface elevation).
An excerpt of the data is as follows:
Methodology
I broke the work down into two steps.
1) Checking that the data plots in the correct area
I used pandas to extract the co-ordinates of each drilling hole from the csv file, and plotted the data using plt.scatter from matplotlib.
This is my output. So far it works, so now I want to plot the 3D (z axis) data.
2) Plotting of Surface_Elevation (z axis)
This is where I am having problems. I've read through several contouring guides for matplotlib which is dependent on plt.contour. The issue is that this function wants a 2D array, and the data that I want to contour is 1D. Am I missing something here?
My attempt
import matplotlib.pyplot as plt # plot data
import pandas as pd # extract data from csv
# access csv and assign as a variable
dataset = pd.read_csv('spreadsheet.csv')
# x_axis values extracted and converted to a list from the csv
x_axis = list(dataset["Orig_East"])
# y_axis values extracted and converted to a list from the csv
y_axis = list(dataset["Orig_North"])
# z_axis values extracted and converted to a list from the csv
z_axis = list(dataset["Surface_Elevation"])
plt.contour(x_axis, y_axis, z_axis, colors='black');
plt.ticklabel_format(useOffset=False, style='plain') # remove exponential axis labels
plt.xlabel('Easting') # label x axis
plt.ylabel('Northing') # label y axis
plt.title('Surface Elevation') # label plot
# plot graph
plt.show()
A possible solution is to encode the elevation of each point into the color of the scatter marker. This can be done by calling plt.scatter(x, y, c=z)
you can also specify a desired cmap, see the documentation.
I am working on a visualization script for a linear algebra class at the university and I am trying to show multiple vectors using the quiver function in python. I am trying to plot vectors coming from a 2x2 matrix in one quiver function, however, now that I am trying to label them I would like to access each vector individually.
import numpy as np
import matplotlib.pyplot as plt
A = np.array([[1,3], [2,2]])
# create figure
fig = plt.figure()
# creates variable containing current figure
ax = fig.gca()
baseArrow = ax.quiver(*origin, A[0,:], A[1,:], color=['r','g']', angles='xy', scale_units='xy', scale=1)
ax.quiverkey(baseArrow,.85,.85,0.8,'i-hat',labelcolor='k',labelpos='S', coordinates = 'figure')
# display grid
plt.grid()
# display figure
plt.show()
This alows me to label the first vector with the respective color (red). Now what I would like to do is label the second vector in green with a different label?
Maybe something like:
ax.quiverkey(baseArrow**[2]**,.85,.85,0.8,'i-hat',labelcolor='k',labelpos='S', coordinates = 'figure')
Is there any way to pull out each vector by itself or would it be better to plot them individually instead of as a vector? I looked at the following question but it doesn't really solve my issue. Matplotlib Quiver plot matching key label color with arrow color
My feeling is that the quiver function is better suited/intended to plot numerous vectors as you would find in a graph depicting magnetic forces, vortices (sic) or gradients (see meshgrid for example). And it's API reflects that, in that it accepts end and start coordinates separately: i.e. you need to split the components of your vectors as you have done above.
May I suggest you look into the plot or arrow functions which will give you greater control over your visualization (e.g. vector-independent labels) and will also provide greater clarity in your code, as you will be able to declare vectors (as np.arrays of course) and use them directly.
Finally note that you can obtain fig and ax in one call: fib, ax = plt.subplots().
Hope this helps!
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