I'm making wind vector barb plots using the matplotlib barb function and basemap in python.
I have a list of vectors (wind observations) at arbitrary latitudes and longitudes, i.e. not on a regular grid.
I need to rotate the vectors onto the map projection before plotting or the barbs point in the wrong direction. What is the best way to do this?
e.g.
import numpy
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
# Define locations of my vectors
lat = numpy.array([50.1,46.2,51.6,52.2,54.4])
lon = numpy.array([-3.3,-1.0,-5.2,-1.2,0.2])
# Define some east-west vectors to illustrate the problem
u = numpy.array([5,5,5,5,5])
v = numpy.array([0,0,0,0,0])
# Set up map projection
m = Basemap(llcrnrlon=-15.,llcrnrlat=46.,urcrnrlon=15.,urcrnrlat=59.,
projection='lcc',lat_1=40.,lat_2=50.,lon_0=-50.,
resolution ='l')
# Calculate positions of vectors on map projection
x,y = m(lon,lat)
# Draw barbs
m.barbs(x,y,u,v, length=7, color='red')
# Draw some grid lines for reference
parallels = numpy.arange(-80.,90,20.)
meridians = numpy.arange(0.,360.,20.)
m.drawparallels(parallels)
m.drawmeridians(meridians)
m.drawcoastlines(linewidth=0.5)
plt.show()
Note that in the plot, the vectors do not point east-west.
I have tried using the rotate_vector and transform_vector routines, but these only work for gridded vector data.
Is there a routine to rotate the vectors onto the map projection for an arbitrary list of lat,lon u,v pairs?
Any help would be much appreciated!
For people with gridded data who stumpled upon this question
Rather use the built-in function rotate_vector, you can find it here:
http://matplotlib.org/basemap/api/basemap_api.html
Your problem is that you're specifying your u and v in lat, long. At the same time, you're specifying your x and y in map coordinates. barbs seems to expect both of them in map coordinates, rather than a mix.
The simplest way is to just calculate the endpoints to get the components. (My description makes no sense, so here's what I had in mind:)
x, y = m(lon, lat)
x1, y1 = m(lon+u, lat+v)
u_map, v_map = x1-x, y1-y
You'll then need to rescale the magnitudes, as well. As a full example:
import numpy
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
# Define locations of my vectors
lat = numpy.array([50.1,46.2,51.6,52.2,54.4])
lon = numpy.array([-3.3,-1.0,-5.2,-1.2,0.2])
# Define some east-west vectors to illustrate the problem
u = numpy.array([5,5,5,5,5])
v = numpy.array([0,0,0,0,0])
# Set up map projection
m = Basemap(llcrnrlon=-15.,llcrnrlat=46.,urcrnrlon=15.,urcrnrlat=59.,
projection='lcc',lat_1=40.,lat_2=50.,lon_0=-50.,
resolution ='l')
# Calculate positions of vectors on map projection
x,y = m(lon,lat)
# Calculate the orientation of the vectors
x1, y1 = m(lon+u, lat+v)
u_map, v_map = x1-x, y1-y
# Rescale the magnitudes of the vectors...
mag_scale = np.hypot(u_map, v_map) / np.hypot(u, v)
u_map /= mag_scale
v_map /= mag_scale
# Draw barbs
m.barbs(x,y,u_map,v_map, length=7, color='red')
# Draw some grid lines for reference
parallels = numpy.arange(-80.,90,20.)
meridians = numpy.arange(0.,360.,20.)
m.drawparallels(parallels)
m.drawmeridians(meridians)
m.drawcoastlines(linewidth=0.5)
plt.show()
Related
I have a data-set of 3D points (x,y,z) projected onto a plane and i'd like to transform them into a simple 2D plot by looking at the points from an orthogonal direction to that plane. Any python explanation are much appreciated!
You can use this :
import pylab
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d import proj3d
fig = pylab.figure()
ax = fig.add_subplot(111, projection = '3d')
x = y = z = [1, 2, 3]
sc = ax.scatter(x,y,z)
#####################
x2, y2, _ = proj3d.proj_transform(1, 1, 1, ax.get_proj())
print x2, y2 # project 3d data space to 2d data space
print ax.transData.transform((x2, y2)) # convert 2d space to screen space
#####################
def on_motion(e):
# move your mouse to (1,1,1), and e.xdata, e.ydata will be the same as x2, y2
print e.x, e.y, e.xdata, e.ydata
fig.canvas.mpl_connect('motion_notify_event', on_motion)
pylab.show()
Depending on how the data were projected, and how perfectly planar they are, one way could be to use a PCA
Example on a fabricated dataset (please, next time, provide such a fabricated dataset. It is better, because I did mine by surmising how yours may look).
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from sklearn.decomposition import PCA
# Fabrication of the dataset : a bunch of 10000 random points distributed in a ring (like Saturn's rings)
radius=np.random.uniform(100,200,(10000,))
angles=np.random.uniform(0,2*np.pi,(10000,))
x1=radius*np.cos(angles)
y1=radius*np.sin(angles)
# Just to see how the original data look like
plt.figure()
plt.scatter(x1,y1)
plt.show()
# Note that those are "secret" data.
# We are not supposed to know how the 2D data are
# What we will work on are 3D data fabricated as follows:
# generate 3D data, that are those, on a plane
# with some noise
# we just use vectors (1,1,1) and (1,-1,1) as generator of the plane.
# So a planar point (x,y) will be sent do a
# 3d point x(1,1,1)+y(1,-1,1).
# Plus some noise
xyz=x1.reshape(-1,1)#[[1,1,1]] + y1.reshape(-1,1)#[[1,-1,1]] + np.random.normal(0,2,(10000,3))
fig=plt.figure()
ax = fig.add_subplot(111, projection = '3d')
ax.scatter(xyz[:,0],xyz[:,1],xyz[:,2])
plt.show()
So, that is the data we will work on. 3D data that are mainly on a plane. We want the 2D dataset of that plane. But, of course, we can't access to x1, y1, since we pretend to know only of xyz
pca=PCA(n_components=2)
xy=pca.fit_transform(xyz)
# xy are the projection on the best possible plane
# of xyz data.
plt.figure()
plt.scatter(xy[:,0], xy[:,1])
plt.show()
You may also know what are the axis of this plane
pca.components_
#array([[-0.70692992, 0.02117576, -0.70696653],
# [ 0.01489184, 0.99977576, 0.01505521]])
So, roughly (-√2/2,0,-√2/2) and (0,1,0).
Not the same axis we've used (1,1,1) and (1,-1,1).
But see that one basis generate the other : (1,1,1) is -√2(-√2/2,0,-√2/2)+(0,1,0). And (1,-1,1) is -√2(-√2/2,0,-√2/2)-(0,1,0).
Or, the other way round : (-√2/2,0,-√2/2) = -√2/4(1,1,1)-√2/4(1,-1,1); (0,1,0)=½(1,1,1)-½(1,-1,1)
So, it is the same plane. Just not the same axis in that plane, but that is normal: nothing in a 3D data of planar points can tell how the 3D data were built.
Note that this method is well suited if 3D data are a little bit noisy. If not, you could achieve the same result with simple Gram-Schmidt method. Choosing extreme points
Starting from another xyz without the noise
# same xyz but without the noise
xyzClean=x1.reshape(-1,1)#[[1,1,1]] + y1.reshape(-1,1)#[[1,-1,1]]
# One (randomly chosen. So why not the 1st) point
# of the dataset
m0=xyzClean[0]
# Choose m1 so that it is further from m0 as possible
dm0=((xyzClean-m0)**2).sum(axis=1)
idx1=np.argmax(dm0)
m1=xyzClean[idx1]
# Choose m2 as far as both m0 and m1 as possible
dm1=((xyzClean-m1)**2).sum(axis=1)
idx2=np.argmax(np.minimum(dm0,dm1))
m2=xyzClean[idx2]
# Gram-Schmidt process to get two orthogonal
# vectors from origin m0
v1=m1-m0
v1=v1/np.sqrt(v1#v1) # normalization
v2=(m2-m0) - ((m2-m0)#v1)*v1
v2=v2/np.sqrt(v2#v2)
# v1=[ 0.70700705, -0.01679433, 0.70700705]
# v2=[0.01187538, 0.99985897, 0.01187538]
# Note that 1.39721978*v1+1.02360973*v2
= (1,1,1)
# And 1.43080844*v1-0.9761082*v2 = (1,-1,1)
# So, again, same plane
# The advantage of having orthogonal basis, is that
# projection on this basis is now easy
projDataV1 = xyzClean#v1
projDataV2 = xyzClean#v2
plt.figure()
plt.scatter(projDataV1, projDataV2)
plt.show()
That second method is well suited if you have no noise, and your 3d data are exactly planar.
Not that the 1st one wouldn't work (it would work anytime). But this one is faster. And could be even faster, if instead of selecting m0, m1 and m2 as far as possible from each other, I had just selected them "far enough" from each other.
I have the following dataframe which I'm trying to plot,
x,y,u,v
-0.157806993154554,-0.05,0.000601310515776,0.003318849951029
-0.374687807296859,-0.35,-0.001057069515809,2.9686838388443E-05
-1,-0.323693574077183,-0.002539682900533,-0.008748378604651
-0.486242955499287,-0.35,-0.001797694480047,0.000218112021685
-0.54184300562917,-0.05,0.001513708615676,0.001884449273348
0,-0.31108016382718,5.28732780367136E-05,-0.000818025320768
-0.428046308037431,-0.35,-0.001458290731534,8.22432339191437E-05
-0.343159653530217,-0.05,0.00112508633174,0.002580288797617
-0.386254219645565,-0.35,-0.001139726256952,2.6945024728775E-05
-0.600252053226546,-0.05,0.001246933126822,0.00207519903779
-1,-0.061575842243108,-0.000705834245309,0.043682213872671
0,-0.052056831172645,0.009899478405714,-0.003894355148077
-0.903283837058102,-0.35,5.81557396799326E-05,-0.001065131276846
-0.418202966058798,-0.05,0.001158628845587,0.002409461885691
-0.809266339501268,-0.35,0.000290673458949,-2.0977109670639E-05
0,-0.066616962597653,2.92772892862558E-05,0.001737955957651
-0.090282152608,-0.35,0.00151876010932,0.001403901726007
-1,-0.173440678035212,-0.007741978392005,0.006023477762938
-1,-0.155079864747918,-0.00761691480875,0.007886063307524
-0.222728396757266,-0.35,0.000686463201419,0.000264558941126
where u,v and x,y are positional coordinates and the velocity vectors at that point. (full dataset - https://pastebin.pl/view/0f60b48e)
I want to plot my data like so (Contour lines and arrows are not required.) .
How do I do this?
So far I've tried:
import numpy as np
import matplotlib.pyplot as plt
# Meshgrid
#x, y = np.meshgrid(box_df['x'], box_df['y'])
x,y = box_df['x'], box_df['y']
# Directional vectors
#u, v = np.meshgrid(box_df['u'], box_df['v'])
u = box_df['u']
v = box_df['v']
# Plotting Vector Field with QUIVER
plt.quiver(x, y, u, v, color='g')
plt.title('Vector Field')
# Show plot with gird
plt.grid()
If you want to plot a scalar field with irregular data points, you can either interpolate between data points to form a regular grid, or you can use matplotlib.pyplot.tricontour and tricontourf to interpolate for you to fill.
Using tricontour you could try:
import numpy as np
import matplotlib.pyplot as plt
x, y = box_df.x, box_df.y
# make scalar field
speed = np.hypot(box_df.u, box_df.v)
# Plotting scalar field with tricontour
plt.tricontourf(x, y, speed)
plt.title('Scalar Field')
# Show plot with gird
plt.grid()
However it appears that you only have data around the edge of a rectangle, so interpolation into the interior of the rectangle is likely to be poor.
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.
I know that matplotlib and scipy can do bicubic interpolation:
http://matplotlib.org/examples/pylab_examples/image_interp.html
http://docs.scipy.org/doc/scipy/reference/tutorial/interpolate.html
http://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.interp2d.html
I also know that it is possible to draw a map of the world with matplotlib:
http://matplotlib.org/basemap/users/geography.html
http://matplotlib.org/basemap/users/examples.html
http://matplotlib.org/basemap/api/basemap_api.html
But can I do a bicubic interpolation based on 4 data points and only color the land mass?
For example using these for 4 data points (longitude and latitude) and colors:
Lagos: 6.453056, 3.395833; red HSV 0 100 100 (or z = 0)
Cairo: 30.05, 31.233333; green HSV 90 100 100 (or z = 90)
Johannesburg: -26.204444, 28.045556; cyan HSV 180 100 100 (or z = 180)
Mogadishu: 2.033333, 45.35; purple HSV 270 100 100 (or z = 270)
I am thinking that it must be possible to do the bicubic interpolation across the range of latitudes and longitudes and then add oceans, lakes and rivers on top of that layer? I can do this with drawmapboundary. Actually there is an option maskoceans for this:
http://matplotlib.org/basemap/api/basemap_api.html#mpl_toolkits.basemap.maskoceans
I can interpolate the data like this:
xnew, ynew = np.mgrid[-1:1:70j, -1:1:70j]
tck = interpolate.bisplrep(x, y, z, s=0)
znew = interpolate.bisplev(xnew[:,0], ynew[0,:], tck)
Or with scipy.interpolate.interp2d:
http://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.interp2d.html
Here it is explained how to convert to map projection coordinates:
http://matplotlib.org/basemap/users/mapcoords.html
But I need to figure out how to do this for a calculated surface instead of individual points. Actually there is an example of such a topographic map using external data, which I should be able to replicate:
http://matplotlib.org/basemap/users/examples.html
P.S. I am not looking for a complete solution. I would much prefer to solve this myself. Rather I am looking for suggestions and hints. I have been using gnuplot for more than 10 years and only switched to matplotlib within the past few weeks, so please don't assume I know even the simplest things about matplotlib.
I think this is what you are looking for (roughly). Note the crucial things are masking the data array before you plot the pcolor and passing in the hsv colormap (Docs: cmap parameter for pcolormesh and available colormaps).
I've kept the code for plotting the maps quite close to the examples so it should be easy to follow. I've kept your interpolation code for the same reason. Note that the interpolation is linear rather than cubic - kx=ky=1 - because you don't give enough points to do cubic interpolation (you'd need at least 16 - scipy will complain with less saying that "m must be >= (kx+1)(ky+1)", although the constraint is not mentioned in the documentation).
I've also extended the range of your meshgrid and kept in lat / lon for x and y throughout.
Code
from mpl_toolkits.basemap import Basemap,maskoceans
import matplotlib.pyplot as plt
import numpy as np
from scipy import interpolate
# set up orthographic map projection with
# perspective of satellite looking down at 0N, 20W (Africa in main focus)
# use low resolution coastlines.
map = Basemap(projection='ortho',lat_0=0,lon_0=20,resolution='l')
# draw coastlines, country boundaries
map.drawcoastlines(linewidth=0.25)
map.drawcountries(linewidth=0.25)
# Optionally (commented line below) give the map a fill colour - e.g. a blue sea
#map.drawmapboundary(fill_color='aqua')
# draw lat/lon grid lines every 30 degrees.
map.drawmeridians(np.arange(0,360,30))
map.drawparallels(np.arange(-90,90,30))
data = {'Lagos': (6.453056, 3.395833,0),
'Cairo': (30.05, 31.233333,90),
'Johannesburg': (-26.204444, 28.045556,180),
'Mogadishu': (2.033333, 45.35, 270)}
x,y,z = zip(*data.values())
xnew, ynew = np.mgrid[-30:60:0.1, -50:50:0.1]
tck = interpolate.bisplrep(x, y, z, s=0,kx=1,ky=1)
znew = interpolate.bisplev(xnew[:,0], ynew[0,:], tck)
znew = maskoceans(xnew, ynew, znew)
col_plot = map.pcolormesh(xnew, ynew, znew, latlon=True, cmap='hsv')
plt.show()
Output
Observe that doing the opposite, that is putting a raster on the sea and lay a mask over the continents, is easy as pie. Simply use map.fillcontinents(). So the basic idea of this solution is to modify the fillcontinents function so that it lays polygons over the oceans.
The steps are:
Create a large circle-like polygon that covers the entire globe.
Create a polygon for each shape in the map.coastpolygons array.
Cut the shape of the landmass polygon away from the circle using shapely and its difference method.
Add the remaining polygons, which have the shape of the oceans, on the top, with a high zorder.
The code:
from mpl_toolkits.basemap import Basemap
import numpy as np
from scipy import interpolate
from shapely.geometry import Polygon
from descartes.patch import PolygonPatch
def my_circle_polygon( (x0, y0), r, resolution = 50 ):
circle = []
for theta in np.linspace(0,2*np.pi, resolution):
x = r * np.cos(theta) + x0
y = r * np.sin(theta) + y0
circle.append( (x,y) )
return Polygon( circle[:-1] )
def filloceans(the_map, color='0.8', ax=None):
# get current axes instance (if none specified).
if not ax:
ax = the_map._check_ax()
# creates a circle that covers the world
r = 0.5*(map.xmax - map.xmin) # + 50000 # adds a little bit of margin
x0 = 0.5*(map.xmax + map.xmin)
y0 = 0.5*(map.ymax + map.ymin)
oceans = my_circle_polygon( (x0, y0) , r, resolution = 100 )
# for each coastline polygon, gouge it out of the circle
for x,y in the_map.coastpolygons:
xa = np.array(x,np.float32)
ya = np.array(y,np.float32)
xy = np.array(zip(xa.tolist(),ya.tolist()))
continent = Polygon(xy)
## catches error when difference with lakes
try:
oceans = oceans.difference(continent)
except:
patch = PolygonPatch(continent, color="white", zorder =150)
ax.add_patch( patch )
for ocean in oceans:
sea_patch = PolygonPatch(ocean, color="blue", zorder =100)
ax.add_patch( sea_patch )
########### DATA
x = [3.395833, 31.233333, 28.045556, 45.35 ]
y = [6.453056, 30.05, -26.204444, 2.033333]
z = [0, 90, 180, 270]
# set up orthographic map projection
map = Basemap(projection='ortho', lat_0=0, lon_0=20, resolution='l')
## Plot the cities on the map
map.plot(x,y,".", latlon=1)
# create a interpolated mesh and set it on the map
interpol_func = interpolate.interp2d(x, y, z, kind='linear')
newx = np.linspace( min(x), max(x) )
newy = np.linspace( min(y), max(y) )
X,Y = np.meshgrid(newx, newy)
Z = interpol_func(newx, newy)
map.pcolormesh( X, Y, Z, latlon=1, zorder=3)
filloceans(map, color="blue")
Voilà:
So, I'm solving a system of differential equations numerically i have x,y,z each a solution. Each array is one dimensional and and for example x[0],y[0],z[0] goes with a point in space. i want to graph these in a contour like the usual x y z coordinates, it says i need z to be a 2d array, i know how to make a mesh from x and y, but how do i do this to z?
I have made a mesh out of the x,y, but for z i don't know what to do.
if someone could give me insight it would be much appreciated.
It is not enough to just mesh in x and y, you need to grid your data on a regular grid to be able to do a contour plot. To do this you should look into matplotlib.mlab.griddata (http://matplotlib.org/examples/pylab_examples/griddata_demo.html).
I'll paste the example code from the link below with some extra comments:
from numpy.random import uniform, seed
from matplotlib.mlab import griddata
import matplotlib.pyplot as plt
import numpy as np
# Here the code generates some x and y coordinates and some corresponding z values.
seed(0)
npts = 200
x = uniform(-2,2,npts)
y = uniform(-2,2,npts)
z = x*np.exp(-x**2-y**2)
# Here you define a grid (of arbitrary dimensions, but equal spacing) onto which your data will be mapped
xi = np.linspace(-2.1,2.1,100)
yi = np.linspace(-2.1,2.1,200)
# Map the data to the grid to get a 2D array of remapped z values
zi = griddata(x,y,z,xi,yi,interp='linear')
# contour the gridded data, plotting dots at the nonuniform data points.
CS = plt.contour(xi,yi,zi,15,linewidths=0.5,colors='k')
CS = plt.contourf(xi,yi,zi,15,cmap=plt.cm.rainbow,
vmax=abs(zi).max(), vmin=-abs(zi).max())
plt.colorbar() # draw colorbar
# Plot the original sampling
plt.scatter(x,y,marker='o',c='b',s=5,zorder=10)
plt.xlim(-2,2)
plt.ylim(-2,2)
plt.title('griddata test (%d points)' % npts)
plt.show()
It looks like you are looking for line or scatter plots instead of contour.