I am using cartopy to display a KDE overlayed on a world map. Initially, I was using the ccrs.PlateCarree projection with no issues, but the moment I tried to use another projection it seemed to explode the scale of the projection. For reference, I have included an example that you can test on your own machine below (just comment out the two projec lines to switch between projections)
from scipy.stats import gaussian_kde
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
projec = ccrs.PlateCarree()
#projec = ccrs.InterruptedGoodeHomolosine()
fig = plt.figure(figsize=(12, 12))
ax = fig.add_subplot(projection=projec)
np.random.seed(1)
discrete_points = np.random.randint(0,10,size=(2,400))
kde = gaussian_kde(discrete_points)
x, y = discrete_points
# https://www.oreilly.com/library/view/python-data-science/9781491912126/ch04.html
resolution = 1
x_step = int((max(x)-min(x))/resolution)
y_step = int((max(y)-min(y))/resolution)
xgrid = np.linspace(min(x), max(x), x_step+1)
ygrid = np.linspace(min(y), max(y), y_step+1)
Xgrid, Ygrid = np.meshgrid(xgrid, ygrid)
Z = kde.evaluate(np.vstack([Xgrid.ravel(), Ygrid.ravel()]))
Zgrid = Z.reshape(Xgrid.shape)
ext = [min(x)*5, max(x)*5, min(y)*5, max(y)*5]
earth = plt.cm.gist_earth_r
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'land', '50m',
edgecolor='black', facecolor="none"))
ax.imshow(Zgrid,
origin='lower', aspect='auto',
extent=ext,
alpha=0.8,
cmap=earth, transform=projec)
ax.axis('on')
ax.get_xaxis().set_visible(True)
ax.get_yaxis().set_visible(True)
ax.set_xlim(-30, 90)
ax.set_ylim(-60, 60)
plt.show()
You'll notice that when using the ccrs.PlateCarree() projection, the KDE is nicely placed over Africa, however when using the ccrs.InterruptedGoodeHomolosine() projection, you can't see the world map at all. This is because the world map is on an enormous scale. Below is an image of both examples:
Plate Carree projection:
Interrupted Goode Homolosine projection (standard zoom):
Interrupted Goode Homolosine projection (zoomed out):
If anyone could explain why this is occurring, and how to fix it so I can plot the same data on different projections, that would be greatly appreciated.
EDIT:
I would also like to specify that I tried adding transform=projec to line 37 in the example I included, namely:
ax.add_feature(cfeature.NaturalEarthFeature('physical', 'land', '50m',
edgecolor='black', facecolor="none", transform=projec))
However this did not help. In fact, it seemed upon adding this the world map no longer appeared at all.
EDIT:
In response to JohanC's answer, this is the plot I get when using that code:
And zoomed out:
Comments on your plots:
Plot1: (the reference map)
projection: PlateCarree projection
(Zgrid) image extents cover (approx) square area, about 40 degrees on each side
image's lower-left corner is at lat/long: (0,0)
Plot2
Q: Why the topo features are not shown on the map?
A: The plot covers very small area that does not include any of them.
projection: InterruptedGoodeHomolosine
the image data, Zgrid is declared to fit within grid (mapprojection) coordinates (unit: meters)
the map is plotted within a small extents of a few meters in both x and y, and aspect ratio is not equal.
Plot3
Q: Why the Zgrid image are not seen on the map?
A: The plot covers very large area that the image become too small to plot.
projection: InterruptedGoodeHomolosine projection
the (Zgrid) image extent is very small, not visible at this scale
the map is plotted within a large extents, and aspect ratio is not equal.
The remedies (for Plot2 and 3)
Zgrid need proper transformation from lat/long to the axes' projection coordinates
map's extents also need to be transformed and set appropriately
the aspect ratio must be set 'equal', to prevent unequal stretches in x and y
About 'gridlines' plots
useful for location reference
latitude/parallels: OK with InterruptedGoodeHomolosine in this case
longitude/meridians: is problematic (dont know how to fix !!)
Here is the modified code that runs and produces the required map.
# proposed code
from scipy.stats import gaussian_kde
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
fig = plt.figure(figsize=(7, 12))
ax = plt.axes(projection=ccrs.InterruptedGoodeHomolosine())
np.random.seed(1)
discrete_points = np.random.randint(0,10,size=(2,400))
kde = gaussian_kde(discrete_points)
x, y = discrete_points
# https://www.oreilly.com/library/view/python-data-science/9781491912126/ch04.html
resolution = 1
x_step = int((max(x)-min(x))/resolution)
y_step = int((max(y)-min(y))/resolution)
xgrid = np.linspace(min(x), max(x), x_step+1)
ygrid = np.linspace(min(y), max(y), y_step+1)
Xgrid, Ygrid = np.meshgrid(xgrid, ygrid)
Z = kde.evaluate(np.vstack([Xgrid.ravel(), Ygrid.ravel()]))
Zgrid = Z.reshape(Xgrid.shape)
ext = [min(x)*5, max(x)*5, min(y)*5, max(y)*5]
earth = plt.cm.gist_earth_r
ocean110 = cfeature.NaturalEarthFeature('physical', 'ocean', \
scale='110m', edgecolor='none', facecolor=cfeature.COLORS['water'])
ax.add_feature(ocean110, zorder=-5)
land110 = cfeature.NaturalEarthFeature('physical', 'land', '110m', \
edgecolor='black', facecolor="silver")
ax.add_feature(land110, zorder=5)
# extents used by both Zgrid and axes
ext = [min(x)*5, max(x)*5, min(y)*5, max(y)*5]
# plot the image's data array
# note the options: `extent` and `transform`
ax.imshow(Zgrid,
origin='lower', aspect='auto',
extent=ext, #set image's extent
alpha=0.75,
cmap=earth, transform=ccrs.PlateCarree(),
zorder=10)
# set the plot's extent with proper coord transformation
ax.set_extent(ext, ccrs.PlateCarree())
ax.coastlines()
#ax.add_feature(cfeature.BORDERS) #uncomment if you need
ax.gridlines(linestyle=':', linewidth=1, draw_labels=True, dms=True, zorder=30, color='k')
ax.set_aspect('equal') #make sure the aspect ratio is 1
plt.show()
The output map:
Related
I build a Jupyter Notebookthat imports geoelectric VES point data and subsequently interpolates the point data over a uniform 2D Mesh. I added the relevant parts of the code below (the previous part only imports all data into a dataframe).
x = df['Distance X [m]'].to_numpy()
y = df['AB/2 [m]'].to_numpy()
z = df['Resistivity [Ohmm]'].to_numpy()
#plot
cax = plt.scatter(x, y, c=z)
cbar = plt.colorbar(cax, fraction=0.03)
plt.title('Measured Resistivity')
#invert y axis
plt.gca().invert_yaxis()
plt.savefig('datapoints.png',dpi=100)
import numpy as np
from scipy.interpolate import griddata
from matplotlib.pyplot import figure
# target grid to interpolate to
xi = np.arange(0,6500,20)
yi = np.arange(0,500,20)
xi,yi = np.meshgrid(xi,yi)
# interpolate
zi = griddata((x,y),z,(xi,yi),method='cubic')
# plot
fig = plt.figure()
figure(figsize=(12, 6), dpi=80)
#ax = fig.add_subplot(111)
plt.contourf(xi,yi,zi)
plt.plot(x,y,'k.')
plt.xlabel('xi',fontsize=16)
plt.ylabel('yi',fontsize=16)
plt.gca().invert_yaxis()
plt.colorbar()
plt.savefig('interpolated.png',dpi=100)
#plt.close(fig)
So far, I managed to import my dataset, plot it and interpolate over the grid. However, especially at higher grid spacings, it becomes obvious that for some reason, the cubic and linear do not interpolation does not include the first row of the mesh (in my context the first meters of the subsurface) which is actually supposed to have the best data coverage. Only the nearest neighbor method works fine. In the added image e.g., the first 20m are not resolved.
Link to Interpolated Section
I am trying to plot some meteorological data onto a map and I would like to add an image of a plane using imshow. Plotting i) the trajectory, ii) some contour-data and iii) the image, works fine. But as soon as I add a contourf-plot (see below) the image dissapears!
Any ideas how to fix this?
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
import cartopy.crs as crs
import cartopy.feature as cfeature
def plot_test():
#DEFINE DATA
x,y = np.meshgrid(np.linspace(0,90,100),np.linspace(0,90,100))
z = x**3 + y**3
#BEGIN FIGURE (IN THIS CASE A MAP, IM PLOTTING METEOROLOGICAL DATA)
fig = plt.figure(figsize = (6,6))
ax1 = plt.axes(projection=crs.PlateCarree(central_longitude=0))
ax1.set_extent([0,90,0,90], crs=crs.PlateCarree())
ax1.coastlines(resolution='auto', color='k')
#EXAMPLE DATA PLOTTED AS CONTOURF
v_max = int(z.max())
v_min = int(z.min())
qcs = ax1.contourf(x, y, z, cmap = "Blues", vmin = v_min, vmax = v_max)
sm = plt.cm.ScalarMappable(cmap="Blues",norm=qcs.norm)
sm._A = []
cbar = plt.colorbar(sm, ax=ax1,orientation="vertical")
cbar.ax.set_ylabel("some contourf data", rotation=90, fontsize = 15)
#PLOT IMAGE OF A PLANE (THIS IS NOT SHOWING UP ON THE PLOT!)
x0 = 50
y0 = 40
img=plt.imread("plane2.png")
ax1.imshow(img,extent=[x0,x0 - 10, y0, y0-10], label = "plane")
plt.show()
without contourf (code from above with lines 14-20 commented out):
with contourf:
Thank you 1000 times #JohanC (see comments). I simply had to place the z-order:
ax1.imshow(img, ...., zorder=3)
which made the plane show up!
I am trying to understand why a hexbin plot in a north or south polar stereo projection shows squashed hexagons, even though the area of the grid is square and the projection is approximately equal area.
I've tried both north and south polar stereo projections using basemap.
import numpy as np
from numpy.random import uniform
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
fig = plt.figure(figsize=(12,10)) # width, height in inches
ax =fig.add_axes([-0.02,0.1,0.74,0.74])
m = Basemap(epsg='3413',lon_0=0.,resolution='l',width=6000000,height=6000000)
m.drawcoastlines()
m.drawmapscale(0.,90.,0.,90.,1000)
npts=2000
lats = uniform(60.,80.,size=npts)
lons = uniform(0.,360.,size=npts)
data = uniform(0.,4800.,size=npts)
x,y=m(lons, lats)
thiscmap=plt.cm.get_cmap('viridis')
p=m.hexbin(x,y,C=data,gridsize=[10,10],cmap=thiscmap)
plt.show()
I don't know why you get squashed hexagons. But you can adjust the hexagon shape by setting appropriate values of gridsize. Here I modify your code and get better plot.
import numpy as np
from numpy.random import uniform
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
fig = plt.figure(figsize=(12,10)) # width, height in inches
ax =fig.add_axes([-0.02, 0.1, 0.74, 0.74])
# North polar stereographic projection epsg='3413'; ***large areal distortion***
#m = Basemap(epsg='3413', lon_0=0., resolution='c', width=6000000, height=6000000)
# 'laea': Lambert Azimuthal Equal Area
# Thematic mapping with ground surface data should be plotted on 'equal-area' projection
m = Basemap(projection='laea', lon_0=0., lat_0=90, resolution='l', width=6000000, height=6000000)
m.drawcoastlines(linewidth=0.5)
m.drawmapscale(0.,90.,0.,90.,1000) # 1000 km?
npts = 2000
lats = uniform(60.,80.,size=npts) # not cover N pole
lons = uniform(0.,360.,size=npts) # around W to E
data = uniform(0.,4800.,size=npts)
x,y = m(lons, lats)
thiscmap = plt.cm.get_cmap('viridis')
# To get 'rounded' hexagons, gridsize should be specified appropriately
# need some trial and error to get them right
#p=m.hexbin(x, y, C=data, gridsize=[10,10], cmap=thiscmap) # original code
m.hexbin(x, y, C=data, gridsize=[16,11], cmap=thiscmap) # better
plt.colorbar() # useful on thematic map
plt.show()
The projection you use (epsg:3413) is stereographic projection which has large areal distortion. More appropriate projection for thematic mapping in this case is Lambert Azimuthal Equal Area.
The resulting plot:
I am trying to draw the maximum (theoretical) field of view of a satellite along its orbit. I am using Basemap, on which I want to plot different positions along the orbit (with scatter), and I would like to add the whole field of view using the tissot method (or equivalent).
The code below works fine until the latitude reaches about 75 degrees North, on a 300km altitude orbit. Beyond which the code outputs a ValueError:
"ValueError: undefined inverse geodesic (may be an antipodal point)"
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import math
earth_radius = 6371000. # m
fig = plt.figure(figsize=(8, 6), edgecolor='w')
m = Basemap(projection='cyl', resolution='l',
llcrnrlat=-90, urcrnrlat=90,
llcrnrlon=-180, urcrnrlon=180)
# draw the coastlines on the empty map
m.drawcoastlines(color='k')
# define the position of the satellite
position = [300000., 75., 0.] # altitude, latitude, longitude
# radius needed by the tissot method
radius = math.degrees(math.acos(earth_radius / (earth_radius + position[0])))
m.tissot(position[2], position[1], radius, 100, facecolor='tab:blue', alpha=0.3)
m.scatter(position[2], position[1], marker='*', c='tab:red')
plt.show()
To be noted that the code works fine at the south pole (latitude lower than -75). I know it's a known bug, is there a known workaround for this issue?
Thanks for your help!
What you found is some of Basemap's limitations. Let's switch to Cartopy for now. The working code will be different but not much.
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import math
earth_radius = 6371000.
position = [300000., 75., 0.] # altitude (m), lat, long
radius = math.degrees(math.acos(earth_radius / (earth_radius + position[0])))
print(radius) # in subtended degrees??
fig = plt.figure(figsize=(12,8))
img_extent = [-180, 180, -90, 90]
# here, cartopy's' `PlateCarree` is equivalent with Basemap's `cyl` you use
ax = fig.add_subplot(1, 1, 1, projection = ccrs.PlateCarree(), extent = img_extent)
# for demo purposes, ...
# let's take 1 subtended degree = 112 km on earth surface (*** you set the value as needed ***)
ax.tissot(rad_km=radius*112, lons=position[2], lats=position[1], n_samples=64, \
facecolor='red', edgecolor='black', linewidth=0.15, alpha = 0.3)
ax.coastlines(linewidth=0.15)
ax.gridlines(draw_labels=False, linewidth=1, color='blue', alpha=0.3, linestyle='--')
plt.show()
With the code above, the resulting plot is:
Now, if we use Orthographic projection, (replace relevant line of code with this)
ax = fig.add_subplot(1, 1, 1, projection = ccrs.Orthographic(central_longitude=0.0, central_latitude=60.0))
you get this plot:
I'm trying to create a contour plot on a North Polar Stereographic map projection using Cartopy. I used add_cyclic_point() to try and get around the problem of having a gap between longitude 0 and longitude 35X and followed an example from the documentation (always_circular_stereographic) to set up the map axes.
When I call plt.contour, I get the following plot. It looks like the contour plotter is getting confused at the transition from 355 to 0 longitude, and sends contour lines around the globe.
Here is my code:
import numpy as np
import cartopy.crs as ccrs
from cartopy.util import add_cyclic_point
import matplotlib.pyplot as plt
def define_map():
from matplotlib.path import Path
fig = plt.figure(figsize=(10,10))
ax = plt.axes(projection=ccrs.NorthPolarStereo())
ax.coastlines()
# From example: http://scitools.org.uk/cartopy/docs/latest/examples/always_circular_stereo.html
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 = Path(verts * radius + center)
ax.set_boundary(circle, transform=ax.transAxes)
return(fig, ax)
lats = np.arange(65,91,5)
lons = add_cyclic_point(np.arange(0,359,5))
data = add_cyclic_point(np.random.random((len(lats),len(lons)-1)))
fig, ax = define_map()
plt.contour(lons,lats,data,5,transform=ccrs.PlateCarree(), cmap=plt.cm.Blues)
plt.colorbar(fraction=0.05, shrink=0.9)
plt.show()
How do I do a Cartopy contour plot properly?
Also, why do the contours only show up with transform=ccrs.PlateCarree() and not with transform=ccrs.NorthPolarStereo()?
Apparently the add_cyclic_point function is just for the data; the contour routine treats 0 different than 360. So the simple fix is to set
lons = np.arange(0,360,5)