I'm trying to plot streamlines on a global map with the Robinson projection, but basemap doesn't seem to like the projected co-ordinates. Of course, it works fine for a plain old cylindrical projection, which is regular in the x direction.
Here is an example:
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import numpy as np
u = np.ones((21,21))
v = np.ones((21,21))
lats = np.arange(-90,91,9)
lons = np.arange(-180,181,18)
x,y = np.meshgrid(lons,lats)
# It works for Cylindrical
mp = Basemap(projection='cyl')
xx,yy = mp(x,y)
mp.streamplot(xx,yy,u,v)
mp.drawcoastlines()
plt.show()
# But not Robinson
mp = Basemap(projection='robin',lon_0=0)
xx, yy = mp(x, y)
mp.streamplot(xx,yy,u,v)
mp.drawcoastlines()
plt.show()
It complains about the x co-ordinates, raising:
ValueError: The rows of 'x' must be equal
So is it possible to plot streamlines on Robinson projections?
With the command xx,yy = mp(x,y) a coordinate transformation according to the particular projection is applied to your lon and lats. For most projections this will result in a distorsion of the gird point such that rows of x are no longer equal, hence the error: ValueError: The rows of 'x' must be equal. To fix this you need to re-grid your data, e.g. like this:
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import numpy as np
import matplotlib as plt
u = np.ones((21,21))
v = np.ones((21,21))
lats = np.arange(-90,91,9)
lons = np.arange(-180,181,18)
x,y = np.meshgrid(lons,lats)
mp = Basemap(projection='robin',lon_0=0)
xx, yy = mp(x, y)
# generate a grid that is equally spaced in a plot with the current pojection
lons, lats, xxnew, yynew = mp.makegrid(21,21, returnxy=True)
# project the data onto the new grid
unew = plt.mlab.griddata(xx.flatten(), yy.flatten(),u.flatten(), xxnew, yynew ,interp='linear')
vnew = plt.mlab.griddata(xx.flatten(), yy.flatten(),v.flatten(), xxnew, yynew ,interp='linear')
mp.streamplot(xxnew,yynew,unew,vnew)
mp.drawcoastlines()
plt.show()
Related
I am trying to regrid my .nc data with irregular grid with the following code:
from mpl_toolkits.basemap import Basemap
from netCDF4 import Dataset as NetCDFFile
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import griddata
nc = NetCDFFile('test.nc')
lat = nc.variables['latitude'][:]
lon = nc.variables['longitude'][:]
time = nc.variables['time'][:]
ssi = nc.variables['solar_irradiation'][:]
XI = np.arange(46.025, 56.525, 0.05)
YI = np.arange(5.025, 15.525, 0.05)
lat_new, lon_new = np.meshgrid(XI, YI)
new_grid = griddata((lat, lon), ssi, (lat_new, lon_new), method='linear')
It works fine, there are NaN values at lat/lon boxes which are not in the original file.
Then I want to plot it using Basemap:
map = Basemap(projection='merc', llcrnrlon=-5., llcrnrlat=35., urcrnrlon=30., urcrnrlat=60.,
resolution='i')
map.drawcountries()
map.drawcoastlines()
x, y = map(XI, YI)
rad = map.contourf(x, y, new_grid)
cb = map.colorbar(rad, "bottom", size="10%", pad="10%")
I am receiving following error: IndexError: too many indices for array: array is 1-dimensional, but 2 were indexed. I know that this means, but I have no clue how to change to code so that it worked the same way. Thank you for every help!
I have the nice hexbin plot below, but I'm wondering if there is any way to get hexbin into an Aitoff projection? The salient code is:
import numpy as np
import math
import matplotlib.pyplot as plt
from astropy.io import ascii
filename = 'WISE_W4SNRge3_and_W4MPRO_lt_6.0_RADecl_nohdr.dat'
datafile= path+filename
data = ascii.read(datafile)
points = np.array([data['ra'], data['dec']])
color_map = plt.cm.Spectral_r
points = np.array([data['ra'], data['dec']])
xbnds = np.array([ 0.0,360.0])
ybnds = np.array([-90.0,90.0])
extent = [xbnds[0],xbnds[1],ybnds[0],ybnds[1]]
fig = plt.figure(figsize=(6, 4))
ax = fig.add_subplot(111)
x, y = points
gsize = 45
image = plt.hexbin(x,y,cmap=color_map,
gridsize=gsize,extent=extent,mincnt=1,bins='log')
counts = image.get_array()
ncnts = np.count_nonzero(np.power(10,counts))
verts = image.get_offsets()
ax.set_xlim(xbnds)
ax.set_ylim(ybnds)
plt.xlabel('R.A.')
plt.ylabel(r'Decl.')
plt.grid(True)
cb = plt.colorbar(image, spacing='uniform', extend='max')
plt.show()
and I've tried:
plt.subplot(111, projection="aitoff")
before doing the plt.hexbin command, but which only gives a nice, but blank, Aitoff grid.
The problem is that the Aitoff projection uses radians, from -π to +π. Not degrees from 0 to 360. I use the Angle.wrap_at function to achieve this, as per this Astropy example (which essentially tells you how to create a proper Aitoff projection plot).
In addition, you can't change the axis limits (that'll lead to an error), and shouldn't use extent (as ImportanceOfBeingErnest's answer also states).
You can change your code as follows to get what you want:
import numpy as np
import matplotlib.pyplot as plt
from astropy.io import ascii
from astropy.coordinates import SkyCoord
from astropy import units
filename = 'WISE_W4SNRge3_and_W4MPRO_lt_6.0_RADecl_nohdr.dat'
data = ascii.read(filename)
coords = SkyCoord(ra=data['ra'], dec=data['dec'], unit='degree')
ra = coords.ra.wrap_at(180 * units.deg).radian
dec = coords.dec.radian
color_map = plt.cm.Spectral_r
fig = plt.figure(figsize=(6, 4))
fig.add_subplot(111, projection='aitoff')
image = plt.hexbin(ra, dec, cmap=color_map,
gridsize=45, mincnt=1, bins='log')
plt.xlabel('R.A.')
plt.ylabel('Decl.')
plt.grid(True)
plt.colorbar(image, spacing='uniform', extend='max')
plt.show()
Which gives
I guess your problem lies in the use of the extent which is set to something other than the range of the spherical coordinate system.
The following works fine:
import matplotlib.pyplot as plt
import numpy as np
ra = np.linspace(-np.pi/2.,np.pi/2.,1000)
dec = np.sin(ra)*np.pi/2./2.
points = np.array([ra, dec])
plt.subplot(111, projection="aitoff")
color_map = plt.cm.Spectral_r
x, y = points
gsize = 45
image = plt.hexbin(x,y,cmap=color_map,
gridsize=45,mincnt=1,bins='log')
plt.xlabel('R.A.')
plt.ylabel(r'Decl.')
plt.grid(True)
cb = plt.colorbar(image, spacing='uniform', extend='max')
plt.show()
I am trying to plot a contour and quiver plot over a basemap. When I plot, I get no errors, but only the basemap will show. The netcdf file only has one point in it for lat and long, so I had to create a range of coordinates. Any ideas why this is happening?
import netCDF4
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import pylab
from mpl_toolkits.basemap import Basemap
from matplotlib.patches import Polygon
ncfile = netCDF4.Dataset('30JUNE2012_0400UTC.cdf', 'r')
dbZ = ncfile.variables['MAXDBZF']
u = ncfile.variables['UNEW']
v = ncfile.variables['VNEW']
#print u
#print v
#print dbZ
data = dbZ[0,0]
data.shape
#print data.shape
z_index = 0 # z-level you want to plot (0-19)
U = u[0,z_index, :,:] #[time,z,x,y]
V = v[0,z_index, :,:]
lats = np.linspace(35.0, 41.0, data.shape[0])
lons = np.linspace(-81.0,-73.0, data.shape[1])
# create the map
map = Basemap(llcrnrlat=36,urcrnrlat=40,\
llcrnrlon=-80,urcrnrlon=-74,lat_ts=20,resolution='c')
# load the shapefile, use the name 'states'
map.readshapefile('st99_d00', name='states', drawbounds=True)
# collect the state names from the shapefile attributes so we can
# look up the shape obect for a state by it's name
state_names = []
for shape_dict in map.states_info:
state_names.append(shape_dict['NAME'])
ax = plt.gca() # get current axes instance
x,y = map(*np.meshgrid(lats,lons))
levels = np.arange(5,60,3)
c = map.contourf(x,y,data, levels, cmap='jet')
plt.colorbar()
q=plt.quiver(U,V,width=0.002, scale_units='xy',scale=10)
qk= plt.quiverkey (q,0.95, 1.02, 20, '20m/s', labelpos='N')
plt.show()
I have some Fortran code which outputs the polar coordinates of a grid on the surface of a sphere in theta, phi format. It also outputs a value associated with each of these points (specifically meant to represent the voltage at that point on the sphere's surface).
Now I want to read this data into Python, plot a sphere, and then colour it according to the voltage data values. I know how to do this for a latitude-longitude grid, but my grid points are not ordered in any specific way.
The code I'm trying is as follows:
import matplotlib.pyplot as plt
from matplotlib import cm, colors
from mpl_toolkits.mplot3d import Axes3D
import option_d
import numpy as np
# Create a sphere
r = 1.0
pi = np.pi
cos = np.cos
sin = np.sin
#Read in grid points
data = np.genfromtxt('grid.txt')
phi, theta = np.hsplit(data, 2)
#Convert grid points to cartesian
x = r*sin(phi)*cos(theta)
y = r*sin(phi)*sin(theta)
z = r*cos(phi)
#Import data from initial state
colorfunction = np.genfromtxt('sphere_init.txt')
print np.shape(colorfunction)
#Normalise the colour map to the initial data
newcm = option_d.test_cm
norm=colors.Normalize(vmin = -np.max(colorfunction), vmax = np.max(colorfunction), clip = False)
#Plot the surface
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(
x,y,z,rstride=1,cstride=1,cmap=newcm,facecolors=newcm(norm(colorfunction)))
#Set axes and display or save
ax.set_aspect("equal")
plt.tight_layout()
plt.show()
The file 'grid.txt' contains two columns, each 770 in length, representing the phi, theta coordinates of each point. The file 'sphere_init.txt' contains a single column of length 770, which are the corresponding data values. However, this does not work - it just throws error messages at me. Is it even possible to plot a sphere from disordered grid points? Any help much appreciated. Thanks.
Edit
Here is the error message:
Traceback (most recent call last):
File "sphere.py", line 43, in <module>
x,y,z,rstride=1,cstride=1, cmap=newcm,facecolors=newcm(norm(colorfunction)))
File "/usr/lib/pymodules/python2.7/mpl_toolkits/mplot3d/axes3d.py", line 1611, in plot_surface
colset.append(fcolors[rs][cs])
IndexError: index out of bounds
I believe I have solved my problem. I read in my irregular grid data, and then also create a regular latitude-longitude grid. I then interpolate from the irregular grid to the lat-long grid:
import matplotlib.pyplot as plt
import matplotlib.mlab as ml
from matplotlib import cm, colors
from mpl_toolkits.mplot3d import Axes3D
import option_d
import numpy as np
import time
#Read in lebedev grid points
data = np.genfromtxt('grid.txt')
u, v = np.hsplit(data, 2)
phi, theta = u[:,0], v[:,0]
#Import data from initial state
colorfunction = np.genfromtxt('sphere_init.txt')
#Generate a lat-long grid to interpolate on
p = np.linspace(0,np.pi, 770)
t = np.linspace(-np.pi, np.pi, 770)
p, t = np.meshgrid(p, t)
#Interpolate using delaunay triangularization
zi = ml.griddata(phi, theta, colorfunction, p, t)
#Convert the lat-long grid points to cartesian
x = np.sin(p)*np.cos(t)
y = np.sin(p)*np.sin(t)
z = np.cos(p)
#Normalize the interpolated colourfunction
#Use fancy new colourmap
newcm = option_d.test_cm
norm=colors.Normalize(vmin = -np.max(zi), vmax = np.max(zi), clip = False)
#Plot the surface
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(
x,y,z,rstride=1,cstride=1, cmap=newcm,facecolors=newcm(norm(zi)))
#Display
ax.set_aspect("equal")
ax.set_xlim([-1,1])
ax.set_ylim([-1,1])
ax.set_zlim([-1,1])
plt.tight_layout()
plt.show()
Edit
I have run into a new problem with this method. It causes a chunk to be missing from the back of my sphere:
Any ideas why?
I am drawing a map using basemap from matplotlib. The data are spreaded all over the world, but I just want to retain all the data on the continent and drop those on the ocean. Is there a way that I can filter the data, or is there a way to draw the ocean again to cover the data?
There's method in matplotlib.basemap: is_land(xpt, ypt)
It returns True if the given x,y point (in projection coordinates) is over land, False otherwise. The definition of land is based upon the GSHHS coastline polygons associated with the class instance. Points over lakes inside land regions are not counted as land points.
For more information, see here.
is_land() will loop all the polygons to check whether it's land or not. For large data size, it's very slow. You can use points_inside_poly() from matplotlib to check an array of points quickly. Here is the code. It doesn't check lakepolygons, if you want remove points in lakes, you can add your self.
It took 2.7 seconds to check 100000 points on my PC. If you want more speed, you can convert the polygons into a bitmap, but it's a little difficult to do this. Please tell me if the following code is not fast enought for your dataset.
from mpl_toolkits.basemap import Basemap
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.nxutils as nx
def points_in_polys(points, polys):
result = []
for poly in polys:
mask = nx.points_inside_poly(points, poly)
result.extend(points[mask])
points = points[~mask]
return np.array(result)
points = np.random.randint(0, 90, size=(100000, 2))
m = Basemap(projection='moll',lon_0=0,resolution='c')
m.drawcoastlines()
m.fillcontinents(color='coral',lake_color='aqua')
x, y = m(points[:,0], points[:,1])
loc = np.c_[x, y]
polys = [p.boundary for p in m.landpolygons]
land_loc = points_in_polys(loc, polys)
m.plot(land_loc[:, 0], land_loc[:, 1],'ro')
plt.show()
The HYRY's answer won't work on new versions of matplotlib (nxutils is deprecated). I've made a new version that works:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
from matplotlib.path import Path
import numpy as np
map = Basemap(projection='cyl', resolution='c')
lons = [0., 0., 16., 76.]
lats = [0., 41., 19., 51.]
x, y = map(lons, lats)
locations = np.c_[x, y]
polygons = [Path(p.boundary) for p in map.landpolygons]
result = np.zeros(len(locations), dtype=bool)
for polygon in polygons:
result += np.array(polygon.contains_points(locations))
print result
The simplest way is to use basemap's maskoceans.
If for each lat, lon you have a data and you want to
use contours:
After meshgrid and interpolation:
from scipy.interpolate import griddata as gd
from mpl_toolkits.basemap import Basemap, cm, maskoceans
xi, yi = np.meshgrid(xi, yi)
zi = gd((mlon, mlat),
scores,
(xi, yi),
method=grid_interpolation_method)
#mask points on ocean
data = maskoceans(xi, yi, zi)
con = m.contourf(xi, yi, data, cmap=cm.GMT_red2green)
#note instead of zi we have data now.
Update (much faster than in_land or in_polygon solutions):
If for each lat, lon you don't have any data, and you just want to scatter the points only over land:
x, y = m(lons, lats)
samples = len(lons)
ocean = maskoceans(lons, lats, datain=np.arange(samples),
resolution='i')
ocean_samples = np.ma.count_masked(ocean)
print('{0} of {1} points in ocean'.format(ocean_samples, samples))
m.scatter(x[~ocean.mask], y[~ocean.mask], marker='.', color=colors[~ocean.mask], s=1)
m.drawcountries()
m.drawcoastlines(linewidth=0.7)
plt.savefig('a.png')
I was answering this question, when I was told that it would be better to post my answer over here. Basically, my solution extracts the polygons that are used to draw the coastlines of the Basemap instance and combines these polygons with the outline of the map to produce a matplotlib.PathPatch that overlays the ocean areas of the map.
This especially useful if the data is coarse and interpolation of the data is not wanted. In this case using maskoceans produces a very grainy outline of the coastlines, which does not look very good.
Here is the same example I posted as answer for the other question:
from matplotlib import pyplot as plt
from mpl_toolkits import basemap as bm
from matplotlib import colors
import numpy as np
import numpy.ma as ma
from matplotlib.patches import Path, PathPatch
fig, ax = plt.subplots()
lon_0 = 319
lat_0 = 72
##some fake data
lons = np.linspace(lon_0-60,lon_0+60,10)
lats = np.linspace(lat_0-15,lat_0+15,5)
lon, lat = np.meshgrid(lons,lats)
TOPO = np.sin(np.pi*lon/180)*np.exp(lat/90)
m = bm.Basemap(resolution='i',projection='laea', width=1500000, height=2900000, lat_ts=60, lat_0=lat_0, lon_0=lon_0, ax = ax)
m.drawcoastlines(linewidth=0.5)
x,y = m(lon,lat)
pcol = ax.pcolormesh(x,y,TOPO)
##getting the limits of the map:
x0,x1 = ax.get_xlim()
y0,y1 = ax.get_ylim()
map_edges = np.array([[x0,y0],[x1,y0],[x1,y1],[x0,y1]])
##getting all polygons used to draw the coastlines of the map
polys = [p.boundary for p in m.landpolygons]
##combining with map edges
polys = [map_edges]+polys[:]
##creating a PathPatch
codes = [
[Path.MOVETO] + [Path.LINETO for p in p[1:]]
for p in polys
]
polys_lin = [v for p in polys for v in p]
codes_lin = [c for cs in codes for c in cs]
path = Path(polys_lin, codes_lin)
patch = PathPatch(path,facecolor='white', lw=0)
##masking the data:
ax.add_patch(patch)
plt.show()
This produces the following plot:
Hope this is helpful to someone :)