Aligning data (contourf) on Basemap - python

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:

Related

Removing Edge colors in Cartopy heatmap

I am attempting to plot a heatmap showing the density of lightning using python's cartopy and matplotlib libraries.
I have roughly followed the code here Cartopy Heatmap over OpenStreetMap Background . However, my plot shown below contains solid lines around each transparent bin, which is my problem. The other plot is the same code with random numbers. An ideal solution would be to not display the lines at all, or for the lines to match the bin's face color with the correct transparency. I've done a fair amount of trial and error to remove them in addition to reading some matplotlib documentation. According to the 2d-histogram docs , I should be plotting a QuadMesh object. You should be able to set the linewidth to 0, or have the edgecolor set to none in the QuadMesh. In my code below, I tried doing that yet the lines still persist. I've also tried the pcolormesh as well with the same result.
Here is my code.
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.io.shapereader as shpreader
import cartopy.feature as cfeature
import cartopy.io.img_tiles as cimgt
import numpy as np
import random
#xsize and ysize are integers and lons, lats are 1d numpy arrays of longitude and latitude.
def testDensity(xsize, ysize, lons, lats):
#Some code below follows example
#https://stackoverflow.com/questions/50611018/cartopy-heatmap-over-openstreetmap-background
request = cimgt.OSM()
fig, ax = plt.subplots(figsize=(xsize,ysize),subplot_kw=dict(projection=request.crs), dpi=200)
extent = [-126,-118,41,44]
ax.set_extent(extent)
ax.add_image(request,8)
xynps = ax.projection.transform_points(ccrs.Geodetic(), lons, lats)#
print(xynps, type(xynps))
#Create 2-d histogram
histogram = ax.hist2d( xynps[:,0] , xynps[:,1] ,cmap='jet', bins=100, zorder=1,alpha=0.5,edgecolors="none",linewidth=0 )
print(histogram[3], dir(histogram[3]) )
histogram[3].set_linewidth(0.0)
histogram[3].set_edgecolor("none")
#histogram:(frequency, xedges, yedges, image QuadMesh)
#ax.pcolormesh(histogram[1], histogram[2], histogram[0], cmap = 'jet', alpha=0.5,edgecolors="none")
cbar = plt.colorbar(mappable=histogram[3], ax=ax , shrink=0.5, format='%.1f1' )
cbar.solids.set_rasterized("True")#Removes lines from colorbar
cbar.solids.set_edgecolor("face")
plt.savefig("densityTest.png", bbox_inches="tight")
#Generate random dataset
for i in range(0,800):
lon = random.randrange(41,44) + random.random()
lat = random.randrange(-126,-118) + random.random()
lons.append(lon)
lats.append(lat)
lons = np.array(lons)
lats = np.array(lats)
testDensity(9,34, lons, lats)
I can't reproduce the problematic results you showed us because of errors in your code. But once I correct the errors in the code and run. I get a good result as shown below.
The modified code:
def testDensity(xsize, ysize, lons, lats):
# Some code below follows example
# https://stackoverflow.com/questions/50611018/cartopy-heatmap-over-openstreetmap-background (That's my another answer)
request = cimgt.OSM()
fig, ax = plt.subplots(figsize=(xsize,ysize),subplot_kw=dict(projection=request.crs), dpi=200)
extent = [-126, -118, 41, 44]
ax.set_extent(extent)
ax.add_image(request, 8)
xynps = ax.projection.transform_points(ccrs.Geodetic(), lons, lats)
#Create 2-d histogram
# histogram = ax.hist2d(xynps[:,0],xynps[:,1],cmap='jet',bins=100,zorder=1,alpha=0.5,edgecolors="none",linewidth=0)
#This produces the same result, but shorter.
histogram = ax.hist2d( xynps[:,0], xynps[:,1], cmap='jet', bins=100, zorder=1, alpha=0.5)
# (Why use these code?)
#histogram[3].set_linewidth(0.0)
#histogram[3].set_edgecolor("none")
#ax.pcolormesh(histogram[1], histogram[2], histogram[0], cmap = 'jet', alpha=0.5,edgecolors="none")
# cbar = plt.colorbar(mappable=histogram[3], ax=ax , shrink=0.5, format='%.1f' )
# cbar.solids.set_rasterized("True")#Removes lines from colorbar
# cbar.solids.set_edgecolor("face")
# ... when this produces good result.
cbar = plt.colorbar(histogram[3], ax=ax, pad=0.03, aspect=28, shrink=0.26, format='%.1f') # h[3]: image
plt.savefig("densityTest.png", bbox_inches="tight")
plt.show()
#Generate random dataset
lons = []
lats = []
for i in range(0,800):
lat = random.randrange(41,44) + random.random()
lon = random.randrange(-126,-118) + random.random()
lons.append(lon)
lats.append(lat)
lons = np.array(lons)
lats = np.array(lats)
#testDensity(9,34, lons, lats)
testDensity(10,16, lons, lats)
The output plot:

Axis labels for LambertConformal in cartopy at wrong location

I want to plot some data in a LambertConformal projection and add labels to the axes. See the example code below. However, now the x-labels show up twice, and both times in the middle of the plot, instead of at its bottom. When instead I set gl.xlabels_bottom = False and gl.xlabels_top = True, no x-labels are plotted at all. With the y-labels, I do not get this problem; they are just nicely plotted either along the left or right boundary of the plot.
How can I get the x-labels at the right location (at the bottom of the figure)?
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
bounds_lon = [-45,-25]
bounds_lat = [55,65]
lon = np.arange(bounds_lon[0],bounds_lon[1]+0.1,0.1)
lat = np.arange(bounds_lat[0],bounds_lat[1]+0.1,0.1)
Lon, Lat = np.meshgrid(lon,lat)
data = np.ones(np.shape(Lon))
data_crs = ccrs.PlateCarree()
projection = ccrs.LambertConformal(central_longitude=np.mean(bounds_lon),central_latitude=np.mean(bounds_lat),cutoff=bounds_lat[0])
plt.figure(figsize=(4,4))
ax = plt.axes(projection=projection)
ax.coastlines()
ax.contourf(Lon, Lat, data, transform=data_crs)
gl = ax.gridlines(crs=ccrs.PlateCarree(), linewidth=2, color='gray', alpha=0.5, linestyle='--')
gl.xlabels_bottom = True
Manual repositioning of tick-labels are needed. To do that successfully, requires some other adjustments of the plot settings. Here is the code you can try.
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
bounds_lon = [-45,-25]
bounds_lat = [55,65]
# make-up data to plot on the map
inc = 0.5
lon = np.arange(bounds_lon[0],bounds_lon[1]+inc, inc)
lat = np.arange(bounds_lat[0],bounds_lat[1]+inc, inc)
Lon, Lat = np.meshgrid(lon,lat)
#data = np.ones(np.shape(Lon)) # original `boring` data
data = np.sin(Lon)+np.cos(Lat) # better data to use instead
data_crs = ccrs.PlateCarree()
projection = ccrs.LambertConformal(central_longitude=np.mean(bounds_lon), \
central_latitude=np.mean(bounds_lat), \
#cutoff=bounds_lat[0]
)
# Note: `cutoff` causes horizontal cut at lower edge
# init plot figure
plt.figure(figsize=(15,9))
ax = plt.axes(projection=projection)
ax.coastlines(lw=0.2)
ax.contourf(Lon, Lat, data, transform=data_crs, alpha=0.5)
# set gridlines specs
gl = ax.gridlines(crs=ccrs.PlateCarree(), linewidth=2, color='gray', alpha=0.5, linestyle='--')
gl.top_labels=True
gl.bottom_labels=True
gl.left_labels=True
gl.right_labels=True
plt.draw() #enable access to lables' positions
xs_ys = ax.get_extent() #(x0,x1, y0,y1)
#dx = xs_ys[1]-xs_ys[0]
dy = xs_ys[3]-xs_ys[2]
# The extent of `ax` must be adjusted
# Extents' below and above are increased
new_ext = [xs_ys[0], xs_ys[1], xs_ys[2]-dy/15., xs_ys[3]+dy/12.]
ax.set_extent(new_ext, crs=projection)
# find locations of the labels and reposition them as needed
xs, ys = [], []
for ix,ea in enumerate(gl.label_artists):
xy = ea[2].get_position()
xs.append(xy[0])
ys.append(xy[1])
# Targeted labels to manipulate has "W" in them
if "W" in ea[2].get_text():
x_y = ea[2].get_position()
# to check which are above/below mid latitude of the plot
# use 60 (valid only this special case)
if x_y[1]<60:
# labels at lower latitudes
curpos = ea[2].get_position()
newpos = (curpos[0], 54.7) # <- from inspection: 54.7
ea[2].set_position(newpos)
else:
curpos = ea[2].get_position()
newpos = (curpos[0], 65.3) # <- from inspection: 65.3
ea[2].set_position(newpos)
plt.show()
Edit1
If you want to move all the lat/long labels to the outside edges, try this code. It is much more concise than the above.
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
bounds_lon = [-45,-25]
bounds_lat = [55,65]
inc = 0.5
lon = np.arange(bounds_lon[0],bounds_lon[1]+inc, inc)
lat = np.arange(bounds_lat[0],bounds_lat[1]+inc, inc)
Lon, Lat = np.meshgrid(lon,lat)
#data = np.ones(np.shape(Lon)) # boring data
data = np.sin(Lon)+np.cos(Lat) # more interesting
data_crs = ccrs.PlateCarree()
projection = ccrs.LambertConformal(central_longitude=np.mean(bounds_lon), \
central_latitude=np.mean(bounds_lat), \
cutoff=bounds_lat[0]
)
# init plot
plt.figure(figsize=(15,9))
ax = plt.axes(projection=projection)
ax.coastlines(lw=0.2)
ax.contourf(Lon, Lat, data, transform=data_crs, alpha=0.3)
gl = ax.gridlines(draw_labels=True, x_inline=False, y_inline=False,
color='k', linestyle='dashed', linewidth=0.5)
gl.top_labels=True
gl.bottom_labels=True
gl.left_labels=True
gl.right_labels=True
plt.show()
If you want to get bottom edge as a straight line, you can achieve that by dropping the option cutoff=bounds_lat[0] from this line of code:-
projection = ccrs.LambertConformal(central_longitude=np.mean(bounds_lon), \
central_latitude=np.mean(bounds_lat), \
cutoff=bounds_lat[0]
)
so that it becomes
projection = ccrs.LambertConformal(central_longitude=np.mean(bounds_lon),
central_latitude=np.mean(bounds_lat))
and you will get the plot like this:-

Griddata and Contourf produce artifacts with increasing steps/levels

I am using SciPy Griddata to interpolate data in its Cartesian form and then plot these data using contourf with a polar projection. When the Cartesian interpolated data is plotted with contourf there are no artifacts. However, when the projection is polar, artifacts develop with increasing "levels".
The artifacts are polygons or rays that form near regions of steep gradients. The code below plots the brightness of the sky with the moon. With graphlevels of "12" there isn't an issue. Artifacts develop with graphlevel of "25." My desired level is 80 or more - which shows terrible artifacts. The below is example real data from one night. These artifacts always occur. See images with Levels = 12 and Levels = 80
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import griddata
gridsize =150
graphlevels =12
plt.figure(figsize=(12,10))
ax = plt.subplot(111,projection='polar')
x = [72.90,68.00,59.14,44.38,29.63,63.94,59.68,51.92,38.98,26.03,47.34,44.20,38.46,28.89,19.31,23.40,20.40,15.34,10.28,-0.18,-0.14,-0.09,-0.04,0.02,-25.39,-23.66,-20.57,-15.40,-10.23,-47.56,-44.34,-38.54,-28.89,-19.22,-64.01,-59.68,-51.89,-38.90,-25.90,-72.77,-67.84,-58.98,-44.21,-29.44,-72.75,-67.83,-58.96,-44.18,-29.41,-59.63,-51.82,-38.83,-25.84,-47.42,-44.20,-38.40,-28.76,-19.12,-23.40,-20.32,-15.19,-10.08,0.27,0.25,0.23,0.20,23.92,20.80,15.63,10.46,47.93,44.67,38.86,29.17,19.48,64.40,60.03,52.20,39.18,26.15,73.08,68.12,59.26,44.47,29.68,-4.81]
y = [12.93,12.01,10.38,7.67,4.99,37.03,34.49,29.93,22.33,14.77,56.60,52.75,45.82,34.26,22.72,64.60,56.14,42.02,27.90,73.66,68.67,59.68,44.68,29.68,69.12,64.45,56.00,41.92,27.84,56.26,52.45,45.56,34.08,22.61,36.59,34.11,29.61,22.11,14.62,12.48,11.62,10.04,7.43,4.83,-13.33,-12.31,-10.78,-8.21,-5.58,-34.84,-30.36,-22.87,-15.36,-57.04,-53.20,-46.31,-34.83,-23.34,-65.20,-56.72,-42.62,-28.53,-69.33,-60.31,-45.31,-30.31,-65.09,-56.63,-42.55,-28.47,-56.81,-52.99,-46.13,-34.69,-23.23,-36.99,-34.53,-30.08,-22.66,-15.22,-12.73,-11.93,-10.44,-7.94,-5.40,-1.22,]
skybrightness = [19.26,19.31,19.21,19.65,19.40,19.26,19.23,19.43,19.57,19.52,19.19,19.31,19.33,19.68,19.50,19.29,19.45,19.50,19.23,18.98,19.28,19.46,19.54,19.22,19.03,19.18,19.35,19.37,19.08,18.99,18.98,19.26,19.36,19.08,18.79,18.85,19.13,19.17,19.05,18.51,18.64,18.88,18.92,18.93,18.12,18.34,18.72,18.82,18.74,18.22,18.46,18.76,18.26,18.13,18.24,18.46,18.58,17.30,18.38,18.08,18.24,17.68,18.34,18.46,18.65,18.23,18.70,18.52,18.79,18.83,18.18,18.51,19.01,19.08,19.08,18.99,19.02,19.07,19.20,19.27,19.06,19.01,19.28,19.46,19.30,18.94]
xgrid = np.linspace(min(x), max(x),gridsize)
ygrid = np.linspace(min(y), max(y),gridsize)
xgrid, ygrid = np.meshgrid(xgrid, ygrid, indexing='ij')
nsb_grid = griddata((x,y),skybrightness,(xgrid, ygrid), method='linear')
r = np.sqrt(xgrid**2 + ygrid**2)
theta = np.arctan2(ygrid, xgrid)
plt.rc('ytick', labelsize=16)
ax.set_facecolor('#eeddcc')
colors = plt.cm.get_cmap('RdYlBu')
levels,steps = np.linspace(min(skybrightness), max(skybrightness)+0.3,graphlevels, retstep=True)
ticks = np.linspace(min(skybrightness), max(skybrightness)+0.3,12)
cax = ax.contourf(theta, r, nsb_grid, levels=levels, cmap=colors)
cbar = plt.colorbar(cax, fraction=0.046, pad=0.04, ticks=ticks)
cbar.set_label(r'mag/arcsec$^2$')
ax.set_theta_zero_location('N')
ax.set_theta_direction(-1)
ax.set_rmax(75)
ax.set_yticks(range(10, 80, 20))
ax.set_xticklabels([r'N', r'NE', r'E', r'SE', r'S', r'SW', r'W', r'NW'])
ax.grid(alpha=0.3)
plt.savefig('StackOverflowHELP.png')
I am going to leave my question and this answer on StackOverflow... because I did get an answer from the developers of Matploblib. The problem is Contourf . In its attempt to project data in polar dimensions there are overlaps and extensions of polygons at the cyclic boundaries that cause problems. The only way to avoid this is to add points at the boundary. To quote the developer:
The workaround is a lot of effort and has to be tuned to each particular problem, so is a very long way from being ideal. We (Matplotlib) should do better in these situations. Inserting extra points into the triangulation isn't the right approach, we should instead correct the lines/polygons that traverse the discontinuity to provide a general solution.
See https://github.com/matplotlib/matplotlib/issues/20060 for the full discussion
The answer I settled on is to interpolate and render the result in Cartesian space. Then I format an empty polar plot with axes and labels to overlay on the top... and get on with my life!
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import griddata
gridsize =150
graphlevels = 200
fig = plt.figure(figsize=(12,10))
ax = fig.add_subplot(111, aspect='equal')
pax = fig.add_subplot(111,projection='polar')
pax.set_facecolor('none')
ax.set_axis_off()
ax.set_xlim([-75,75])
ax.set_ylim([-75,75])
x = [72.90,68.00,59.14,44.38,29.63,63.94,59.68,51.92,38.98,26.03,47.34,44.20,38.46,28.89,19.31,23.40,20.40,15.34,10.28,-0.18,-0.14,-0.09,-0.04,0.02,-25.39,-23.66,-20.57,-15.40,-10.23,-47.56,-44.34,-38.54,-28.89,-19.22,-64.01,-59.68,-51.89,-38.90,-25.90,-72.77,-67.84,-58.98,-44.21,-29.44,-72.75,-67.83,-58.96,-44.18,-29.41,-59.63,-51.82,-38.83,-25.84,-47.42,-44.20,-38.40,-28.76,-19.12,-23.40,-20.32,-15.19,-10.08,0.27,0.25,0.23,0.20,23.92,20.80,15.63,10.46,47.93,44.67,38.86,29.17,19.48,64.40,60.03,52.20,39.18,26.15,73.08,68.12,59.26,44.47,29.68,-4.81]
y = [12.93,12.01,10.38,7.67,4.99,37.03,34.49,29.93,22.33,14.77,56.60,52.75,45.82,34.26,22.72,64.60,56.14,42.02,27.90,73.66,68.67,59.68,44.68,29.68,69.12,64.45,56.00,41.92,27.84,56.26,52.45,45.56,34.08,22.61,36.59,34.11,29.61,22.11,14.62,12.48,11.62,10.04,7.43,4.83,-13.33,-12.31,-10.78,-8.21,-5.58,-34.84,-30.36,-22.87,-15.36,-57.04,-53.20,-46.31,-34.83,-23.34,-65.20,-56.72,-42.62,-28.53,-69.33,-60.31,-45.31,-30.31,-65.09,-56.63,-42.55,-28.47,-56.81,-52.99,-46.13,-34.69,-23.23,-36.99,-34.53,-30.08,-22.66,-15.22,-12.73,-11.93,-10.44,-7.94,-5.40,-1.22,]
skybrightness = [19.26,19.31,19.21,19.65,19.40,19.26,19.23,19.43,19.57,19.52,19.19,19.31,19.33,19.68,19.50,19.29,19.45,19.50,19.23,18.98,19.28,19.46,19.54,19.22,19.03,19.18,19.35,19.37,19.08,18.99,18.98,19.26,19.36,19.08,18.79,18.85,19.13,19.17,19.05,18.51,18.64,18.88,18.92,18.93,18.12,18.34,18.72,18.82,18.74,18.22,18.46,18.76,18.26,18.13,18.24,18.46,18.58,17.30,18.38,18.08,18.24,17.68,18.34,18.46,18.65,18.23,18.70,18.52,18.79,18.83,18.18,18.51,19.01,19.08,19.08,18.99,19.02,19.07,19.20,19.27,19.06,19.01,19.28,19.46,19.30,18.94]
xgrid = np.linspace(min(x), max(x),gridsize)
ygrid = np.linspace(min(y), max(y),gridsize)
xgrid, ygrid = np.meshgrid(xgrid, ygrid, indexing='ij')
nsb_grid = griddata((x,y),skybrightness,(xgrid, ygrid), method='linear')
plt.rc('ytick', labelsize=16) #colorbar font
colors = plt.cm.get_cmap('RdYlBu')
levels,steps = np.linspace(min(skybrightness), max(skybrightness)+0.3,graphlevels, retstep=True)
ticks = np.linspace(min(skybrightness), max(skybrightness)+0.3,12)
cax = ax.contourf(xgrid, ygrid, nsb_grid, levels=levels, cmap=colors)
cbar = plt.colorbar(cax, fraction=0.046, pad=0.04, ticks=ticks)
cbar.set_label(r'mag/arcsec$^2$')
pax.set_theta_zero_location('N')
pax.set_theta_direction(-1)
pax.set_rmax(75)
pax.set_yticks(range(10, 80, 20))
pax.set_xticklabels([r'N', r'NE', r'E', r'SE', r'S', r'SW', r'W', r'NW'])
pax.grid(alpha=0.3)

Overwrite points from pcolormesh if they aren't contained in a polygon

I'm trying to plot a map whereby a spatial pattern is plotted over the land using pcolormesh or contourf. A shapefile/polygon that describes the border of the UK is then overlayed onto this plot. My problem is how to directly access the points that fall outside the polygon to set them as 0 or directly colour them a single colour e.g. white. See the following minimal working example
import geopandas as gpd
import matplotlib.pyplot as plt
import numpy as np
# Load polygon
world = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
UK = world[world.iso_a3 == "GBR"]
UK.boundary.plot()
# Simulate a spatial pattern
xlims = (-8, 3)
ylims = (49, 60)
resolution = 0.05
y, x = np.mgrid[slice(ylims[0], ylims[1] + resolution, resolution),
slice(xlims[0], xlims[1] + resolution, resolution)]
z = 0.5*x+np.sin(x)**2+ np.cos(y)
# Plot
fig, ax=plt.subplots(figsize=(6, 10))
im = ax.pcolormesh(x, y, z, cmap='viridis')
fig.colorbar(im, ax=ax)
UK.boundary.plot(ax=ax, color='black')
I have tried excluding any points in the original dataset and then generating the pcolormesh. However, pcolormesh interpolates between points. This results in a series of points being generated from Northern Ireland down to Cornwall. Just to be clear, what I would like is to fill outside the polygon. Thanks for any help.
Rather than what you request (modifying the values of z), I plot another layer of pcolormesh() on top to get the desired effect. In this process, a new_z array is created with individual values obtained from point-within_polygon operation. A custom colormap, new_binary is created to use with new_z to plot this layer and get the final plot.
import geopandas as gpd
import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl
from shapely.geometry import Point
# Load polygon
world = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
UK = world[world.iso_a3 == "GBR"]
# plot 1
#UK.boundary.plot()
# Simulate a spatial pattern
xlims = (-8, 3)
ylims = (49, 60)
resolution = 0.05 # 0.05
# slice()
y, x = np.mgrid[slice(ylims[0], ylims[1] + resolution, resolution),
slice(xlims[0], xlims[1] + resolution, resolution)]
z = 0.5*x+np.sin(x)**2+ np.cos(y)
# Target geometry, for point inside/outside polygon operation
ukgeom = UK['geometry'].values[0]
def prep_z(Xs,Ys,Zs):
# Xs,Ys: result of np.meshgrid(lon, lat)
# Zs: function of(Xs,Ys) to be manipulated; here hard-coded as `new_z`
for ro,(arow,acol) in enumerate(zip(Xs,Ys)):
# need 2 level loop to handle each grid point
for co,xiyi in enumerate(zip(arow,acol)):
pnt1 = Point(xiyi)
if pnt1.within(ukgeom):
new_z[ro][co]=1 #0:white, 1:black with cm='binary'
else:
new_z[ro][co]=0
pass
pass
# Usage of the function above: prep_z(x,y,z)
# Result: new_z is modified.
# New z for locations in/outside-polygon operation
new_z = np.zeros(z.shape)
prep_z(x,y,z)
# create custom colormap to use later
new_binary = mpl.colors.ListedColormap(np.array([[1., 1., 1., 1.],
[0., 0., 0., 0.]]))
# 0:white, 1:transparent with cm='new_binary'
# do not use alpha option to get the intended result
# Plot 2
fig, ax = plt.subplots(figsize=(6, 10))
im = ax.pcolormesh(x, y, z, cmap='viridis', zorder=1)
im2 = ax.pcolormesh(x, y, new_z, cmap=new_binary, zorder=2) # do not use alpha to get transparent mask
UK.boundary.plot(ax=ax, color='black', zorder=10)
fig.colorbar(im, ax=ax, shrink=0.5)
plt.show()

Problems with pcolormesh in Cartopy

I'm trying to adapt the Cartopy example plot for circular South Polar Stereographic plots to the North Pole and add data to it. I have a couple questions.
First, in the example code, the land feature is added before the ocean feature. When I did that, I got a map with only ocean. I reversed the order of the call in the code below and get a map with land and ocean. Why did the other order work with the South Polar example?
Second, and more importantly, I can't figure out why my pcolormesh call isn't having any effect.
I'm using Python 2.7.7, matplotlib 1.5.1, and Cartopy 0.15.1.
import matplotlib.path as mpath
import matplotlib.pyplot as plt
import numpy as np
import cartopy.crs as ccrs
import cartopy.feature
lats = np.linspace(60,90,30)
lons = np.linspace(0,360,200)
X,Y = np.meshgrid(lons,lats)
Z = np.random.normal(size = X.shape)
def main():
fig = plt.figure(figsize=[10, 5])
ax = plt.subplot(1, 1, 1, projection=ccrs.NorthPolarStereo())
fig.subplots_adjust(bottom=0.05, top=0.95,
left=0.04, right=0.95, wspace=0.02)
# Limit the map to -60 degrees latitude and below.
ax.set_extent([-180, 180, 60, 60], ccrs.PlateCarree())
ax.gridlines()
ax.add_feature(cartopy.feature.OCEAN)
ax.add_feature(cartopy.feature.LAND)
# Compute a circle in axes coordinates, which we can use as a boundary
# for the map. We can pan/zoom as much as we like - the boundary will be
# permanently circular.
theta = np.linspace(0, 2*np.pi, 100)
center, radius = [0.5, 0.5], 0.5
verts = np.vstack([np.sin(theta), np.cos(theta)]).T
circle = mpath.Path(verts * radius + center)
ax.set_boundary(circle, transform=ax.transAxes)
ax.pcolormesh(X,Y,Z,transform=ccrs.PlateCarree())
plt.show()
if __name__ == '__main__':
main()
Your code leaves cartopy to dictate the order of feature plots on the map, as a result, some features can be hidden with no clues. It is possible to specify the order of plots explicitly.
The order of features plot is controlled by zorder, which can be specified with zorder=integer in most plotting statements. Here is a modified code that produces a better plot.
# your data
lats = np.linspace(60, 90, 30)
lons = np.linspace(0, 360, 160)
X,Y = np.meshgrid(lons, lats)
Z = np.random.normal(size = X.shape)
# new data for pcolormesh plot
latz = np.linspace(75, 90, 15)
lonz = np.linspace(0, 360, 160)
X1,Y1 = np.meshgrid(lonz, latz)
Z1 = np.random.normal(size = X1.shape)
def main():
fig = plt.figure(figsize=[10, 10])
ax = plt.subplot(1, 1, 1, projection=ccrs.NorthPolarStereo())
fig.subplots_adjust(bottom=0.05, top=0.95,
left=0.04, right=0.95, wspace=0.02)
# Limit the map to -60 degrees latitude and below.
ax.set_extent([-180, 180, 60, 60], ccrs.PlateCarree())
ax.gridlines()
# zorder can be used to arrange what is on top
ax.add_feature(cartopy.feature.LAND, zorder=4) # land is specified to plot above ...
ax.add_feature(cartopy.feature.OCEAN, zorder=1) # ... the ocean
# Compute a circle in axes coordinates, which we can use as a boundary
# for the map. We can pan/zoom as much as we like - the boundary will be
# permanently circular.
theta = np.linspace(0, 2*np.pi, 100)
center, radius = [0.5, 0.5], 0.5
verts = np.vstack([np.sin(theta), np.cos(theta)]).T
circle = mpath.Path(verts * radius + center)
ax.set_boundary(circle, transform=ax.transAxes)
# pcolormesh is specified to plot on top of the ocean but below land
ax.pcolormesh(X1, Y1, Z1, transform=ccrs.PlateCarree(), zorder=3)
plt.show()
if __name__ == '__main__':
main()

Categories

Resources