Plotting geographic data in 3d with matplotlib - python

I'm trying to plot data that contains lat, lon, and altitude as a 3d scatter plot in mpl. What I've found for documentation so far is either how to plot 2d geographic data using Basemap, OR how to plot 3d data using Axes3D, but not both. The specific coding issue I'm running into is how to set my lat/lon data to be interpreted as geographic lat and lon, but to keep my alt data as... well, altitude. I know Basemap contains the latlon setting:
"If latlon keyword is set to True, x,y are intrepreted as longitude
and latitude in degrees. Data and longitudes are automatically shifted
to match map projection region for cylindrical and pseudocylindrical
projections, and x,y are transformed to map projection coordinates."
However if I'm plotting in 3d, Axes3D doesn't support the latlon argument. The reason having geographic coordinates is so important is that I'm plotting the data over a basemap for visual reference.
My code:
import os
os.environ['PROJ_LIB'] = r'E:\Programs\Anaconda3\pkgs\proj4-5.2.0-ha925a31_1\Library\share'
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.basemap import Basemap
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.gca(projection='3d')
# Define lower left, uperright lontitude and lattitude respectively
extent = [-180, 180, -90, 90]
# Create a basemap instance that draws the Earth layer
#bm = Basemap(llcrnrlon=extent[0], llcrnrlat=extent[2],
# urcrnrlon=extent[1], urcrnrlat=extent[3],
# projection='cyl', resolution='l', fix_aspect=False, ax=ax)
bm = Basemap(llcrnrlon=-73,llcrnrlat=41,urcrnrlon=-69.5,urcrnrlat=43.5,projection='lcc', resolution='i', lat_0=42, lon_0=-71, ax=ax, fix_aspect=True)
# Add Basemap to the figure
ax.add_collection3d(bm.drawcoastlines(linewidth=0.35))
ax.add_collection3d(bm.drawstates(linewidth=0.25))
#ax.add_collection3d(bm.drawcounties(linewidth=0.15))
#ax.set_axis_off()
ax.view_init(azim=230, elev=50)
ax.set_xlabel('Longitude (°E)', labelpad=20)
ax.set_ylabel('Latitude (°N)', labelpad=20)
ax.set_zlabel('Altitude (ft)', labelpad=20)
# Add meridian and parallel gridlines
#lon_step = 5
#lat_step = 5
#meridians = np.arange(extent[0], extent[1] + lon_step, lon_step)
#parallels = np.arange(extent[2], extent[3] + lat_step, lat_step)
#ax.set_yticks(parallels)
#ax.set_yticklabels(parallels)
#ax.set_xticks(meridians)
#ax.set_xticklabels(meridians)
ax.set_zlim(0., 50000.)
#ax.set_xlim(-69., -73.)
#ax.set_ylim(40.,44.)
# empty array for place holder
lons = np.array([]) # longtitude
lats = np.array([]) # latitude
alt = np.array([]) # altitude
# Make sure your working directory is the directory contains this script and the data file.
#directory = os.fsencode('.')
# Import data to illustrate
lons, lats, alt = np.loadtxt('adsb-csv-2019-07-07_xzyonly_small.csv', delimiter=',', unpack=True, skiprows=1)
#alons, alats = map(lons, lats, latlon=True)
# scatter map based on lons, lats, alts
p = ax.scatter(lons, lats, alt, c=alt, cmap='jet')
# Add a colorbar to reference the intensity
#fig.colorbar(p, label='Aircraft Altitude')
plt.show()
This was adapted from code written by Phúc Lê.
Any help would be much appreciated!

Related

How to make spatial plot of irregular geographical data

I have lat=[13.7,21,23.7,10.6,34.5,20.7,33.1,15.5]
lon=[65.7,87.5,69.8,98.3,67,79.8,88.8,77.9] and
val=[234,310,287,279,298,280,279,321]
How can I make a spatial plot these data over map ? My code look like
lat=[13.7,21,23.7,10.6,34.5,20.7,33.1,15.5]
lon=[65.7,87.5,69.8,98.3,67,79.8,88.8,77.9]
val=[234,310,287,279,298,280,279,321]
lon, lat = np.meshgrid(lon, lat)
m = Basemap(projection='merc', resolution=None,
llcrnrlat=0, urcrnrlat=40,
llcrnrlon=60, urcrnrlon=100, )
m.contourf(lon,lat,val)
To be able to use contourf, you need gridded data (i.e. if you have an 8x8 lon-lat grid, you need 64 z values). As you have only (lon,lat,z) triplets, it is better to use a tricontourf plot. However, Basemap does not have that function, but has an additional tri keyword for the contourf function:
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits import basemap
lat=np.array([13.7,21,23.7,10.6,34.5,20.7,33.1,15.5])
lon=np.array([65.7,87.5,69.8,98.3,67,79.8,88.8,77.9])
val=np.array([234,310,287,279,298,280,279,321])
#lon, lat = np.meshgrid(lon, lat) <-- do not use this
m = basemap.Basemap(projection='merc', resolution=None,
llcrnrlat=0, urcrnrlat=40,
llcrnrlon=60, urcrnrlon=100, )
##need to convert coordinates
x,y = m(lon,lat)
##add the `tri=True` kwarg
m.contourf(x,y,val, tri=True)
plt.show()

