Plotting sphere grid with matplotlib basemap - matplotlib

I would like to draw a grid covering all the sphere on an orthographic projection.
The issue is cells outside the projection are not drawed correctly. This happened with drawgreatcircles as pointed here.
I have also tried to use Polygons as described here, but same problem.
Finally, I have coded a custom check based on Wikipedia. The idea is for each point of each segment, we check cos c (cf Wikipedia) and do not plot it if the cosinus is negative.
My question is : can we do this kind of check with basemap own functions ?
This strategy would not work for other projections.
Also, why is this kind of check not included in Basemap ?

Thanks to your example, I took the data and plotted it with cartopy. The following changes were needed to create the plot:
import cartopy.crs as ccrs
ax =plt.axes(projection=ccrs.Orthographic())
plt.pcolormesh(lons, lats,val, edgecolors='k',
linewidths=1, transform=ccrs.PlateCarree())
ax.coastlines()
ax.gridlines()
plt.show()
This is using pcolormesh so is pretty quick (though your example wasn't that slow on my machine in the first place).

Here is a solution using pcolor :
import pylab as plt
from mpl_toolkits.basemap import Basemap
import numpy as np
nb_lat2 = 20
nb_lat = 2*nb_lat2
nb_lon = 3*(2*(nb_lat+1) - 1)
lats = np.zeros((2*nb_lat, nb_lon))
lons = np.zeros((2*nb_lat, nb_lon))
val = np.zeros((2*nb_lat, nb_lon))
dlat = 90./nb_lat2
for i in range(nb_lat):
nb_lon = 2*(i+1)-1
if ((i+1) > nb_lat2):
nb_lon = 2*(nb_lat - i)-1
dlon = 120./nb_lon
lats[2*i][:] = 90 - i*dlat
lats[2*i+1][:] = 90 - (i+1)*dlat
for j in range(nb_lon):
lons[2*i][j] = j*dlon
lons[2*i+1][j] = j*dlon
for k in range(1,3):
lons[2*i][j + k*nb_lon] = j*dlon + 120.*k
lons[2*i+1][j + k*nb_lon] = j*dlon + 120.*k
lons[2*i][3*nb_lon:] = nb_lon*dlon + 240.
lons[2*i+1][3*nb_lon:] = nb_lon*dlon + 240.
lons = lons - 180
val = lats + lons
# Crash
##m = Basemap(projection='robin',lon_0=0,resolution=None)
#m = Basemap(projection='mill',lon_0=0)
m = Basemap(projection='ortho', lat_0=0,lon_0=0)
x, y = m(lons, lats)
m.pcolor(x,y,val, edgecolors='k', linewidths=1)
m.drawcoastlines()
m.drawparallels(np.arange(-90.,91.,30.))
m.drawmeridians(np.arange(-180.,181.,60.))
plt.show()
This does exactly what I want : drawing rectangles and filling them with one color.
But it is very slow (too slow). A lot of cells are unused : at the end of a latidude line, we set the width of unused cells to 0.
Another issue is some projections crash (Robin for example).

Related

xarray : how to stack several pcolormesh figures above a map?

For a ML project I'm currently on, I need to verify if the trained data are good or not.
Let's say that I'm "splitting" the sky into several altitude grids (let's take 3 values for the moment) and for a given region (let's say, Europe).
One grid could be a signal reception strength (RSSI), another one the signal quality (RSRQ)
Each cell of the grid is therefor a rectangle and it has a mean value of each measurement (i.e. RSSI or RSRQ) performed in that area.
I have hundreds of millions of data
In the code below, I know how to draw a coloured mesh with xarray for each altitude: I just use xr.plot.pcolormesh(lat,lon, the_data_set); that's fine
But this will only give me a "flat" figure like this:
RSSI value at 3 different altitudes
I need to draw all the pcolormesh() of a dataset for each altitude in such way that:
1: I can have the map at the bottom
2: Each pcolormesh() is stacked and "displayed" at its altitude
3: I need to add a 3d scatter plot for testing my trained data
4: Need to be interactive as I have to zoom in areas
For 2 and 3 above, I managed to do something using plt and cartopy :
enter image description here
But plt/cartopy combination is not as interactive as plotly.
But plotly doesn't have the pcolormesh functionality
And still ... I don't know in anycase, how to "stack" the pcolormesh results that I did get above.
I've been digging Internet for few days but I didn't find something that could satisfy all my criteria.
What I did to get my pcolormesh:
import numpy as np
import xarray as xr
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
class super_data():
def __init__(self, lon_bound,lat_bound,alt_bound,x_points,y_points,z_points):
self.lon_bound = lon_bound
self.lat_bound = lat_bound
self.alt_bound = alt_bound
self.x_points = x_points
self.y_points = y_points
self.z_points = z_points
self.lon, self.lat, self.alt = np.meshgrid(np.linspace(self.lon_bound[0], self.lon_bound[1], self.x_points),
np.linspace(self.lat_bound[0], self.lat_bound[1], self.y_points),
np.linspace(self.alt_bound[0], self.alt_bound[1], self.z_points))
self.this_xr = xr.Dataset(
coords={'lat': (('latitude', 'longitude','altitude'), self.lat),
'lon': (('latitude', 'longitude','altitude'), self.lon),
'alt': (('latitude', 'longitude','altitude'), self.alt)})
def add_data_array(self,ds_name,ds_min,ds_max):
def create_temp_data(ds_min,ds_max):
data = np.random.randint(ds_min,ds_max,size=self.y_points * self.x_points)
return data
temp_data = []
# Create "z_points" number of layers in the z axis
for i in range(self.z_points):
temp_data.append(create_temp_data(ds_min,ds_max))
data = np.concatenate(temp_data)
data = data.reshape(self.z_points,self.x_points, self.y_points)
self.this_xr[ds_name] = (("altitude","longitude","latitude"),data)
def plot(self,dataset, extent=None, plot_center=False):
# I want t
if np.sqrt(self.z_points) == np.floor(np.sqrt(self.z_points)):
side_size = int(np.sqrt(self.z_points))
else:
side_size = int(np.floor(np.sqrt(self.z_points) + 1))
fig = plt.figure()
i_ax=1
for i in range(side_size):
for j in range(side_size):
if i_ax < self.z_points+1:
this_dataset = self.this_xr[dataset].sel(altitude=i_ax-1)
# Initialize figure with subplots
ax = fig.add_subplot(side_size, side_size, i_ax, projection=ccrs.PlateCarree())
i_ax += 1
ax.coastlines()
this_dataset.plot.pcolormesh('lon', 'lat', ax=ax, infer_intervals=True, alpha=0.5)
else:
break
plt.tight_layout()
plt.show()
if __name__ == "__main__":
# Wanted coverage :
lons = [-15, 30]
lats = [35, 65]
alts = [1000, 5000]
xarr = super_data(lons,lats,alts,10,8,3)
# Add some fake data
xarr.add_data_array("RSSI",-120,-60)
xarr.add_data_array("pressure",700,1013)
xarr.plot("RSSI",0)
Thanks for you help

Interactive zoom with y axis autoscale

I'm trying to alter the behavior of the interactive zoom-to-rectangle so that data are autoscaled on the y axis based on the new visible x axis range. Indeed I am exploring a lot of telemetry data with large dynamic and I need to zoom back and forth a lot. So it would be good to have autoscaling of the y axis. After much googling and searching the forum I've not found anything that helped to achieve that. Or may be I searched wrong.
Could anyone give me some guidance ?
One option is to connect to the xlim_changed signal and calculate, based on the current x limits, the new limits for the y axis such that all data is included.
The following does this and it's a bit more complicated than initially thought, because just setting the y limits will not work, because they would be overwritten by the (simultaneous) ylim_changed event that the zooming triggers. So instead, there is a timer set to 10 milliseconds, that artificially sets the limits, after that event has been processed.
import numpy as np; np.random.seed(42)
import matplotlib.pyplot as plt
class AutoScaleY():
def __init__(self, line, margin=0.05):
self.margin = margin
self.line = line
self.ax = line.axes
self.ax.callbacks.connect('xlim_changed', self.rescale_y)
def rescale_y(self,evt=None):
xmin, xmax = ax.get_xlim()
x, y = line.get_data()
cond = (x >= xmin) & (x <= xmax)
yrest = y[cond]
margin = (yrest.max()-yrest.min())*self.margin
self.ybounds = [yrest.min()-margin, yrest.max()+margin]
self.timer = self.ax.figure.canvas.new_timer(interval=10)
self.timer.single_shot = True
self.timer.add_callback(self.change_y)
self.timer.start()
def change_y(self):
self.ax.set_ylim(self.ybounds)
self.ax.figure.canvas.draw()
x=np.linspace(0,100,1001)
y = np.sin(x/16) + np.cumsum(np.random.randn(1001))/30.
fig, ax = plt.subplots()
line, = ax.plot(x,y)
r = AutoScaleY(line)
plt.show()

Plotting Lat/Long Points Using Basemap

I am trying to plot points on a map using matplotlib and Basemap, where the points represent the lat/long for specific buildings. My map does indeed plot the points, but puts them in the wrong location. When I use the same data and do the same thing using Bokeh, instead of matplotlib and basemap, I get the correct plot.
Here is the CORRECT result in Bokeh:
Bokeh Version
And here is the INCORRECT result in Basemap:
Basemap Version
I have seen discussion elsewhere on StackOverflow that suggested this might be related to the fact that plot() "shifts" the longitude somehow. I've tried the suggestion from there, which was to include the line:
lons, lats = m.shiftdata(long, lat)
and then use the shifted data. That didn't have any visible impact.
My full sample code which generates both of the plots in Basemap and Bokeh is here:
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import pandas as pd
from bokeh.plotting import figure, show
from bokeh.sampledata.us_states import data as states
from bokeh.models import ColumnDataSource, Range1d
# read in data to use for plotted points
buildingdf = pd.read_csv('buildingdata.csv')
lat = buildingdf['latitude'].values
long = buildingdf['longitude'].values
# determine range to print based on min, max lat and long of the data
margin = .2 # buffer to add to the range
lat_min = min(lat) - margin
lat_max = max(lat) + margin
long_min = min(long) - margin
long_max = max(long) + margin
# create map using BASEMAP
m = Basemap(llcrnrlon=long_min,
llcrnrlat=lat_min,
urcrnrlon=long_max,
urcrnrlat=lat_max,
lat_0=(lat_max - lat_min)/2,
lon_0=(long_max-long_min)/2,
projection='merc',
resolution = 'h',
area_thresh=10000.,
)
m.drawcoastlines()
m.drawcountries()
m.drawstates()
m.drawmapboundary(fill_color='#46bcec')
m.fillcontinents(color = 'white',lake_color='#46bcec')
# convert lat and long to map projection coordinates
lons, lats = m(long, lat)
# plot points as red dots
m.scatter(lons, lats, marker = 'o', color='r')
plt.show()
# create map using Bokeh
source = ColumnDataSource(data = dict(lat = lat,lon = long))
# get state boundaries
state_lats = [states[code]["lats"] for code in states]
state_longs = [states[code]["lons"] for code in states]
p = figure(
toolbar_location="left",
plot_width=1100,
plot_height=700,
)
# limit the view to the min and max of the building data
p.y_range = Range1d(lat_min, lat_max)
p.x_range = Range1d(long_min, long_max)
p.xaxis.visible = False
p.yaxis.visible = False
p.xgrid.grid_line_color = None
p.ygrid.grid_line_color = None
p.patches(state_longs, state_lats, fill_alpha=0.0,
line_color="black", line_width=2, line_alpha=0.3)
p.circle(x="lon", y="lat", source = source, size=4.5,
fill_color='red',
line_color='grey',
line_alpha=.25
)
show(p)
I don't have enough reputation points to post a link to the data or to include it here.
In the basemap plot the scatter points are hidden behind the fillcontinents. Removing the two lines
#m.drawmapboundary(fill_color='#46bcec')
#m.fillcontinents(color = 'white',lake_color='#46bcec')
would show you the points. Because this might be undesired, the best solution would be to place the scatter on top of the rest of the map by using the zorder argument.
m.scatter(lons, lats, marker = 'o', color='r', zorder=5)
Here is the complete code (and I would like to ask you to include this kind of runnable minimal example with hardcoded data next time asking a question, as it saves everyone a lot of work inventing the data oneself):
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import pandas as pd
import io
u = u"""latitude,longitude
42.357778,-71.059444
39.952222,-75.163889
25.787778,-80.224167
30.267222, -97.763889"""
# read in data to use for plotted points
buildingdf = pd.read_csv(io.StringIO(u), delimiter=",")
lat = buildingdf['latitude'].values
lon = buildingdf['longitude'].values
# determine range to print based on min, max lat and lon of the data
margin = 2 # buffer to add to the range
lat_min = min(lat) - margin
lat_max = max(lat) + margin
lon_min = min(lon) - margin
lon_max = max(lon) + margin
# create map using BASEMAP
m = Basemap(llcrnrlon=lon_min,
llcrnrlat=lat_min,
urcrnrlon=lon_max,
urcrnrlat=lat_max,
lat_0=(lat_max - lat_min)/2,
lon_0=(lon_max-lon_min)/2,
projection='merc',
resolution = 'h',
area_thresh=10000.,
)
m.drawcoastlines()
m.drawcountries()
m.drawstates()
m.drawmapboundary(fill_color='#46bcec')
m.fillcontinents(color = 'white',lake_color='#46bcec')
# convert lat and lon to map projection coordinates
lons, lats = m(lon, lat)
# plot points as red dots
m.scatter(lons, lats, marker = 'o', color='r', zorder=5)
plt.show()

Map a colorbar based on plot instead of imshow

I'm trying to get a colorbar for the following minimal example of my code.
g1 = gridspec.GridSpec(1, 1)
f, ((ax0)) = plt.subplots(1, 1)
ax0 = subplot(g1[0])
cmap = matplotlib.cm.get_cmap('viridis')
for i in linspace(0,1,11):
x = [-1,0,1]
y = [i,i,i]
rgba = cmap(i)
im = ax0.plot(x,y,color=rgba)
f.colorbar(im)
I also tried f.colorbar(cmap)
Probably pretty obvious, but I get errors such as
'ListedColormap' object has no attribute 'autoscale_None'
In reality, the value defining i is more complex, but I think this should do the trick. My data is plotted with plot and not with imshow (for which I know how to make the colormap).
The answers so far seem overly complicated. fig.colorbar() expects a ScalarMappable as its first argument. Often ScalarMappables are produced by imshow or contourplots and are readily avaible.
In this case you would need to define your custom ScalarMappable to provide to the colorbar.
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
cmap = plt.cm.get_cmap('viridis')
for i in np.linspace(0,1,11):
x = [-1,0,1]
y = [i,i,i]
rgba = cmap(i)
im = ax.plot(x,y,color=rgba)
sm = plt.cm.ScalarMappable(cmap=cmap)
sm.set_array([])
fig.colorbar(sm)
plt.show()
You should pass an Image or ContourSet when you call colorbar on a Figure.
You can make an image of the data points by calling plt.imshow with the data. You can start with this:
data = []
for i in np.linspace(0,1,11):
x = [-1,0,1]
y = [i,i,i]
rgba = cmap(i)
ax0.plot(x,y,color=rgba)
data.append([x, y])
image = plt.imshow(data)
figure.colorbar(image)
plt.show()
Reference:
https://matplotlib.org/api/figure_api.html#matplotlib.figure.Figure.colorbar
Oluwafemi Sule's solution almost works, but it plots the matrix into the same figure as the lines. Here a solution that opens a second figure, does the imshow call on that second figure, uses the result to draw the colorbar in the first figure, and then closes the second figure before calling plt.show():
import matplotlib
from matplotlib import pyplot as plt
from matplotlib import gridspec
import numpy as np
cmap = matplotlib.cm.get_cmap('viridis')
g1 = gridspec.GridSpec(1, 1)
f0, ((ax0)) = plt.subplots(1, 1)
f1, ((ax1)) = plt.subplots(1, 1)
for i in np.linspace(0,1,11):
x = [-1,0,1]
y = [i,i,i]
rgba = cmap(i)
ax0.plot(x,y,color=rgba)
data = np.linspace(0,1,100).reshape((10,10))
image = ax1.imshow(data)
f0.colorbar(image)
plt.close(f1)
plt.show()
The result looks like this:

How can draw a line in matplotlib so that the edge (not the center) of the drawn line follows the plotted data?

I'm working on a figure to show traffic levels on a highway map. The idea is that for each
highway segment, I would plot two lines - one for direction. The thickness of each
line
would correspond to the traffic volume in that direction. I need to plot the lines
so that the left edge (relative to driving direction) of the drawn line follows
the shape of the highway segment. I would like to specify the shape in data coordinates,
but I would like to specify the thickness of the line in points.
My data is like this:
[[((5,10),(-7,2),(8,9)),(210,320)],
[((8,4),(9,1),(8,1),(11,4)),(2000,1900)],
[((12,14),(17,14)),(550,650)]]
where, for example, ((5,10),(-7,2),(8,9)) is a sequence of x,y values giving the shape of a highway segment, and (210,320) is traffic volumes in the forward and reverse direction, respectively
Looks matter: the result should be pretty.
I figured out a solution using matplotlib.transforms.Transform and shapely.geometry.LineString.parallel_offset.
Note that shapely's parallel_offset method can sometimes return a MultiLineString, which
is not handled by this code. I've changed the second shape so it does not cross over itself to avoid this problem. I think this problem would happen rarely happen in my application.
Another note: the documentation for matplotlib.transforms.Transform seems to imply that the
array returned by the transform method must be the same shape as the array passed
as an argument, but adding additional points to plot in the transform method seems
to work here.
#matplotlib version 1.1.0
#shapely version 1.2.14
#Python 2.7.3
import matplotlib.pyplot as plt
import shapely.geometry
import numpy
import matplotlib.transforms
def get_my_transform(offset_points, fig):
offset_inches = offset_points / 72.0
offset_dots = offset_inches * fig.dpi
class my_transform(matplotlib.transforms.Transform):
input_dims = 2
output_dims = 2
is_separable = False
has_inverse = False
def transform(self, values):
l = shapely.geometry.LineString(values)
l = l.parallel_offset(offset_dots,'right')
return numpy.array(l.xy).T
return my_transform()
def plot_to_right(ax, x,y,linewidth, **args):
t = ax.transData + get_my_transform(linewidth/2.0,ax.figure)
ax.plot(x,y, transform = t,
linewidth = linewidth,
solid_capstyle = 'butt',
**args)
data = [[((5,10),(-7,2),(8,9)),(210,320)],
[((8,4),(9,1),(8,1),(1,4)),(2000,1900)],
[((12,14),(17,16)),(550,650)]]
fig = plt.figure()
ax = fig.add_subplot(111)
for shape, volumes in data:
x,y = zip(*shape)
plot_to_right(ax, x,y, volumes[0]/100., c = 'blue')
plot_to_right(ax, x[-1::-1],y[-1::-1], volumes[1]/100., c = 'green')
ax.plot(x,y, c = 'grey', linewidth = 1)
plt.show()
plt.close()