fill oceans for basemap in 3D - matplotlib-basemap

I would like to fill oceans for my basemap in 3D but
ax.add_collection3d(m.drawmapboundary(fill_color='aqua'))
doesn't seem to work because the basemap drawmapboundary method doesn’t return an object supported by add_collection3d but a matplotlib.collections.PatchCollection object. Is there any workaround similar to the one done for land polygons here? Thank you!

Drawing a rectangle (polygon) below the map is one solution. Here is the working code that you may try.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.basemap import Basemap
from matplotlib.collections import PolyCollection
map = Basemap()
fig = plt.figure()
ax = Axes3D(fig)
ax.azim = 270
ax.elev = 50
ax.dist = 8
ax.add_collection3d(map.drawcoastlines(linewidth=0.20))
ax.add_collection3d(map.drawcountries(linewidth=0.15))
polys = []
for polygon in map.landpolygons:
polys.append(polygon.get_coords())
# This fills polygons with colors
lc = PolyCollection(polys, edgecolor='black', linewidth=0.3, \
facecolor='#BBAAAA', alpha=1.0, closed=False)
lcs = ax.add_collection3d(lc, zs=0) # set zero zs
# Create underlying blue color rectangle
# It's `zs` value is -0.003, so it is plotted below land polygons
bpgon = np.array([[-180., -90],
[-180, 90],
[180, 90],
[180, -90]])
polys2 = []
polys2.append(bpgon)
lc2 = PolyCollection(polys2, edgecolor='none', linewidth=0.1, \
facecolor='#445599', alpha=1.0, closed=False)
lcs2 = ax.add_collection3d(lc2, zs=-0.003) # set negative zs value
plt.show()
The resulting plot:

Related

Migrating from basemap to cartopy - coordinates offset?

I was happy with basemap showing some points of interest I have visited, but since basemap is dead, I am moving over to cartopy.
So far so good, I have transferred all the map features (country borders, etc.) without major problems, but I have noticed my points of interest are shifted a bit to North-East in cartopy.
Minimal example from the old basemap (showing two points of interest on the Germany-Czech border):
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
map = Basemap(projection='merc', epsg='3395',
llcrnrlon = 11.5, urcrnrlon = 12.5,
llcrnrlat = 50, urcrnrlat = 50.7,
resolution = 'f')
map.fillcontinents(color='Bisque',lake_color='LightSkyBlue')
map.drawcountries(linewidth=0.2)
lats = [50.3182289, 50.2523744]
lons = [12.1010486, 12.0906336]
x, y = map(lons, lats)
map.plot(x, y, marker = '.', markersize=1, mfc = 'red', mew = 1, mec = 'red', ls = 'None')
plt.savefig('example-basemap.png', dpi=200, bbox_inches='tight', pad_inches=0)
And the same example from cartopy - both points are slightly shifted to North-East as can be verified also e.g. via google maps:
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt
ax = plt.axes(projection=ccrs.Mercator())
ax.set_extent([11.5, 12.5, 50, 50.7])
ax.add_feature(cfeature.LAND.with_scale('10m'), color='Bisque')
ax.add_feature(cfeature.LAKES.with_scale('10m'), alpha=0.5)
ax.add_feature(cfeature.BORDERS.with_scale('10m'), linewidth=0.2)
lats = [50.3182289, 50.2523744]
lons = [12.1010486, 12.0906336]
ax.plot(lons, lats, transform=ccrs.Geodetic(),
marker = '.', markersize=1, mfc = 'red', mew = 1, mec = 'red', ls = 'None')
plt.savefig('cartopy.png', dpi=200, bbox_inches='tight', pad_inches=0)
Any idea how to get cartopy plot the points on expected coordinates? Tried cartopy 0.18 and 0.20 with the same result.

annulus with scipy Delaunay

i try to draw a 3d solid that represents an annulus. I have used the scipy module and Delaunay to do the calculation.
Unfortunately the plot shows a 3d cylinder and not an annulus. Has somebody an idea how to modify the code? Is scipy the right module? Can i use Delaunay with retangular shapes?
thanks in advance!
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.spatial import Delaunay
points = 50
theta = np.linspace(0,2*np.pi,points)
radius_middle = 7.5
radius_inner = 7
radius_outer = 8
x_m_cartesian = radius_middle * np.cos(theta)
y_m_cartesian = radius_middle * np.sin(theta)
z_m_cartesian = np.zeros(points)
M_m = np.c_[x_m_cartesian,y_m_cartesian,z_m_cartesian]
x_i_cartesian = radius_inner * np.cos(theta)
y_i_cartesian = radius_inner * np.sin(theta)
z_i_cartesian = np.zeros(points)
M_i = np.c_[x_i_cartesian,y_i_cartesian,z_i_cartesian]
x1_m_cartesian = radius_middle * np.cos(theta)
y1_m_cartesian = radius_middle * np.sin(theta)
z1_m_cartesian = np.ones(points)
M1_m = np.c_[x1_m_cartesian,y1_m_cartesian,z1_m_cartesian]
x2_i_cartesian = radius_inner * np.cos(theta)
y2_i_cartesian = radius_inner * np.sin(theta)
z2_i_cartesian = np.ones(points)
M2_i = np.c_[x2_i_cartesian,y2_i_cartesian,z2_i_cartesian]
M = np.vstack((M_m,M_i,M1_m,M2_i))
# Delaunay
CH = Delaunay(M).convex_hull
x,y,z = M[:,0],M[:,1],M[:,2]
fig = plt.figure(figsize=(12,8))
ax = fig.add_subplot(111,projection='3d')
#ax.scatter(x[:,0],y[:,1],z[:,2])
ax.plot_trisurf(x,y,z,triangles=CH, shade=False, color='lightblue',lw=1, edgecolor='k')
plt.show()
As noted in the comments the convex hull is a convex shape and therefore cannot represent an annulus. However, the concept of the concave hull (also known as the alpha-shape) is probably appropriate for your needs. Basically, the alpha-shape removes from the Delaunay triangulation the triangles (tetrahedra in your 3D case) that have a circumradius greater than some value (defined by the alpha parameter).
This answer provides an implementation of the alpha-shape surface (i.e., the outer boundary) for 3D points. Using the alpha_shape_3D function from that answer, with an alpha value of 3, resulted in the figure below.
The following two lines in the code (replacing the assignment to CH and the plot function) do the job.
vertices, edges, facets = alpha_shape_3D(pos=M, alpha=3.)
ax.plot_trisurf(x,y,z,triangles=facets, shade=False, color='lightblue',lw=1, edgecolor='k')

Basemap plus 3d graph

Hello Stackoverflow forks,
I'm a enthusiastic python learner.
I have studied python to visualiza my personal project about population density.
I have gone through tutorials about matplotlib and basemap in python.
I came across with the idea about
mapping my 3dimensional graph on top of the basemap which allows me to use geographycal coordinate information.
Can anyone let me know how I could use basemap as a base plane for the 3dimensional graph?
Please let me know which tutorial or references I could go with for developing this.
Best,
Thank you always Stackoverflow forks.
The basemap documentation has a small section on 3D plotting. Here's a simple script to get you started:
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
plt.close('all')
fig = plt.figure()
ax = fig.gca(projection='3d')
extent = [-127, -65, 25, 51]
# make the map and axis.
m = Basemap(llcrnrlon=extent[0], llcrnrlat=extent[2],
urcrnrlon=extent[1], urcrnrlat=extent[3],
projection='cyl', resolution='l', fix_aspect=False, ax=ax)
ax.add_collection3d(m.drawcoastlines(linewidth=0.25))
ax.add_collection3d(m.drawcountries(linewidth=0.25))
ax.add_collection3d(m.drawstates(linewidth=0.25))
ax.view_init(azim = 230, elev = 15)
ax.set_xlabel(u'Longitude (°E)', labelpad=10)
ax.set_ylabel(u'Latitude (°N)', labelpad=10)
ax.set_zlabel(u'Altitude (ft)', labelpad=20)
# values to plot - change as needed. Plots 2 dots, one at elevation 0 and another 100.
# also draws a line between the two.
x, y = m(-85.4808, 32.6099)
ax.plot3D([x, x], [y, y], [0, 100], color = 'green', lw = 0.5)
ax.scatter3D(x, y, 100, s = 5, c = 'k', zorder = 4)
ax.scatter3D(x, y, 0, s = 2, c = 'k', zorder = 4)
ax.set_zlim(0., 400.)
plt.show()

How can I align the y-axis (latitudes) of a map plot and a plot in python

I'm trying plot two panels in a plot.
The first one (left) is a data with latitude values in its y-axis. The second panel is a map.
I wanna that the latitude values of both panels coinciding, but I don't know how get it.
I have a code like this:
fig_mapa= plt.figure()
'''Mapa'''
ax1=fig_mapa.add_subplot(122)
map = Basemap(llcrnrlon=-90,llcrnrlat=-58.1,urcrnrlon=-32,urcrnrlat=12.6,
resolution='f',projection='merc',lon_0=-58,lat_0=-25, ax=ax1)
map.drawparallels(np.arange(-90,90.,5), labels=[0,1,0,0], linewidth=0.5)
map.drawmeridians(np.arange(-180.,180.,5), labels=[0,0,0,1], linewidth=0.5)
map.readshapefile("./Fases_tectonicas/Shapefiles/Unidades_Fi", 'Unidades_Fi', linewidth=0.1)
#map.warpimage(image='./Geotiffs/NE1_HR_LC_SR_W_DR/NE1_HR_LC_SR_W_DR.tif', zorder=1)
map.drawcoastlines(linewidth=0.5, color='k')
Nombre_Unidad= []
for elemento in map.Unidades_Fi_info:
Nombre_Unidad.append(elemento['NAME'])
for i in range(len(Nombre_Unidad)):
draw=map.Unidades_Fi[i]
poly=Polygon(draw, facecolor=color[Nombre_Unidad[i]],edgecolor='k', alpha=0.5,linewidth=0.1, zorder=2)
plt.gca().add_patch(poly)
'''Gráfico Eventos Compresivos'''
ax2= fig_mapa.add_subplot(121)
ax2.set_ylim(-58.1,12.6)
ax2.set_xlim(120,0)
ax2.set_xlabel('Tiempo [Ma]')
ax2.set_ylabel('Latitud[°]')
ax2.grid()
The simplest way to align two axes is with the sharex or sharey keyword for plt.subplots. However, the coordinates that Basemap shows and the coordinates that it uses for the Axes instance are two different things, so you will have to convert between the two if you want to have understandable ytick labels and some meaningful graph in your second Axes instance. Below I show how you can align the two y-axes, set the yticks properly and transform your data to the data coordinates of your Basemap. I left the creation of the Basemap untouched.
from matplotlib import pyplot as plt
from mpl_toolkits.basemap import Basemap
import numpy as np
##figure with two subplots and shared y-axis
fig,(ax2,ax1) = plt.subplots(nrows=1, ncols=2, sharey='row')
m1 = Basemap(llcrnrlon=-90,llcrnrlat=-58.1,urcrnrlon=-32,urcrnrlat=12.6,
#resolution='f',
projection='merc',lon_0=-58,lat_0=-25, ax=ax1)
m1.drawparallels(np.arange(-90,90.,5), labels=[0,1,0,0], linewidth=0.5)
m1.drawmeridians(np.arange(-180.,180.,5), labels=[0,0,0,1], linewidth=0.5)
m1.drawcoastlines(linewidth=0.5, color='k')
##turning off yticks at basemap
ax1.yaxis.set_ticks_position('none')
##setting yticks:
yticks = np.arange(-55,12.6,5)
##transform yticks:
_,yticks_data = m1(0*yticks,yticks)
ax2.set_yticks(yticks_data)
ax2.set_yticklabels(['{: >3}$^\circ${}'.format(
abs(int(y)), 'N' if y>0 else 'S' if y<0 else ' '
) for y in yticks])
ax2.set_xlim(120,0)
ax2.set_xlabel('Tiempo [Ma]')
ax2.set_ylabel('Latitud[$^\circ$]')
ax2.grid()
#some fake data for testing plotting
yrange = np.linspace(-60,20,100)
temp = (np.sin(10*np.deg2rad(yrange))+1)*50
##transform yrange
_,yrange_data = m1(0*yrange, yrange)
ax2.plot(temp,yrange_data)
plt.show()
The result of the above code looks like this:
Hope this helps.

Wrong annotation text location on basemap

I try to annotate sampling position name on each scatter sample location based on mercator projection. I used sampling position as annotation location with map projection coordinate. But placed annotation location is wrong position on map.
Ex. When I annotate some position in pacific ocean, annotate text placed on Japan inland.
My map is below.
Here is my code.
from mpl_toolkits.basemap import Basemap
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
from matplotlib.patches import Polygon
# create new figure, axes instances.
fig=plt.figure()
ax=fig.add_axes([0.1,0.1,0.8,0.8])
# setup mercator map projection.
m = Basemap(llcrnrlon=120.,llcrnrlat=10.,urcrnrlon=150.,urcrnrlat=44.,\
rsphere=(6378137.00,6356752.3142),\
resolution='l',projection='merc',\
lat_0=20.,lon_0=135.,lat_ts=25)
# nylat, nylon are lat/lon of Guam
nylat = 14; nylon = 140
# lonlat, lonlon are lat/lon of Busan.
lonlat = 35; lonlon = 128
# Draw Background Map
m.bluemarble()
m.drawgreatcircle(nylon,nylat,lonlon,lonlat,linewidth=2,color='b')
m.drawcoastlines()
m.fillcontinents()
# draw parallels
m.drawparallels(np.arange(10,50,10),labels=[1,1,0,1])
# draw meridians
m.drawmeridians(np.arange(120,155,10),labels=[1,1,0,1])
#m.drawmapscale(129, 15, 131, 16, 500, barstyle='fancy') drawing map scale
m.ax = ax
# Draw your dataset
lons = [130, 131, 132, 133]
lats = [21, 22, 23, 24]
Annotes = ['Sample #1', '2', '3', '4']
x, y = m(lons, lats)
#m.scatter(x1,y1,s=sizes,c=cols,marker="o",cmap=cm.cool,alpha=0.7)
m.scatter(x, y, s= 20, marker='*',color='y',alpha=0.7)
plt.annotate(Annotes[0], xy=(1110450, 1147253),xycoords='data',
xytext=(1110450, 1147253),textcoords='data',
arrowprops=dict(arrowstyle="->")
)
ax.set_title('Cruise line from Guam to Busan')
plt.show()
Thanks in advance. Any comments welcome!