Making a filled contour plot in python with netcdf files

I'm attempting to make a plot emissions from a model, using Basemap and matplotlib.pyplot. I'm very new to python so was attempting to use someone else's example and adjust it for my data but finding numerous errors.
from mpl_toolkits.basemap import Basemap, cm
from netCDF4 import Dataset as NetCDFFile
import numpy as np
import matplotlib.pyplot as plt
# plot rainfall from NWS using special precipitation
# colormap used by the NWS, and included in basemap.
nc = NetCDFFile('file.nc')
pm25var = nc.variables['emis_all']
data = 0.01*pm25var[:]
latcorners = nc.variables['lat'][:]
loncorners = -nc.variables['lon'][:]
lon_0 = -nc.variables['true_lon'].getValue()
lat_0 = nc.variables['true_lat'].getValue()
# create figure and axes instances
fig = plt.figure(figsize=(8,8))
ax = fig.add_axes([0.1,0.1,0.8,0.8])
# create polar stereographic Basemap instance.
m = Basemap(projection='stere',lon_0=lon_0,lat_0=90.,lat_ts=lat_0,\
llcrnrlat=latcorners[0],urcrnrlat=latcorners[2],\
llcrnrlon=loncorners[0],urcrnrlon=loncorners[2],\
rsphere=6371200.,resolution='l',area_thresh=10000)
# draw coastlines, state and country boundaries, edge of map.
m.drawcoastlines()
m.drawstates()
m.drawcountries()
# draw parallels.
parallels = np.arange(0.,90,10.)
m.drawparallels(parallels,labels=[1,0,0,0],fontsize=10)
# draw meridians
meridians = np.arange(0.,60.,10.)
m.drawmeridians(meridians,labels=[0,0,0,1],fontsize=10)
ny = data.shape[0]; nx = data.shape[1]
lons, lats = m.makegrid(nx, ny) # get lat/lons of ny by nx evenly space grid.
x, y = m(lons, lats) # compute map proj coordinates.
# draw filled contours.
clevs = [0,1,2.5,5,7.5,10,15,20,30,40,50,70,100,150,200,250,300,400,500,600,750]
cs = m.contourf(x,y,data,clevs,cmap=cm.s3pcpn)
# add colorbar.
cbar = m.colorbar(cs,location='bottom',pad="5%")
cbar.set_label('mm')
# add title
plt.title(pm25var.long_name+' for period ending '+pm25var.dateofdata)
plt.show()
I keep getting "KeyError: 'true_lon'" and have no idea how to resolve it. The data has 3 keys (lat, lon and time). I have shown the details of the lon variable below.
>>>print dataset.variables['lon']
<type 'netCDF4._netCDF4.Variable'>
float64 lon(lon)
long_name: longitude
units: degrees_east
comment: centre of grid cell
unlimited dimensions:
current shape = (720,)
filling on, default _FillValue of 9.96920996839e+36 used
The data is global. The details of variable I'm trying to plot (emis_all) are below.
>>>print dataset.variables['emis_all']
<type 'netCDF4._netCDF4.Variable'>
float64 emis_all(time, lat, lon)
long_name: PM25 - Total
pollutant: PM25
sector: Total
units: kt/year
unlimited dimensions:
current shape = (11, 360, 720)
filling on, default _FillValue of 9.96920996839e+36 used
Any help/advice much appreciated. Like I said I am a beginner just trying to get started and practice making a few plots with my own data.
The error you get is because you don't have that variable in your netCDF file. You should start from "the beggining" and try to figure out which does each line of your code. Then, select what do you actually need. I've simplified your code, just try it out:
# read netcdf file
nc = NetCDFFile('file.nc')
emis = nc.variables['emis_all'][:]
lats = nc.variables['lat'][:]
lons = nc.variables['lon'][:]
nc.close()
data = emis[0,:,:] #emissions for the first hour only
# create figure and axes instances
fig = plt.figure(figsize=(8,8))
ax = fig.add_axes([0.1,0.1,0.8,0.8])
m = Basemap()
m.drawcoastlines()
m.drawstates()
m.drawcountries()
# draw parallels.
parallels = np.arange(0.,90,10.)
m.drawparallels(parallels,labels=[1,0,0,0],fontsize=10)
# draw meridians
meridians = np.arange(0.,60.,10.)
m.drawmeridians(meridians,labels=[0,0,0,1],fontsize=10)
x, y = m(lons, lats) # compute map proj coordinates.
# draw filled contours.
clevs = [0,1,2.5,5,7.5,10,15,20,30,40,50,70,100,150,200,250,300,400,500,600,750]
cs = m.contourf(x,y,data,clevs)
# add colorbar.
cbar = m.colorbar(cs,location='bottom',pad="5%")
cbar.set_label('mm')
# add title
plt.title('emissions')
plt.show()
some usefull comands:
print nc.variables will print you the list of variables of your netCDF file
emi.shape will return the shape of your array. In my example I chose only the 1st hour (or other time lapse) of the data. You can do everything you want with your array (like sum, mean), search for numpy module.

