Related
When plotting low-resolution contours over a high-resolution coastline I get the following result
I would like to fill the area outside of the coastlines (caused by the low resolution of the underlining filled contour plot) with the ocean color at high resolution.
I tried to use the land-sea mask option without coloring the land
m.drawlsmask(land_color=(0, 0, 0, 0), ocean_color='#2081C3',
resolution='h', lakes=True, zorder=2, grid=1.25)
but the 1.25 resolution is not enough for this level of detail (see second image)
Unfortunately there is no builtin method that fills the ocean (and lakes) with the same resolution used for the coastlines ('h' in my case). As a workaround is there any way to fill the area "outside" of the coastline using the original resolution?
I could use a high resolution land-sea mask in drawlsmask but that's a waste of resource since basemap already has indirectly that information with the polygons given by the coastlines.
General notes:
It looks like other questions on Stack Overflow suggest to use the builtin land sea mask of basemap. I can't because it is too low resolution at this zoom level.
Unfortunately I cannot use Cartopy. I already built my entire pipeline on Cartopy but it is way too slow for what I have to do.
I ended up using the solution posted in Fill oceans in basemap adapted to my needs. Note that, in order to retain the lakes, I had to do multiple passes of fillcontinents, so that's how I did
# extents contain the projection extents as [lon1, lon2, lat1, lat2]
m = Basemap(projection='merc',
llcrnrlat=extents[2],
urcrnrlat=extents[3],
llcrnrlon=extents[0],
urcrnrlon=extents[1],
lat_ts=20,
resolution='h')
m.fillcontinents(color='#c5c5c5', lake_color='#acddfe', zorder=1)
# Fill again the lakes over the contour plot
m.fillcontinents(color=(0, 0, 0, 0), lake_color='#acddfe', zorder=3)
ax = plt.gca()
# Workaround to add high resolution oceans
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]
polys = [map_edges]+polys[:]
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='#acddfe', lw=0, zorder=2)
ax.add_patch(patch)
m.drawcountries(linewidth=0.6)
m.readshapefile(f'{SHAPEFILES_DIR}/ITA_adm_shp/ITA_adm2',
'ITA_adm2', linewidth=0.1, color='gray', zorder=5)
which gives something like this
Not perfect (because the shapefile which defines the coastline has a different resolution), but definitely better than before.
TL; DR
How to I set the map to be exactly 1600x900 px?
Description
I am trying to draw a map with Jupyter Notebook using Basemap library as follows:
from mpl_toolkits.basemap import Basemap
import numpy as np
import matplotlib.pyplot as plt
atlas = Basemap(
llcrnrlon = -10.5, # Longitude lower right corner
llcrnrlat = 35, # Latitude lower right corner
urcrnrlon = 14.0, # Longitude upper right corner
urcrnrlat = 44.0, # Latitude upper right corner
resolution = 'i', # Crude resolution
projection = 'tmerc', # Transverse Mercator projection
lat_0 = 39.5, # Central latitude
lon_0 = -3.25 # Central longitude
)
atlas.drawmapboundary(fill_color='aqua')
atlas.fillcontinents(color='#cc9955',lake_color='aqua')
atlas.drawcoastlines()
plt.show()
and getting the following result
Is it possible to make the drawn map larger, defining the minimum width and height it should have?
You can use figure.
For example:
plt.figure(figsize=(1, 1))
Creates an inch-by-inch image, which will be 80-by-80 pixels unless you also give a different dpi argument.
You can change dpi with two possible ways
Way 1: Passing it as an argument to the figure as the following:(i.e 300)
plt.figure(figsize=(1, 1),dpi=300)
Way 2:Passing it to savefig()
plt.savefig("foo.png", dpi=300)
Perfect Example
Here is an example figure as illustration.
This plot present the satellite SO2 column data for part of Europe.
Due to the difference between satellite and longitude, the grid network which fit the satellite scanning principle are not parallel to longitude.
I don't know if it's possible to draw this kind of grid network using pcolor or pcolormesh in matplotlib.basemap. So, I post my question here.
I stumbled upon this question because I was also looking for a way to plot gridded satellite measurements on a map, using matplotlib and basemap.
I am not sure if my idea is relevant to your question, as my pixels can assume only a very limited discrete number of values (4), but I decided to answer anyway, also to find out if you eventually found an efficient solution. What I did was to directly plot each single pixel as a polygon on the map, by using the method Polygon.
I set the alpha value to be function of the underlying physical measurement. In my case—a cloud mask plot—this strategy works out pretty well.
Here's the function that gets called for each pixel to be plotted:
def draw_cloud_pixel(lats, lons, index, mapplot):
"""Draw a pixel on the map. The fill color alpha level depends on the cloud index,
ranging from 0.1 (almost fully transparent) for confidently clear pixels to 1 (fully opaque)
for confidently cloudy pixels.
Keyword arguments:
lats -- Array of latitude values for the pixel 4 corner points (numpy array)
lons -- Array of longitudes values for the pixel 4 corner points (numpy array)
index -- Cloud mask index for given pixel:
0: confidently_cloudy
1: probably_cloudy
2: probably_clear
3: confidently_clear
mapplot -- Map object for coordinate transformation
Returns:
None
"""
x, y = mapplot(lons, lats)
xy = zip(x,y)
poly = Polygon(xy, facecolor='white', alpha=1-0.3*index)
plt.gca().add_patch(poly)
In my main plotting routine, I then call the draw_cloud_pixel function for each pixel in the selected region:
# draw plot, each pixel at the time
for scanline in xrange(select_cp_lat.shape[0]):
for pixel in xrange(select_cp_lat.shape[1]):
draw_cloud_pixel(select_cp_lat[scanline, pixel,:],
select_cp_lon[scanline, pixel,:],
cloud_mask[scanline, pixel],
mapplot)
I get plots like this one:
Look on different examples from this page: http://www.uvm.edu/~jbagrow/dsv/heatmap_basemap.html
Main idea of a sample is plot a pcolormesh on a basemap:
import csv
import numpy as np
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
# load earthquake epicenters:
# http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/1.0_week.csv
lats, lons = [], []
with open('earthquake_data.csv') as f:
reader = csv.reader(f)
next(reader) # Ignore the header row.
for row in reader:
lat = float(row[1])
lon = float(row[2])
# filter lat,lons to (approximate) map view:
if -130 <= lon <= -100 and 25 <= lat <= 55:
lats.append( lat )
lons.append( lon )
# Use orthographic projection centered on California with corners
# defined by number of meters from center position:
m = Basemap(projection='ortho',lon_0=-119,lat_0=37,resolution='l',\
llcrnrx=-1000*1000,llcrnry=-1000*1000,
urcrnrx=+1150*1000,urcrnry=+1700*1000)
m.drawcoastlines()
m.drawcountries()
m.drawstates()
# ######################################################################
# bin the epicenters (adapted from
# http://stackoverflow.com/questions/11507575/basemap-and-density-plots)
# compute appropriate bins to chop up the data:
db = 1 # bin padding
lon_bins = np.linspace(min(lons)-db, max(lons)+db, 10+1) # 10 bins
lat_bins = np.linspace(min(lats)-db, max(lats)+db, 13+1) # 13 bins
density, _, _ = np.histogram2d(lats, lons, [lat_bins, lon_bins])
# Turn the lon/lat of the bins into 2 dimensional arrays ready
# for conversion into projected coordinates
lon_bins_2d, lat_bins_2d = np.meshgrid(lon_bins, lat_bins)
# convert the bin mesh to map coordinates:
xs, ys = m(lon_bins_2d, lat_bins_2d) # will be plotted using pcolormesh
# ######################################################################
# define custom colormap, white -> nicered, #E6072A = RGB(0.9,0.03,0.16)
cdict = {'red': ( (0.0, 1.0, 1.0),
(1.0, 0.9, 1.0) ),
'green':( (0.0, 1.0, 1.0),
(1.0, 0.03, 0.0) ),
'blue': ( (0.0, 1.0, 1.0),
(1.0, 0.16, 0.0) ) }
custom_map = LinearSegmentedColormap('custom_map', cdict)
plt.register_cmap(cmap=custom_map)
# add histogram squares and a corresponding colorbar to the map:
plt.pcolormesh(xs, ys, density, cmap="custom_map")
cbar = plt.colorbar(orientation='horizontal', shrink=0.625, aspect=20, fraction=0.2,pad=0.02)
cbar.set_label('Number of earthquakes',size=18)
#plt.clim([0,100])
# translucent blue scatter plot of epicenters above histogram:
x,y = m(lons, lats)
m.plot(x, y, 'o', markersize=5,zorder=6, markerfacecolor='#424FA4',markeredgecolor="none", alpha=0.33)
# http://matplotlib.org/basemap/api/basemap_api.html#mpl_toolkits.basemap.Basemap.drawmapscale
m.drawmapscale(-119-6, 37-7.2, -119-6, 37-7.2, 500, barstyle='fancy', yoffset=20000)
# make image bigger:
plt.gcf().set_size_inches(15,15)
plt.show()
I am trying to generate contour plots on a polar plot and did some quick scripting in matlab to get some results. Out of curiosity I also wanted to try out the same thing in python using the matplotlib but somehow I am seeing different sets of contour plots for the same input data. I am trying to figure out whats going on and if there is anything I could tweak in my python code to get similar results in both cases.
A screenshot of the matlab results is here:
In the matlab code I used the scatteredinterpolant function to get the interpolated data, I am assuming the differences are occurring due to the interpolation function used?
The input data is -
Angles = [-180, -90, 0 , 90, 180, -135, -45,45, 135, 180,-90, 0, 90, 180 ]
Radii = [0,0.33,0.33,0.33,0.33,0.5,0.5,0.5,0.5,0.5,0.6,0.6,0.6,0.6]
Values = [30.42,24.75, 32.23, 34.26, 26.31, 20.58, 23.38, 34.15,27.21, 22.609, 16.013, 22.75, 27.062, 18.27]
This was done using python 2.7, on spyder. I have tried both scipy.interpolate.griddata as well as matplotlib.mlab.griddata and the results are similar. I was unable to get the nn method working in mlab.griddata because it kept giving me masked data.
Apologies if I am missing anything relevant - please let me know if anyother info is required I will update my post.
Edit:
The linear scipt griddata image looks like:
And the cubic scipy image looks like
As for the code, here is the code - I pass the interpolation type string into the function where this code is present. So 'linear' and 'cubic' are the 2 inputs.
val = np.array(list(values[i]))
radius = np.array(list(gamma[i]))
ang = [math.radians(np.array(list(theta[i]))[x]) for x in xrange(0,len(theta[i]))]
radiiGrid = np.linspace(min(radius),max(radius),100)
anglesGrid = np.linspace(min(ang),max(ang),100)
radiiGrid, anglesGrid = np.meshgrid(radiiGrid, anglesGrid)
zgrid = griddata((ang,radius),val,(anglesGrid,radiiGrid), method=interpType)
The angle input is what comes out of np.array(list(theta[i]))[x] - this is because the angle information is stored in a list of tuples (this is because I am reading in and sorting data). I took a look at the code to make sure the data is correct and it seems to line up. gamma corresponds to radii and values are the values in the sample data I provided.
Hope this helps!
Polar plots in matplotlib can get tricky. When that happens, a quick solution is to convert radii and angle to x,y, plot in a normal projection. Then make a empty polar axis to superimpose on it:
from scipy.interpolate import griddata
Angles = [-180, -90, 0 , 90, 180, -135,
-45,45, 135, 180,-90, 0, 90, 180 ]
Radii = [0,0.33,0.33,0.33,0.33,0.5,0.5,
0.5,0.5,0.5,0.6,0.6,0.6,0.6]
Angles = np.array(Angles)/180.*np.pi
x = np.array(Radii)*np.sin(Angles)
y = np.array(Radii)*np.cos(Angles)
Values = [30.42,24.75, 32.23, 34.26, 26.31, 20.58,
23.38, 34.15,27.21, 22.609, 16.013, 22.75, 27.062, 18.27]
Xi = np.linspace(-1,1,100)
Yi = np.linspace(-1,1,100)
#make the axes
f = plt.figure()
left, bottom, width, height= [0,0, 1, 0.7]
ax = plt.axes([left, bottom, width, height])
pax = plt.axes([left, bottom, width, height],
projection='polar',
axisbg='none')
cax = plt.axes([0.8, 0, 0.05, 1])
ax.set_aspect(1)
ax.axis('Off')
# grid the data.
Vi = griddata((x, y), Values, (Xi[None,:], Yi[:,None]), method='cubic')
cf = ax.contour(Xi,Yi,Vi, 15, cmap=plt.cm.jet)
#make a custom colorbar, because the default is ugly
gradient = np.linspace(1, 0, 256)
gradient = np.vstack((gradient, gradient))
cax.xaxis.set_major_locator(plt.NullLocator())
cax.yaxis.tick_right()
cax.imshow(gradient.T, aspect='auto', cmap=plt.cm.jet)
cax.set_yticks(np.linspace(0,256,len(cf1.get_array())))
cax.set_yticklabels(map(str, cf.get_array())[::-1])
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à: