Spinning globe GIF with cartopy - python

I'm failing to find the most efficient way to generate a simple animation of a spinning globe with a filled contour using cartopy. The following code yields a static gif, probably because the figure is not redrawing itself? Is there a way for the animation function to just change the geographic projection, without calling a contourf() again (which is computationally expensive)?
from pylab import *
import cartopy.crs as ccrs
from matplotlib.animation import FuncAnimation
lon, lat = meshgrid(
(linspace(-180,180,361)+0.5)[::4],
(linspace(-90,90,181)+0.5)[::4],
)
h = (abs(lon)<20).astype(int) * (abs(lat)<10).astype(int)
fig = figure(figsize=(3,3))
ax = fig.add_subplot(1, 1, 1, projection = ccrs.Orthographic())
ax.contourf(lon, lat, h, transform=ccrs.PlateCarree())
def update_fig(t):
ax.projection = ccrs.Orthographic(t)
ani = FuncAnimation(
fig,
update_fig,
frames = linspace(0,360,13)[:-1],
interval = 100,
blit = False,
)
ani.save('mwe.gif')

Related

Save 3D Matplot as interactive HTML

I´m trying to save my 3D trisurface Plot as an interactive HTML figure, so it should be possible to zoom in/ out and change the viewpoint. In the IDE the plot already exists and works so far, but I
can`t save it in the HTML format because of the ValueError:
"The fig parameter must be a dict or Figure.
Received value of type <class 'matplotlib.figure.Figure'>: Figure(1600x900)".
I don´t understand why the "<class 'matplotlib.figure.Figure'>" is not a Figure?
This was my approach: https://plotly.com/python/interactive-html-export/
And I tried it with go.Figure() (Export rotable 3D plots from Python to HTML) already but it didn´t work with the trisurf.
Is there a way to keep my Plot settings (use trisurf as it is) and get the interactive figure in HTML?
Thanks a lot for any answer
#Import libraries
import matplotlib
#matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import LinearLocator
import numpy as np
from mpl_toolkits.mplot3d import axes3d, Axes3D
import pandas as pd
import plotly.express as px
import io
import plotly.io as pio
%matplotlib notebook
E = np.arange(225)
D = np.arange(225)
A = np.arange(225)
E = [10000.0, 10000.0, ...]
D = [500.0, 1000.0, ...]
A = [1.9495, 1.9644, ...]
#Create figure
fig = plt.figure(figsize =(16, 9))
ax = plt.axes(projection ='3d')
# Creating color map
my_cmap = plt.get_cmap('hot')
# Data for three-dimensional scattered points
zdata = A
xdata = D
ydata = E
# Creating plot
trisurf = ax.plot_trisurf(xdata, ydata, zdata,
cmap = my_cmap,
linewidth = 0.2,
antialiased = True,
edgecolor = 'grey')
fig.colorbar(trisurf, ax = ax, shrink = 0.5, aspect = 10)
ax.set_title('AIE_SIM0.003__lowE_10000_upE_460000_stepE_30000_lowD_500.0_upD_8000.0_stepD_500.0')
ax.set_xlabel('Damping Ns/m')
ax.set_ylabel('Stifness N/m')
ax.set_zlabel('Amplification')
A2 = np.arange(225)
A2.fill(20.757)
# Creating color map
my_cmap2 = plt.get_cmap('gray')
# Data for three-dimensional scattered points
zdata2 = A2
xdata = D
ydata = E
# Creating plot
trisurf2 = ax.plot_trisurf(xdata, ydata, zdata2,
cmap = my_cmap2,
linewidth = 0.2,
antialiased = False,
edgecolor = 'none', alpha = 0.2)
fig.colorbar(trisurf2, ax = ax, shrink = 0.5, aspect = 10)
print(type(fig))
#fig.write_html("file.html")
plotly.io.to_html(fig=fig)
fig.savefig('3D_Plot_PNG_lowE_10000_upE_460000_stepE_30000_lowD_500.0_upD_8000.0_stepD_500.0.png')
fig.show()
------------------------------------------------------------------------------------------
Figure 1
printed: <class 'matplotlib.figure.Figure'>
ValueError:
The fig parameter must be a dict or Figure.
Received value of type <class 'matplotlib.figure.Figure'>: Figure(1600x900)
As far as I'm aware, Matplotlib is not able to generate 3D html plot.
Moreover, what you tried above is wrong. That error message is telling you that Plotly's to_html only works with Plotly's Figure. So mixing Plotly and Matplotlib is not going to work. You need to create a Plotly figure.
Also, I don't think that Plotly exposes something similar to Matplotlib's plot_trisurf. However, it exposes go.Mesh that allows us to achieve the same result.
The recipe:
Generate your numerical data.
Create a triangulation. We will use Matplotlib's Triangulation class for this part.
Create the Plotly figure and add the surface.
Export the figure to html.
Here I'm going to post an example to guide you:
import numpy as np
import matplotlib.tri as mtri
import plotly.graph_objects as go
### DATA GENERATION
# Make parameter spaces radii and angles.
n_angles = 36
n_radii = 8
min_radius = 0.25
radii = np.linspace(min_radius, 0.95, n_radii)
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
angles[:, 1::2] += np.pi/n_angles
# Map radius, angle pairs to x, y, z points.
x = (radii*np.cos(angles)).flatten()
y = (radii*np.sin(angles)).flatten()
z = (np.cos(radii)*np.cos(3*angles)).flatten()
### TRIANGULATION
# Create the Triangulation; no triangles so Delaunay triangulation created.
triang = mtri.Triangulation(x, y)
# Mask off unwanted triangles.
xmid = x[triang.triangles].mean(axis=1)
ymid = y[triang.triangles].mean(axis=1)
mask = xmid**2 + ymid**2 < min_radius**2
triangles = triang.triangles[~mask]
### PLOT
fig = go.Figure(data=[
# go.Mesh allows to provide the triangulation
go.Mesh3d(
x=x, y=y, z=z,
colorbar_title='z',
colorscale="aggrnyl",
# Intensity of each vertex, which will be interpolated and color-coded
intensity =z,
# i, j and k give the vertices of triangles
i = triangles[:, 0],
j = triangles[:, 1],
k = triangles[:, 2],
showscale=True
)
])
fig.show()
### EXPORT TO HTML
# Please, execute `help(fig.write_html)` to learn about all the
# available keyword arguments to control the output
fig.write_html("test.html", include_plotlyjs=True, full_html=True)

Using matlotlib: why do imshow and contourf not plot together? (contourf "overrides" imshow)

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!

GOES-East Full Disk domain realtime imagery does not fit actual data

I'm trying to plot GOES-East full disk data using metpy, and Siphon to download the latest data from the THREDDS data server. However, after comparing my plots with the realtime imagery, ther seems to be a large difference.
Below is my code:
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt
import metpy.calc as mpcalc
from metpy.plots.ctables import registry
from metpy.plots import add_timestamp
from metpy.units import units
from siphon.catalog import TDSCatalog
import xarray as xr
import numpy as np
from xarray.backends import NetCDF4DataStore
from datetime import datetime, timedelta
dt = datetime.utcnow().date()
data = TDSCatalog(f'http://thredds.ucar.edu/thredds/catalog/satellite/goes/east/products/'
f'CloudAndMoistureImagery/FullDisk/Channel09/{dt:%Y%m%d}/catalog.xml')
sat_dataset = data.datasets[0].remote_access(use_xarray = True)
cmi = sat_dataset.metpy.parse_cf('Sectorized_CMI')
x = cmi.coords['x'][:]
y = cmi.coords['y'][:]
timestamp = datetime.strptime(str(cmi.time.values.astype('datetime64[s]')), '%Y-%m-%dT%H:%M:%S')
print(timestamp)
vtime = timestamp.strftime('%Y-%m-%d %H%M%S')
# Create the figure
fig = plt.figure(figsize = [16, 10])
ax = fig.add_subplot(1, 1, 1, projection = cmi.metpy.cartopy_crs)
ax.set_extent([-80, -45, -50, -15], crs = ccrs.PlateCarree())
ax.add_feature(cfeature.BORDERS.with_scale('50m'), edgecolor = 'black', linewidth = 1)
ax.add_feature(cfeature.COASTLINE.with_scale('50m'), edgecolor = 'black', linewidth = 1)
ax.add_feature(cfeature.STATES.with_scale('50m'), edgecolor = 'white', linewidth = 1)
# Add mapping information
ax.add_feature(cfeature.STATES)
ax.add_feature(cfeature.BORDERS, linewidth=2)
# Plot the image with our colormapping choices
wv_norm, wv_cmap = registry.get_with_range('WVCIMSS_r', 193, 283)
im = ax.imshow(cmi, extent=(x[0], x[-1], y[0], y[-1]), origin='upper',
cmap = wv_cmap, norm = wv_norm, transform = cmi.metpy.cartopy_crs)
plt.colorbar(im, ticks = np.arange(193, 293, 10), ax = ax)
plt.title(f'Vapor da Água em Níveis Médios [$K$] \nValid: {vtime} UTC', loc = 'left')
plt.savefig(f'/mnt/c/Users/vitor/Desktop/WV_{vtime}.jpg', bbox_inches = 'tight')
Also below, is a comparison between the output from my code and the actual water vapor imagery from the CODNEXLAB website. I also looked at the metadata of the downloaded files and everything seems to be fine. Not sure if I'm doing something wrong here.
What you're seeing is that your image is flipped (it's easier to identify if you look at the global plot of that data). What's happening is the origin you specified ('upper'/'lower') disagree with what you passed as extent. So either tweak your origin parameter:
im = ax.imshow(cmi, extent=(x[0], x[-1], y[0], y[-1]),
origin='lower', cmap=wv_cmap, norm=wv_norm,
transform=cmi.metpy.cartopy_crs)
or flip the order of your y extents:
im = ax.imshow(cmi, extent=(x[0], x[-1], y[-1], y[0]),
origin='upper', cmap=wv_cmap, norm=wv_norm,
transform=cmi.metpy.cartopy_crs)

Strange behavior with contours in Cartopy polar stereographic 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)

set_clip_path on a matplotlib clabel not clipping properly

I'm making a contour plot that is clipped to a polygon path:
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
import numpy as np
fig = plt.figure()
axes = plt.subplot()
x,y = np.meshgrid( np.linspace(-10,10,51), np.linspace(-10,10,51) )
z = np.sin(np.sqrt(x**2+y**2))
CS = axes.contour(x, y, z, np.linspace(-1,1,11) )
axes.set_aspect('equal')
# clip contours by polygon
radius = 8
t = np.linspace(0,2*np.pi,101)
x_bound,y_bound = radius*np.sin(t),radius*(np.cos(t)+0.1*(np.cos(7*t)))
clip_map = Polygon(list(zip(x_bound,y_bound)),fc='#EEEEEE',ec='none')
axes.add_patch(clip_map)
for collection in CS.collections:
collection.set_clip_path(clip_map)
# label the contours
CLB = axes.clabel(CS, colors='black')
for text_object in CLB:
text_object.set_clip_path(clip_map) # Doesn't do anything!
plt.show()
To my surprise, the labels aren't clipped despite the Text objects having a set_clip_path method that doesn't return an error:
How can I clip the labels outside of the gray polygon area? Do I need to resort to manually finding the X and Y positions, calculating point in polygon, and set_visible = False for each Text item? Why doesn't this code work as-is? I'm using matplotlib version 1.5.1 and python 3.5.1.
Just in case someone comes across the same issue someday, here's a solution that resorts to having to use the shapely package to test for point in polygon to set the visibility state of the Text object. It gets the job done, but it would be nice if it was possible to use set_clip_path to work directly on the Text object.
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
import numpy as np
from shapely.geometry import Polygon as ShapelyPolygon
from shapely.geometry import Point as ShapelyPoint
fig = plt.figure()
axes = plt.subplot()
x,y = np.meshgrid( np.linspace(-10,10,51), np.linspace(-10,10,51) )
z = np.sin(np.sqrt(x**2+y**2))
CS = axes.contour(x, y, z, np.linspace(-1,1,11) )
axes.set_aspect('equal')
# clip contours by polygon
radius = 8
t = np.linspace(0,2*np.pi,101)
x_bound,y_bound = radius*np.sin(t),radius*(np.cos(t)+0.1*(np.cos(7*t)))
clip_map = Polygon(list(zip(x_bound,y_bound)),fc='#EEEEEE',ec='none')
axes.add_patch(clip_map)
for collection in CS.collections:
collection.set_clip_path(clip_map)
# label the contours
CLB = axes.clabel(CS, colors='black')
clip_map_shapely = ShapelyPolygon(clip_map.get_xy())
for text_object in CLB:
if not clip_map_shapely.contains(ShapelyPoint(text_object.get_position())):
text_object.set_visible(False)
plt.show()

Categories