Fill oceans in basemap (don't plot colour grid on oceans) - python

My question is the same as this one: Fill oceans in basemap. I know there is an answer given here, but when I try to apply the answer to my code, it doesn't work.
Here is my original graph.
I want to make the oceans white and only show the colour differences on Antarctica.
Here is the code that I tried:
from mpl_toolkits.basemap import Basemap
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Path, PathPatch
D = pd.read_pickle('directory')
fig, ax = plt.subplots()
lons = D['longitude'][:]
lats = D['latitude'][:]
lon, lat = np.meshgrid(lons, lats)
variable_to_graph = D['variable_to_graph']
m = Basemap(projection='spstere', boundinglat=-60, lon_0=180, resolution='l')
m.drawcoastlines(color='k') # shows Antarctica
x,y = m(lon, lat)
ax.pcolormesh(x, y, variable_to_graph, cmap='viridis', vmin=-400, vmax=-0)
# 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()
Which gives me this.
I have also tried using a is_land() function but as the resolution is relatively low (1x1 degrees) the image is not very neat.
Any help would be appreciated as to how to get the colour map to only show on Antarctica and for it to stop on the edges of Antarctica.
EDIT: I have updated my question with code that can be tested.
from mpl_toolkits.basemap import Basemap
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Path, PathPatch
# D = pd.read_pickle('directory')
fig, ax = plt.subplots()
lons = range(0,361)
lats = [-60, -61, -62, -63, -64, -65, -66, -67, -68, -69, -70,
-71, -72, -73, -74, -75, -76, -77, -78, -79, -80, -81,
-82, -83, -84, -85, -86, -87, -88, -89, -90]
lon, lat = np.meshgrid(lons, lats)
variable_to_graph = np.random.rand(31,361)
m = Basemap(projection='spstere', boundinglat=-60, lon_0=180, resolution='l')
m.drawcoastlines(color='k') # shows Antarctica
x,y = m(lon, lat)
ax.pcolormesh(x, y, variable_to_graph, cmap='viridis', vmin=-400, vmax=-0)
# 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()

Related

Python Basemap Heatmap

I am trying to copy the method that was done on this page: https://makersportal.com/blog/2018/7/20/geographic-mapping-from-a-csv-file-using-python-and-basemap under "Mapping Interesting Data" to have a color bar associated with my data.
Right now I just get a plain map of South America, which is what I want as my background but there is no data included.
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
m = Basemap(projection='mill',
llcrnrlat = -30, #bottom
llcrnrlon = -120, #left
urcrnrlat = 20, #top
urcrnrlon = -50, #right
resolution='c')
m.drawcoastlines()
m.drawcountries()
# format colors for elevation range
SST_min = np.min(df5.DaasgardSST)
SST_max = np.max(df5.DaasgardSST)
cmap = plt.get_cmap('gist_earth')
normalize = matplotlib.colors.Normalize(vmin=SST_min, vmax=SST_max)
# plot SST with different colors
for i in range(0,len(df5.DaasgardSST)):
x,y = m(lon,lat)
color_interp = np.interp(df5,[SST_min,SST_max],[0,30])
plt.plot(x,y,marker='o',markersize=6,color=cmap(int(color_interp)))
# format the colorbar
cax, _ = matplotlib.colorbar.make_axes(ax)
cbar = matplotlib.colorbar.ColorbarBase(cax, cmap=cmap,norm=normalize,label='Elevation')
plt.title('Title')
plt.show()

Aligning data (contourf) on Basemap

I've started working with Basemap, which seems potentially very useful.
If I plot some global data on a latitude/longitude grid as filled contours, it works great: Iff I leave the lat_0 and lon_0 as zero. Once I change the center location, the map moves but the data doesn't. I would be grateful for advice.
I've created a simple version of the code I'm using, with some simple sample data that illustrates the problem. The values should be (are) large at the equator but small at the poles. If you run the code with lat_0 and lon_0 = 0, it works fine. But if you change the center location to a different coordinate, the same pattern/data is presented even though the map has moved.
from mpl_toolkits.basemap import Basemap, cm
import matplotlib.pyplot as plt
import numpy as np
# create data
lat = np.linspace(-90,90,num=180)
lon = np.linspace(-180,180,num=361)
h2o_north = np.linspace(1,65,num=90)
h2o_south = np.flipud(h2o_north)
h2o = np.append(h2o_north,h2o_south)
data = np.transpose(np.tile(h2o,(len(lon),1)))
# create figure and axes instances
fig = plt.figure(figsize=(10,10))
ax = fig.add_axes([0.1,0.1,0.8,0.8])
# create map
m = Basemap(projection='ortho',lon_0=-50,lat_0=50,resolution='l')
# draw coastlines and country boundaries
m.drawcoastlines()
m.drawcountries()
# draw parallels
parallels = np.arange(-90.,90,10.)
m.drawparallels(parallels)
# draw meridians
meridians = np.arange(180.,360.,10.)
m.drawmeridians(meridians)
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 projection coordinates
# draw filled contours.
clevs = np.linspace(0,70,num=281)
cs = m.contourf(x,y,data,clevs,cmap=plt.cm.jet)
# colorbar
cbar = m.colorbar(cs,location='bottom',pad="5%",ticks=np.linspace(0,70,15))
cbar.set_label('Scale of the data')
plt.title('Some global data', fontsize=14)
Use np.meshgrid() to create the meshgrid of lon-lat, then, convert it to projection coordinates, and the data are ready to generate contours and plot.
Here is the working code:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
# data for z (2D array)
h2o_north = np.linspace(1, 65, num=90)
h2o_south = np.flipud(h2o_north)
h2o = np.append(h2o_north, h2o_south)
data = np.transpose(np.tile(h2o, (len(h2o_north), 1)))
# create figure and axes instances
fig = plt.figure(figsize=(8, 8))
ax = fig.add_subplot()
# create basemap instance
m = Basemap(projection='ortho', lon_0=-50, lat_0=50, resolution='c', ax=ax)
# create meshgrid covering the whole globe with ...
# conforming dimensions of the `data`
lat = np.linspace(-90, 90, data.shape[0])
lon = np.linspace(-180, 180, data.shape[1])
xs, ys = np.meshgrid(lon, lat) # basic mesh in lon, lat (degrees)
x, y = m(xs, ys) # convert (lon,lat) to map (x,y)
# draw filled contours
clevs = np.linspace(0, np.max(data), 60)
cs = m.contourf(x, y, data, clevs, cmap=plt.cm.jet)
m.drawcoastlines()
m.drawcountries()
m.drawmeridians(range(-180, 180, 30))
m.drawparallels(range(-90, 90, 30))
# draw colorbar
cbar = m.colorbar(cs, location='bottom', pad="5%", ticks=np.linspace(0, np.max(data), 5))
cbar.set_label('Scale of the data')
plt.show()
The resulting plot:

Python Matplotlib - imshow but with hexagons

Code is:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors
example_data = np.random.randint(4, size=(40,44))
cmap = colors.ListedColormap(['black', 'green', 'red', 'blue'])
bounds = [0,1,2,3,4]
norm = colors.BoundaryNorm(bounds, cmap.N)
img = plt.imshow(example_data, interpolation = 'nearest', origin = 'lower',
cmap = cmap, norm = norm)
Which gets me roughly what I want. What I am looking for is if there is a way to get the shape of each tile to be hexagonal rather than square? I think imshow might not be the way to do it but if there is a way you can change the default tile it would be good.
Thanks.
Here is a solution using patches:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors
import matplotlib.patches as mpatches
from matplotlib.collections import PatchCollection
nx = 40
ny = 44
example_data = np.random.randint(4, size=(nx,ny))
cmap = colors.ListedColormap(['black', 'green', 'red', 'blue'])
bounds = [0,1,2,3,4]
norm = colors.BoundaryNorm(bounds, cmap.N)
x = np.linspace(0, 1, nx)
y = np.linspace(0, 1, ny)
X, Y = np.meshgrid(x, y)
dx = np.diff(x)[0]
dy = np.diff(y)[0]
ds = np.sqrt(dx**2 + dy**2)
patches = []
for i in x:
for n, j in enumerate(y):
if n%2:
polygon = mpatches.RegularPolygon([i-dx/2., j], 6, 0.6*dx)
else:
polygon = mpatches.RegularPolygon([i, j], 6, 0.6*dx)
patches.append(polygon)
collection = PatchCollection(patches, cmap=cmap, norm=norm, alpha=1.0)
fig, ax = plt.subplots(1,1)
ax.add_collection(collection)
collection.set_array(example_data.ravel())
plt.show()
which looks like this,
Previous solution, it doesn't tessellate nicely and the hexagons are poorly shaped but you could use a scatter plot with coloured hexagons,
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors
nx = 40
ny = 44
example_data = np.random.randint(4, size=(nx,ny))
cmap = colors.ListedColormap(['black', 'green', 'red', 'blue'])
bounds = [0,1,2,3,4]
norm = colors.BoundaryNorm(bounds, cmap.N)
x = np.linspace(0, 1, nx)
y = np.linspace(0, 1, ny)
X, Y = np.meshgrid(x, y)
img = plt.scatter(X.ravel(),Y.ravel(),c=example_data.ravel(), cmap=cmap, norm=norm, s=360, marker=(6, 0), alpha=0.4)
plt.colorbar(img)
plt.show()
which looks like,

How to format a polar contour plot in matplotlib

I have some sample code to make a polar contour plot:
import numpy as np
import matplotlib.pyplot as plt
azimuths = np.radians(np.linspace(0, 180, 90))
zeniths = np.arange(50, 5050, 50)
r, theta = np.meshgrid(zeniths, azimuths)
values = 90.0+5.0*np.random.random((len(azimuths), len(zeniths)))
fig, ax = plt.subplots(subplot_kw=dict(projection='polar'))
ax.set_theta_zero_location("W")
pp = plt.contourf(theta, r, values, label='tmp')
cbar = plt.colorbar(pp, orientation='vertical')
cbar.ax.set_ylabel('scale label')
plt.show()
which gives me something like:
...but I would like something more like this:
...with space in the middle, and only showing 0 to 180 degrees. Does anyone know of a convenient way to do this?
I'm not sure how convenient this is, but here's a hackable solution (taken from here):
import numpy as np
import mpl_toolkits.axisartist.floating_axes as floating_axes
from matplotlib.projections import PolarAxes
from mpl_toolkits.axisartist.grid_finder import FixedLocator, MaxNLocator, \
DictFormatter
import matplotlib.pyplot as plt
tr = PolarAxes.PolarTransform()
degree_ticks = lambda d: (d*np.pi/180, "%d$^\\circ$"%(360-d))
angle_ticks = map(degree_ticks, np.linspace(180, 360, 5))
grid_locator1 = FixedLocator([v for v, s in angle_ticks])
tick_formatter1 = DictFormatter(dict(angle_ticks))
tick_formatter2 = DictFormatter(dict(zip(np.linspace(1000, 6000, 6),
map(str, np.linspace(0, 5000, 6)))))
grid_locator2 = MaxNLocator(5)
gh = floating_axes.GridHelperCurveLinear(tr,
extremes=(2*np.pi, np.pi, 1000, 6000),
grid_locator1=grid_locator1,
grid_locator2=grid_locator2,
tick_formatter1=tick_formatter1,
tick_formatter2=tick_formatter2)
fig = plt.figure()
ax = floating_axes.FloatingSubplot(fig, 111, grid_helper=gh)
fig.add_subplot(ax)
azimuths = np.radians(np.linspace(180, 360, 90)) # added 180 degrees
zeniths = np.arange(1050, 6050, 50) # added 1000
r, theta = np.meshgrid(zeniths, azimuths)
values = 90.0+5.0*np.random.random((len(azimuths), len(zeniths)))
aux_ax = ax.get_aux_axes(tr)
aux_ax.patch = ax.patch
ax.patch.zorder = 0.9
aux_ax.contourf(theta, r, values) # use aux_ax instead of ax
plt.show()
Note that (in order to get the space near the origin), you'll need to shift all your data points by 1000 in the radius direction and by pi in the theta direction (to get the lower hemisphere).
This yields:

Custom scale for radial contour plot in matplotlib

I have a sample script to generate a polar contour plot in matplotlib:
import os
import math
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.axisartist.floating_axes as floating_axes
from matplotlib.projections import PolarAxes
from mpl_toolkits.axisartist.grid_finder import FixedLocator, MaxNLocator, DictFormatter
import random
# ------------------------------------ #
def setup_arc_radial_axes(fig, rect, angle_ticks, radius_ticks, min_rad, max_rad):
tr = PolarAxes.PolarTransform()
pi = np.pi
grid_locator1 = FixedLocator([v for v, s in angle_ticks])
tick_formatter1 = DictFormatter(dict(angle_ticks))
grid_locator2 = FixedLocator([a for a, b in radius_ticks])
tick_formatter2 = DictFormatter(dict(radius_ticks))
grid_helper = floating_axes.GridHelperCurveLinear(tr,
extremes=((370.0*(pi/180.0)), (170.0*(pi/180.0)), max_rad, min_rad),
grid_locator1=grid_locator1,
grid_locator2=grid_locator2,
tick_formatter1=tick_formatter1,
tick_formatter2=tick_formatter2,
)
ax1 = floating_axes.FloatingSubplot(fig, rect, grid_helper=grid_helper)
fig.add_subplot(ax1)
ax1.grid(True)
# create a parasite axes whose transData in RA, cz
aux_ax = ax1.get_aux_axes(tr)
aux_ax.patch = ax1.patch
ax1.patch.zorder=0.9
#ax1.axis["left"].set_ticklabel_direction("+")
return ax1, aux_ax
# ------------------------------------ #
# write angle values to the plotting array
angles = []
for mic_num in range(38):
angle = float(mic_num)*(180.0/36.0)*(math.pi/180.0)+math.pi
angles.append(angle)
# ------------------------------------ #
### these are merely the ticks that appear on the plot axis
### these don't actually get plotted
angle_ticks = range(0,190,10)
angle_ticks_rads = [a*math.pi/180.0 for a in angle_ticks]
angle_ticks_rads_plus_offset = [a+math.pi for a in angle_ticks_rads]
angle_ticks_for_plot = []
for i in range(len(angle_ticks)):
angle_ticks_for_plot.append((angle_ticks_rads_plus_offset[i],r"$"+str(angle_ticks[i])+"$"))
# ------------------------------------ #
scale = 1.0
aspect = 1.50
height = 8.0
fig = plt.figure(1, figsize=(height*aspect*scale, height*scale))
fig.subplots_adjust(wspace=0.3, left=0.05, right=0.95, top=0.84)
fig.subplots_adjust()
plot_real_min = 30.0
plot_real_max = 100.0
plot_fake_min = 0.0
plot_fake_max = 5000.0
rad_tick_increment = 500.0
radius_ticks = []
for i in range(int(plot_fake_min),int(plot_fake_max)+int(rad_tick_increment),int(rad_tick_increment)):
plot_fake_val = ((i-plot_fake_min)/(plot_fake_max-plot_fake_min))*(plot_real_max-plot_real_min)+plot_real_min
radius_ticks.append((plot_fake_val, r"$"+str(i)+"$"))
ax2, aux_ax2 = setup_arc_radial_axes(fig, 111, angle_ticks_for_plot, radius_ticks, plot_real_min, plot_real_max)
azimuths = np.radians(np.linspace(0, 180, 91))
azimuths_adjusted = [ (x + math.pi) for x in azimuths ]
zeniths = np.arange(0, 5050, 50)
zeniths_adjusted = [((x-plot_fake_min)/(plot_fake_max-plot_fake_min))*(plot_real_max-plot_real_min)+plot_real_min for x in zeniths]
r, theta = np.meshgrid(zeniths_adjusted, azimuths_adjusted)
values = 90.0+5.0*np.random.random((len(azimuths), len(zeniths)))
aux_ax2.contourf(theta, r, values)
cbar = plt.colorbar(aux_ax2.contourf(theta, r, values), orientation='vertical')
cbar.ax.set_ylabel('Contour Value [Unit]', fontsize = 16)
plt.suptitle('Plot Title ', fontsize = 24, weight="bold")
plt.legend(loc=3,prop={'size':20})
plt.xlabel('Angle [deg]', fontsize=20, weight="bold")
plt.ylabel('Frequency [Hz]', fontsize=20, weight="bold")
# plt.show()
plt.savefig('test.png', dpi=100)
plt.close()
This script will generate a plot that looks something like:
My question is how can I plot with an alternate color bar scale? Is it possible to define a custom scale?
Something like a blue-white-red scale where deltas around a central value can easily be shown would be the best, something like:
You can create a custom scale, but matplotlib already has what you want. All you have to do is add an argument to contourf:
aux_ax2.contourf(theta, r, values, cmap = 'bwr')
If you don't like bwr, coolwarm and seismic are also blue to red. If you need to reverse the scale, just add _r to the colormap name. You can find more colormaps here: http://matplotlib.org/examples/color/colormaps_reference.html
I can't run your code, but I think you could solve your problem this way:
from matplotlib import pyplot as plt
import matplotlib as mpl
f = plt.figure(figsize=(5,10))
ax = f.add_axes([0.01, 0.01, 0.4, 0.95])
#here we create custom colors
cmap = mpl.colors.LinearSegmentedColormap.from_list(name='Some Data',colors=['b', 'w','w', 'r'])
cb = mpl.colorbar.ColorbarBase(ax, cmap=cmap, orientation='vertical')
cb.set_label('Some Data')
plt.show()
And if linear way is not what you are looking for here is some other types:
http://matplotlib.org/api/colors_api.html#module-matplotlib.colors

Categories

Resources