Plotting data set with extra points using matplotlib

I have searched stackoverflow to find an answer to my issue, but to no avail. I wish to plot an earthquake data set with a yellow star to represent the center of my study area. However, I can only plot the earthquake data and cannot plot the star. I have tried two solutions: just plotting both data sets such as what I have in my code, or using a subplot.
In other words, what is the equivalent of MatLab's hold on command in Python/matplotlib?
#!/usr/bin/env python
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
# Read in latitudes and longitudes
eq_data = open('eq_data')
lats, lons = [], []
mag = []
for index, line in enumerate(eq_data.readlines()):
if index > 0:
lats.append(float(line.split(',')[0]))
lons.append(float(line.split(',')[1]))
mag.append(float(line.split(',')[2]))
#Build the basemap
antmap = Basemap(projection='spstere', boundinglat=10, lon_0=-60, resolution='f')
antmap.drawcoastlines(color='black', linewidth=0.15)
antmap.fillcontinents(color='0.95')
antmap.drawmapboundary(fill_color='aqua')
x,y = antmap(lons, lats)
x1,x2= (0,-90)
antmap.plot(x1,x2, 'r*', markersize=10)
antmap.plot(x,y,'ro', markersize=8)
plt.show()

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.

Drawing a graph with NetworkX on a Basemap

I want to plot a graph on a map where the nodes would be defined by coordinates (lat, long) and have some value associated.
I have been able to plot points as a scatterplot on a basemap but can't seem to find how to plot a graph on the map.
Thanks.
EDIT: I have added code on how I plotted the points on a basemap. Most of it has been adapted from code in this article.
from mpl_toolkits.basemap import Basemap
from shapely.geometry import Point, MultiPoint
import pandas as pd
import matplotlib.pyplot as plt
m = Basemap(
projection='merc',
ellps = 'WGS84',
llcrnrlon=-130,
llcrnrlat=25,
urcrnrlon=-60,
urcrnrlat=50,
lat_ts=0,
resolution='i',
suppress_ticks=True)
# Create Point objects in map coordinates from dataframe lon
# and lat values
# I have a dataframe of coordinates
map_points = pd.Series(
[Point(m(mapped_x, mapped_y))
for mapped_x, mapped_y in zip(df['lon'],
df['lat'])])
amre_points = MultiPoint(list(map_points.values))
plt.clf()
fig = plt.figure()
ax = fig.add_subplot(111, axisbg='w', frame_on=False)
fig.set_size_inches(18.5, 10.5)
# Create a scatterplot on the map
dev = m.scatter(
[geom.x for geom in map_points],
[geom.y for geom in map_points],
20, marker='o', lw=.25,
facecolor='#33ccff', edgecolor='w',
alpha=0.9,antialiased=True,
zorder=3)
m.fillcontinents(color='#555555')
I get this image:
Here is one way to do it:
import networkx as nx
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap as Basemap
m = Basemap(
projection='merc',
llcrnrlon=-130,
llcrnrlat=25,
urcrnrlon=-60,
urcrnrlat=50,
lat_ts=0,
resolution='i',
suppress_ticks=True)
# position in decimal lat/lon
lats=[37.96,42.82]
lons=[-121.29,-73.95]
# convert lat and lon to map projection
mx,my=m(lons,lats)
# The NetworkX part
# put map projection coordinates in pos dictionary
G=nx.Graph()
G.add_edge('a','b')
pos={}
pos['a']=(mx[0],my[0])
pos['b']=(mx[1],my[1])
# draw
nx.draw_networkx(G,pos,node_size=200,node_color='blue')
# Now draw the map
m.drawcountries()
m.drawstates()
m.bluemarble()
plt.title('How to get from point a to point b')
plt.show()
As of today there is a nice alternative to basemap. Mplleaflet is a library inspired by mpld3. It plots faster than basemap, is more easy to use and allows to visualizing geographic data on beautiful interactive openstreetmap. The input can be longitude and latitude the library automatically projects the data properly.
Input dictionary pos, where the node (country) is the key and long lat are saved as value.
pos = {u'Afghanistan': [66.00473365578554, 33.83523072784668],
u'Aland': [19.944009818523348, 60.23133494165451],
u'Albania': [20.04983396108883, 41.14244989474517],
u'Algeria': [2.617323009197829, 28.158938494487625],
.....
Plotting is as easy as:
import mplleaflet
fig, ax = plt.subplots()
nx.draw_networkx_nodes(GG,pos=pos,node_size=10,node_color='red',edge_color='k',alpha=.5, with_labels=True)
nx.draw_networkx_edges(GG,pos=pos,edge_color='gray', alpha=.1)
nx.draw_networkx_labels(GG,pos, label_pos =10.3)
mplleaflet.display(fig=ax.figure)

Categories

Resources