import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.animation import PillowWriter as ImageWriter
from scipy import stats
from rocketpy.units import convert_units
from ..tools import find_two_closest_integers, import_optional_dependency
# TODO: `wind_speed_limit` and `clear_range_limits` and should be numbers, not booleans
[docs]
class _EnvironmentAnalysisPlots:
"""Class that holds plot methods for EnvironmentAnalysis class.
Attributes
----------
_EnvironmentAnalysisPlots.env_analysis : EnvironmentAnalysis
EnvironmentAnalysis object that will be used for the plots.
_EnvironmentAnalysisPlots.surface_level_dict : dict
Dictionary with all surface level data.
_EnvironmentAnalysisPlots.pressure_level_dict : dict
Dictionary with all pressure level data.
"""
[docs]
def __init__(self, env_analysis):
"""Initializes the class.
Parameters
----------
env_analysis : rocketpy.EnvironmentAnalysis
Instance of the rocketpy EnvironmentAnalysis class
Returns
-------
None
"""
# Save attributes
self.env_analysis = env_analysis
# Save commonly used attributes
self.surface_level_dict = self.env_analysis.converted_surface_data
self.pressure_level_dict = self.env_analysis.converted_pressure_level_data
return None
def __beaufort_wind_scale(self, units, max_wind_speed=None):
"""Returns a list of bins equivalent to the Beaufort wind scale in the
desired unit system.
Parameters
----------
units: str
Desired units for wind speed.
Options are: "knot", "mph", "m/s", "ft/s: and "km/h".
max_wind_speed: float
Maximum wind speed to be included in the scale. Should be expressed
in the same unit as the units parameter.
Returns
-------
list[float]
"""
wind_scale_knots = np.array(
[0, 1, 3, 6, 10, 16, 21, 27, 33, 40, 47, 55, 63, 71]
)
wind_scale = wind_scale_knots * convert_units(1, "knot", units)
wind_scale_truncated = wind_scale[np.where(wind_scale <= max_wind_speed)]
if wind_scale[1] < 1:
return np.round(wind_scale_truncated, 1)
else:
return np.round(wind_scale_truncated, 0)
# Surface level plots
[docs]
def wind_gust_distribution(self):
"""Get all values of wind gust speed (for every date and hour available)
and plot a single distribution. Expected result is a Weibull distribution,
however, the result is not always a perfect fit, and sometimes it may
look like a normal distribution.
Returns
-------
None
"""
plt.figure()
# Plot histogram
plt.hist(
self.env_analysis.wind_gust_list,
bins=int(len(self.env_analysis.wind_gust_list) ** 0.5),
density=True,
histtype="stepfilled",
alpha=0.2,
label="Wind Gust",
)
# Plot weibull distribution
c, loc, scale = stats.weibull_min.fit(
self.env_analysis.wind_gust_list, loc=0, scale=1
)
x = np.linspace(0, np.max(self.env_analysis.wind_gust_list), 100)
plt.plot(
x,
stats.weibull_min.pdf(x, c, loc, scale),
"r-",
linewidth=2,
label="Weibull Distribution",
)
# Label plot
plt.ylabel("Probability")
plt.xlabel(f"Wind gust speed ({self.env_analysis.unit_system['wind_speed']})")
plt.title("Wind Gust Speed Distribution (at surface)")
plt.xlim(0, max(self.env_analysis.wind_gust_list))
plt.legend()
plt.show()
return None
[docs]
def surface10m_wind_speed_distribution(self, wind_speed_limit=False):
"""Get all values of sustained surface wind speed (for every date and
hour available) and plot a single distribution. Expected result is a
Weibull distribution. The wind speed limit is plotted as a vertical line.
Parameters
----------
wind_speed_limit : bool, optional
If True, plots the wind speed limit as a vertical line. The default
is False.
Returns
-------
None
"""
plt.figure()
# Plot histogram
plt.hist(
self.env_analysis.surface_10m_wind_speed_list,
bins=int(len(self.env_analysis.surface_10m_wind_speed_list) ** 0.5),
density=True,
histtype="stepfilled",
alpha=0.2,
label="Wind Speed",
)
# Plot weibull distribution
c, loc, scale = stats.weibull_min.fit(
self.env_analysis.surface_10m_wind_speed_list, loc=0, scale=1
)
x = np.linspace(0, np.max(self.env_analysis.surface_10m_wind_speed_list), 100)
plt.plot(
x,
stats.weibull_min.pdf(x, c, loc, scale),
"r-",
linewidth=2,
label="Weibull Distribution",
)
if wind_speed_limit:
plt.vlines(
convert_units(20, "mph", self.env_analysis.unit_system["wind_speed"]),
0,
0.3,
"g",
(0, (15, 5, 2, 5)),
label="Wind Speed Limit",
) # Plot Wind Speed Limit
# Label plot
plt.ylabel("Probability")
plt.xlabel(
f"Sustained surface wind speed ({self.env_analysis.unit_system['wind_speed']})"
)
plt.title("Sustained Wind Speed Distribution (at surface+10m)")
plt.xlim(0, max(self.env_analysis.surface_10m_wind_speed_list))
plt.legend()
plt.show()
return None
[docs]
def average_surface_temperature_evolution(self):
"""Plots average temperature progression throughout the day, including
sigma contours.
Returns
-------
None
"""
# Get handy arrays
temperature_mean = np.array(
list(self.env_analysis.average_temperature_by_hour.values())
)
temperature_std = np.array(
list(self.env_analysis.std_temperature_by_hour.values())
)
temperatures_p1sigma = temperature_mean + temperature_std
temperatures_m1sigma = temperature_mean - temperature_std
temperatures_p2sigma = temperature_mean + 2 * temperature_std
temperatures_m2sigma = temperature_mean - 2 * temperature_std
plt.figure()
# Plot temperature along day for each available date
for hour_entries in self.surface_level_dict.values():
plt.plot(
[int(hour) for hour in hour_entries.keys()],
[val["surface_temperature"] for val in hour_entries.values()],
"gray",
alpha=0.1,
)
# Plot average temperature along day
plt.plot(self.env_analysis.hours, temperature_mean, "r", label="$\\mu$")
# Plot standard deviations temperature along day
plt.plot(
self.env_analysis.hours,
temperatures_m1sigma,
"b--",
label=r"$\mu \pm \sigma$",
)
plt.plot(self.env_analysis.hours, temperatures_p1sigma, "b--")
plt.plot(self.env_analysis.hours, temperatures_p2sigma, "b--", alpha=0.5)
plt.plot(
self.env_analysis.hours,
temperatures_m2sigma,
"b--",
label=r"$\mu \pm 2\sigma $",
alpha=0.5,
)
# Format plot
plt.gca().xaxis.set_major_locator(plt.MaxNLocator(integer=True))
plt.gca().xaxis.set_major_formatter(
lambda x, pos: "{0:02.0f}:{1:02.0f}".format(*divmod(x * 60, 60))
)
plt.autoscale(enable=True, axis="x", tight=True)
plt.xlabel("Time (hours)")
plt.ylabel(f"Temperature ({self.env_analysis.unit_system['temperature']})")
plt.title("Average Temperature Along Day")
plt.grid(alpha=0.25)
plt.legend()
plt.show()
return None
[docs]
def average_surface10m_wind_speed_evolution(self, wind_speed_limit=False):
"""Plots average surface wind speed progression throughout the day,
including sigma contours.
Parameters
----------
wind_speed_limit : bool, optional
If True, plots the wind speed limit as a horizontal line. The default
is False.
Returns
-------
None
"""
# Get handy arrays
wind_speed_mean = np.array(
list((self.env_analysis.average_surface_10m_wind_speed_by_hour.values()))
)
wind_speed_std = np.array(
list(self.env_analysis.std_surface_10m_wind_speed_by_hour.values())
)
wind_speeds_p1sigma = wind_speed_mean + wind_speed_std
wind_speeds_m1sigma = wind_speed_mean - wind_speed_std
wind_speeds_p2sigma = wind_speed_mean + 2 * wind_speed_std
wind_speeds_m2sigma = wind_speed_mean - 2 * wind_speed_std
plt.figure()
# Plot average wind speed along day
for hour_entries in self.surface_level_dict.values():
plt.plot(
[x for x in self.env_analysis.hours],
[
(
val["surface10m_wind_velocity_x"] ** 2
+ val["surface10m_wind_velocity_y"] ** 2
)
** 0.5
for val in hour_entries.values()
],
"gray",
alpha=0.1,
)
# Plot average temperature along day
plt.plot(self.env_analysis.hours, wind_speed_mean, "r", label="$\\mu$")
# Plot standard deviations temperature along day
plt.plot(
self.env_analysis.hours,
wind_speeds_m1sigma,
"b--",
label=r"$\mu \pm \sigma$",
)
plt.plot(self.env_analysis.hours, wind_speeds_p1sigma, "b--")
plt.plot(self.env_analysis.hours, wind_speeds_p2sigma, "b--", alpha=0.5)
plt.plot(
self.env_analysis.hours,
wind_speeds_m2sigma,
"b--",
label=r"$\mu \pm 2\sigma $",
alpha=0.5,
)
# Format plot
plt.gca().xaxis.set_major_locator(plt.MaxNLocator(integer=True))
plt.gca().xaxis.set_major_formatter(
lambda x, pos: "{0:02.0f}:{1:02.0f}".format(*divmod(x * 60, 60))
)
plt.autoscale(enable=True, axis="x", tight=True)
if wind_speed_limit:
plt.hlines(
convert_units(20, "mph", self.env_analysis.unit_system["wind_speed"]),
min(self.env_analysis.hours),
max(self.env_analysis.hours),
"g",
(0, (15, 5, 2, 5)),
label="Wind Speed Limit",
)
plt.xlabel("Time (hours)")
plt.ylabel(
f"Surface Wind Speed ({self.env_analysis.unit_system['wind_speed']})"
)
plt.title("Average Sustained Surface Wind Speed Along Day")
plt.grid(alpha=0.25)
plt.legend()
plt.show()
return None
[docs]
def average_surface100m_wind_speed_evolution(self):
"""Plots average surface wind speed progression throughout the day, including
sigma contours.
Returns
-------
None
"""
# Get handy arrays
wind_speed_mean = (
self.env_analysis.average_surface_100m_wind_speed_by_hour.values()
)
wind_speed_mean = np.array(list(wind_speed_mean))
wind_speed_std = np.array(
list(self.env_analysis.std_surface_100m_wind_speed_by_hour.values())
)
wind_speeds_p1sigma = wind_speed_mean + wind_speed_std
wind_speeds_m1sigma = wind_speed_mean - wind_speed_std
wind_speeds_p2sigma = wind_speed_mean + 2 * wind_speed_std
wind_speeds_m2sigma = wind_speed_mean - 2 * wind_speed_std
plt.figure()
# Plot temperature along day for each available date
for hour_entries in self.surface_level_dict.values():
plt.plot(
[int(hour) for hour in hour_entries.keys()],
[
(
val["surface100m_wind_velocity_x"] ** 2
+ val["surface100m_wind_velocity_y"] ** 2
)
** 0.5
for val in hour_entries.values()
],
"gray",
alpha=0.1,
)
# Plot average temperature along day
plt.plot(self.env_analysis.hours, wind_speed_mean, "r", label="$\\mu$")
# Plot standard deviations temperature along day
plt.plot(
self.env_analysis.hours,
wind_speeds_m1sigma,
"b--",
label=r"$\mu \pm \sigma$",
)
plt.plot(self.env_analysis.hours, wind_speeds_p1sigma, "b--")
plt.plot(self.env_analysis.hours, wind_speeds_p2sigma, "b--", alpha=0.5)
plt.plot(
self.env_analysis.hours,
wind_speeds_m2sigma,
"b--",
label=r"$\mu \pm 2\sigma $",
alpha=0.5,
)
# Format plot
plt.gca().xaxis.set_major_locator(plt.MaxNLocator(integer=True))
plt.gca().xaxis.set_major_formatter(
lambda x, pos: "{0:02.0f}:{1:02.0f}".format(*divmod(x * 60, 60))
)
plt.autoscale(enable=True, axis="x", tight=True)
plt.xlabel("Time (hours)")
plt.ylabel(f"100m Wind Speed ({self.env_analysis.unit_system['wind_speed']})")
plt.title("Average 100m Wind Speed Along Day")
plt.grid(alpha=0.25)
plt.legend()
plt.show()
return None
# Average profiles plots (pressure level data)
[docs]
def average_wind_speed_profile(self, clear_range_limits=False):
"""Average wind speed for all datetimes available. The plot also includes
sigma contours.
Parameters
----------
clear_range_limits : bool, optional
If True, clears the range limits. The default is False.
Returns
-------
None
"""
plt.figure()
plt.plot(
self.env_analysis.average_wind_speed_profile,
self.env_analysis.altitude_list,
"r",
label="$\\mu$ speed",
)
plt.plot(
np.percentile(
self.env_analysis.wind_speed_profiles_list, 50 - 34.1, axis=0
),
self.env_analysis.altitude_list,
"b--",
alpha=1,
label="$\\mu \\pm \\sigma$",
)
plt.plot(
np.percentile(
self.env_analysis.wind_speed_profiles_list, 50 + 34.1, axis=0
),
self.env_analysis.altitude_list,
"b--",
alpha=1,
)
plt.plot(
np.percentile(
self.env_analysis.wind_speed_profiles_list, 50 - 47.4, axis=0
),
self.env_analysis.altitude_list,
"b--",
alpha=0.5,
label="$\\mu \\pm 2\\sigma$",
)
plt.plot(
np.percentile(
self.env_analysis.wind_speed_profiles_list, 50 + 47.7, axis=0
),
self.env_analysis.altitude_list,
"b--",
alpha=0.5,
)
for wind_speed_profile in self.env_analysis.wind_speed_profiles_list:
plt.plot(
wind_speed_profile, self.env_analysis.altitude_list, "gray", alpha=0.01
)
plt.autoscale(enable=True, axis="x", tight=True)
plt.autoscale(enable=True, axis="y", tight=True)
if clear_range_limits:
x_min, xmax, _, _ = plt.axis()
plt.fill_between(
[x_min, xmax],
0.7
* convert_units(10000, "ft", self.env_analysis.unit_system["length"]),
1.3
* convert_units(10000, "ft", self.env_analysis.unit_system["length"]),
color="g",
alpha=0.2,
label=f"10,000 {self.env_analysis.unit_system['length']} ± 30%",
)
plt.fill_between(
[x_min, xmax],
0.7
* convert_units(30000, "ft", self.env_analysis.unit_system["length"]),
1.3
* convert_units(30000, "ft", self.env_analysis.unit_system["length"]),
color="g",
alpha=0.2,
label=f"30,000 {self.env_analysis.unit_system['length']} ± 30%",
)
plt.xlabel(f"Wind speed ({self.env_analysis.unit_system['wind_speed']})")
plt.ylabel(f"Altitude AGL ({self.env_analysis.unit_system['length']})")
plt.xlim(0, 360)
plt.title("Average Wind speed Profile")
plt.legend()
plt.xlim(
0,
max(
np.percentile(
self.env_analysis.wind_speed_profiles_list, 50 + 49.85, axis=0
)
),
)
plt.show()
return None
[docs]
def average_wind_velocity_xy_profile(self, clear_range_limits=False):
"""Average wind X and wind Y for all datetimes available. The X component
is the wind speed in the direction of East, and the Y component is the
wind speed in the direction of North.
Parameters
----------
clear_range_limits : bool, optional
If True, clears the range limits. The default is False.
Returns
-------
None
"""
plt.figure()
plt.plot(
self.env_analysis.average_wind_velocity_x_profile,
self.env_analysis.altitude_list,
"r",
label="$\\mu$ X",
)
plt.plot(
self.env_analysis.average_wind_velocity_y_profile,
self.env_analysis.altitude_list,
"b",
label="$\\mu$ Y",
)
if clear_range_limits:
x_min, xmax, _, _ = plt.axis()
plt.fill_between(
[x_min, xmax],
0.7
* convert_units(10000, "ft", self.env_analysis.unit_system["length"]),
1.3
* convert_units(10000, "ft", self.env_analysis.unit_system["length"]),
color="g",
alpha=0.2,
label=f"10,000 {self.env_analysis.unit_system['length']} ± 30%",
)
plt.fill_between(
[x_min, xmax],
0.7
* convert_units(30000, "ft", self.env_analysis.unit_system["length"]),
1.3
* convert_units(30000, "ft", self.env_analysis.unit_system["length"]),
color="g",
alpha=0.2,
label=f"30,000 {self.env_analysis.unit_system['length']} ± 30%",
)
plt.autoscale(enable=True, axis="x", tight=True)
plt.autoscale(enable=True, axis="y", tight=True)
plt.xlabel(f"Wind speed ({self.env_analysis.unit_system['wind_speed']})")
plt.ylabel(f"Altitude AGL ({self.env_analysis.unit_system['length']})")
plt.title("Average Wind X and Y Profile")
plt.legend()
plt.grid()
plt.show()
return None
[docs]
def average_wind_heading_profile(self, clear_range_limits=False):
"""Average wind heading for all datetimes available.
Parameters
----------
clear_range_limits : bool, optional
If True, clears the range limits. The default is False.
Returns
-------
None
"""
plt.figure()
plt.plot(
self.env_analysis.average_wind_heading_profile,
self.env_analysis.altitude_list,
"r",
label="$\\mu$",
)
plt.autoscale(enable=True, axis="x", tight=True)
plt.autoscale(enable=True, axis="y", tight=True)
if clear_range_limits:
x_min, xmax = 0, 360
plt.fill_between(
[x_min, xmax],
0.7
* convert_units(10000, "ft", self.env_analysis.unit_system["length"]),
1.3
* convert_units(10000, "ft", self.env_analysis.unit_system["length"]),
color="g",
alpha=0.2,
label=f"10,000 {self.env_analysis.unit_system['length']} ± 30%",
)
plt.fill_between(
[x_min, xmax],
0.7
* convert_units(30000, "ft", self.env_analysis.unit_system["length"]),
1.3
* convert_units(30000, "ft", self.env_analysis.unit_system["length"]),
color="g",
alpha=0.2,
label=f"30,000 {self.env_analysis.unit_system['length']} ± 30%",
)
plt.xlabel(f"Wind heading ({self.env_analysis.unit_system['angle']})")
plt.ylabel(f"Altitude AGL ({self.env_analysis.unit_system['length']})")
plt.xlim(0, 360)
plt.title("Average Wind heading Profile")
plt.legend()
plt.show()
return None
[docs]
def average_pressure_profile(self, clear_range_limits=False):
"""Average pressure profile for all datetimes available. The plot also
includes sigma contours.
Parameters
----------
clear_range_limits : bool, optional
If True, clears the range limits. The default is False.
Returns
-------
None
"""
plt.figure()
plt.plot(
np.mean(self.env_analysis.pressure_profiles_list, axis=0),
self.env_analysis.altitude_list,
"r",
label="$\\mu$",
)
plt.plot(
np.percentile(self.env_analysis.pressure_profiles_list, 15.9, axis=0),
self.env_analysis.altitude_list,
"b--",
alpha=1,
label="$\\mu \\pm \\sigma$",
)
plt.plot(
np.percentile(self.env_analysis.pressure_profiles_list, 84.1, axis=0),
self.env_analysis.altitude_list,
"b--",
alpha=1,
)
plt.plot(
np.percentile(self.env_analysis.pressure_profiles_list, 2.6, axis=0),
self.env_analysis.altitude_list,
"b--",
alpha=0.5,
label="$\\mu \\pm 2\\sigma$",
)
plt.plot(
np.percentile(self.env_analysis.pressure_profiles_list, 97.4, axis=0),
self.env_analysis.altitude_list,
"b--",
alpha=0.5,
)
for pressure_profile in self.env_analysis.pressure_profiles_list:
plt.plot(
pressure_profile, self.env_analysis.altitude_list, "gray", alpha=0.01
)
plt.autoscale(enable=True, axis="x", tight=True)
plt.autoscale(enable=True, axis="y", tight=True)
if clear_range_limits:
x_min, xmax, _, _ = plt.axis()
plt.fill_between(
[x_min, xmax],
0.7
* convert_units(10000, "ft", self.env_analysis.unit_system["length"]),
1.3
* convert_units(10000, "ft", self.env_analysis.unit_system["length"]),
color="g",
alpha=0.2,
label=f"10,000 {self.env_analysis.unit_system['length']} ± 30%",
)
plt.fill_between(
[x_min, xmax],
0.7
* convert_units(30000, "ft", self.env_analysis.unit_system["length"]),
1.3
* convert_units(30000, "ft", self.env_analysis.unit_system["length"]),
color="g",
alpha=0.2,
label=f"30,000 {self.env_analysis.unit_system['length']} ± 30%",
)
plt.xlabel(f"Pressure ({self.env_analysis.unit_system['pressure']})")
plt.ylabel(f"Altitude AGL ({self.env_analysis.unit_system['length']})")
plt.title("Average Pressure Profile")
plt.legend()
plt.xlim(
0,
max(np.percentile(self.env_analysis.pressure_profiles_list, 99.85, axis=0)),
)
plt.show()
return None
[docs]
def average_temperature_profile(self, clear_range_limits=False):
"""Average temperature profile for all datetimes available. The plot
also includes sigma contours.
Parameters
----------
clear_range_limits : bool, optional
If True, clears the range limits. The default is False.
Returns
-------
None
"""
plt.figure()
plt.plot(
np.mean(self.env_analysis.temperature_profiles_list, axis=0),
self.env_analysis.altitude_list,
"r",
label="$\\mu$",
)
plt.plot(
np.percentile(self.env_analysis.temperature_profiles_list, 15.9, axis=0),
self.env_analysis.altitude_list,
"b--",
alpha=1,
label="$\\mu \\pm \\sigma$",
)
plt.plot(
np.percentile(self.env_analysis.temperature_profiles_list, 84.1, axis=0),
self.env_analysis.altitude_list,
"b--",
alpha=1,
)
plt.plot(
np.percentile(self.env_analysis.temperature_profiles_list, 2.6, axis=0),
self.env_analysis.altitude_list,
"b--",
alpha=0.5,
label="$\\mu \\pm 2\\sigma$",
)
plt.plot(
np.percentile(self.env_analysis.temperature_profiles_list, 97.4, axis=0),
self.env_analysis.altitude_list,
"b--",
alpha=0.5,
)
for pressure_profile in self.env_analysis.temperature_profiles_list:
plt.plot(
pressure_profile, self.env_analysis.altitude_list, "gray", alpha=0.01
)
plt.autoscale(enable=True, axis="x", tight=True)
plt.autoscale(enable=True, axis="y", tight=True)
if clear_range_limits:
x_min, xmax, ymax, ymin = plt.axis()
plt.fill_between(
[x_min, xmax],
0.7
* convert_units(10000, "ft", self.env_analysis.unit_system["length"]),
1.3
* convert_units(10000, "ft", self.env_analysis.unit_system["length"]),
color="g",
alpha=0.2,
label=f"10,000 {self.env_analysis.unit_system['length']} ± 30%",
)
plt.fill_between(
[x_min, xmax],
0.7
* convert_units(30000, "ft", self.env_analysis.unit_system["length"]),
1.3
* convert_units(30000, "ft", self.env_analysis.unit_system["length"]),
color="g",
alpha=0.2,
label=f"30,000 {self.env_analysis.unit_system['length']} ± 30%",
)
plt.xlabel(f"Temperature ({self.env_analysis.unit_system['temperature']})")
plt.ylabel(f"Altitude AGL ({self.env_analysis.unit_system['length']})")
plt.title("Average Temperature Profile")
plt.legend()
plt.xlim(
min(
np.percentile(
self.env_analysis.temperature_profiles_list, 99.85, axis=0
)
),
max(
np.percentile(
self.env_analysis.temperature_profiles_list, 99.85, axis=0
)
),
)
plt.show()
return None
# Wind roses (surface level data)
[docs]
@staticmethod
def plot_wind_rose(
wind_direction, wind_speed, bins=None, title=None, fig=None, rect=None
):
"""Plot a windrose given the data.
Parameters
----------
wind_direction: list[float]
The wind direction.
wind_speed: list[float]
The wind speed
bins: 1D array or integer, optional
number of bins, or a sequence of bins variable. If not set, bins=6,
then bins=linspace(min(var), max(var), 6)
title: str, optional
Title of the plot
fig: matplotlib.pyplot.figure, optional
Figure to plot the windrose
Returns
-------
WindroseAxes
"""
windrose = import_optional_dependency("windrose")
WindroseAxes = windrose.WindroseAxes
ax = WindroseAxes.from_ax(fig=fig, rect=rect)
ax.bar(
wind_direction,
wind_speed,
bins=bins,
normed=True,
opening=0.8,
edgecolor="white",
)
ax.set_title(title)
ax.set_legend()
# Format the ticks (only integers, as percentage, at most 3 intervals)
ax.yaxis.set_major_locator(
mtick.MaxNLocator(integer=True, nbins=3, prune="lower")
)
ax.yaxis.set_major_formatter(mtick.PercentFormatter(decimals=0))
return ax
[docs]
def average_wind_rose_specific_hour(self, hour, fig=None):
"""Plot a specific hour of the average windrose
Parameters
----------
hour: int
Hour to be plotted
fig: matplotlib.pyplot.figure
Figure to plot the windrose
Returns
-------
None
"""
self.plot_wind_rose(
self.env_analysis.surface_wind_direction_by_hour[hour],
self.env_analysis.surface_wind_speed_by_hour[hour],
bins=self.__beaufort_wind_scale(
units=self.env_analysis.unit_system["wind_speed"],
max_wind_speed=self.env_analysis.record_max_surface_wind_speed,
),
title=f"Wind Rose of an Average Day ({self.env_analysis.unit_system['wind_speed']}) - Hour {float(hour):05.2f}".replace(
".", ":"
),
fig=fig,
)
plt.show()
return None
[docs]
def average_wind_rose_grid(self):
"""Plot wind roses for all hours of a day, in a grid like plot.
Returns
-------
None
"""
# Figure settings
windrose_side = 2.5 # inches
vertical_padding_top = 2.5 # inches
plot_padding = 0.18 # percentage
n_cols, n_rows = find_two_closest_integers(len(self.env_analysis.hours))
vertical_plot_area_percentage = (
n_rows * windrose_side / (n_rows * windrose_side + vertical_padding_top)
)
# Create figure
fig = plt.figure()
fig.set_size_inches(
n_cols * windrose_side, n_rows * windrose_side + vertical_padding_top
)
bins = self.__beaufort_wind_scale(
units=self.env_analysis.unit_system["wind_speed"],
max_wind_speed=self.env_analysis.record_max_surface_wind_speed,
)
width = (1 - 2 * plot_padding) * 1 / n_cols
height = vertical_plot_area_percentage * (1 - 2 * plot_padding) * 1 / n_rows
for k, hour in enumerate(self.env_analysis.hours):
# Row count bottom up
i, j = len(self.env_analysis.hours) // n_rows - k // n_cols, k % n_cols
left = j * 1 / n_cols + plot_padding / n_cols
bottom = (
vertical_plot_area_percentage
* ((i - 2) / n_rows + plot_padding / n_rows)
+ 0.5
)
ax = self.plot_wind_rose(
self.env_analysis.surface_wind_direction_by_hour[hour],
self.env_analysis.surface_wind_speed_by_hour[hour],
bins=bins,
title=f"{float(hour):05.2f}".replace(".", ":"),
fig=fig,
rect=[left, bottom, width, height],
)
if k == 0:
ax.legend(
loc="upper center",
# 0.8 is a magic number
bbox_to_anchor=(n_cols / 2 + 0.8, 1.8),
fancybox=True,
shadow=True,
ncol=n_cols,
)
else:
ax.legend().set_visible(False)
fig.add_axes(ax)
fig.suptitle(
f"Wind Roses ({self.env_analysis.unit_system['wind_speed']})",
fontsize=17,
x=0.5,
y=1,
)
plt.bbox_inches = "tight"
plt.show()
return None
[docs]
def animate_average_wind_rose(self, figsize=(5, 5), filename="wind_rose.gif"):
"""Animates the wind_rose of an average day. The inputs of a wind_rose
are the location of the place where we want to analyze, (x,y,z). The data
is assembled by hour, which means, the windrose of a specific hour is
generated by bringing together the data of all of the days available for
that specific hour. It's possible to change the size of the gif using the
parameter figsize, which is the height and width in inches.
Parameters
----------
figsize : tuple, optional
Size of the figure in inches, (width, height). The default is (8, 8).
filename : str
Name of the file to save the gif. The default is "wind_rose.gif".
Returns
-------
Image : ipywidgets.widget_media.Image
"""
widgets = import_optional_dependency("ipywidgets")
metadata = dict(
title="windrose",
artist="windrose",
comment="""Made with windrose
http://www.github.com/scls19fr/windrose""",
)
writer = ImageWriter(fps=1, metadata=metadata)
fig = plt.figure(facecolor="w", edgecolor="w", figsize=figsize)
with writer.saving(fig, filename, 100):
for hour in self.env_analysis.hours:
self.plot_wind_rose(
self.env_analysis.surface_wind_direction_by_hour[hour],
self.env_analysis.surface_wind_speed_by_hour[hour],
bins=self.__beaufort_wind_scale(
units=self.env_analysis.unit_system["wind_speed"],
max_wind_speed=self.env_analysis.record_max_surface_wind_speed,
),
title=f"Wind Rose of an Average Day ({self.env_analysis.unit_system['wind_speed']}). Hour {float(hour):05.2f}".replace(
".", ":"
),
fig=fig,
)
writer.grab_frame()
plt.clf()
with open(filename, "rb") as file:
image = file.read()
fig_width, fig_height = plt.gcf().get_size_inches() * fig.dpi
plt.close(fig)
return widgets.Image(
value=image,
format="gif",
width=fig_width,
height=fig_height,
)
# More plots and animations
[docs]
def wind_gust_distribution_grid(self):
"""Plots shown in the animation of how the wind gust distribution varies
throughout the day.
Returns
-------
None
"""
wind_gusts = self.env_analysis.surface_wind_gust_by_hour
# Create grid of plots to show a distribution for each hour
n_rows, n_cols = find_two_closest_integers(len(self.env_analysis.hours))
fig = plt.figure(figsize=(n_cols * 2, n_rows * 2.2))
gs = fig.add_gridspec(n_rows, n_cols, hspace=0, wspace=0, left=0.12)
axs = gs.subplots(sharex=True, sharey=True)
x_min, x_max, y_min, y_max = 0, 0, 0, 0
# Iterate over all hours and plot histograms
for i, j in [(i, j) for i in range(n_rows) for j in range(n_cols)]:
hour = self.env_analysis.hours[i * n_cols + j]
ax = axs[i, j]
ax.set_title(f"{float(hour):05.2f}".replace(".", ":"), y=0.8)
ax.hist(
wind_gusts[hour],
bins=int(len(wind_gusts[hour]) ** 0.5),
density=True,
histtype="stepfilled",
alpha=0.2,
label="Wind Gust",
)
ax.autoscale(enable=True, axis="y", tight=True)
# Plot weibull distribution
c, loc, scale = stats.weibull_min.fit(wind_gusts[hour], loc=0, scale=1)
x = np.linspace(0, np.ceil(self.env_analysis.max_wind_gust_list), 50)
ax.plot(
x,
stats.weibull_min.pdf(x, c, loc, scale),
"r-",
linewidth=2,
label="Weibull Distribution",
)
current_x_max = ax.get_xlim()[1]
current_y_max = ax.get_ylim()[1]
x_max = current_x_max if current_x_max > x_max else x_max
y_max = current_y_max if current_y_max > y_max else y_max
ax.label_outer()
ax.grid()
# Set x and y limits for the last axis. Since axes are shared, set to all
ax.set_xlim(x_min, x_max)
ax.set_ylim(y_min, y_max)
ax.xaxis.set_major_locator(
mtick.MaxNLocator(integer=True, nbins=5, prune="lower")
)
ax.yaxis.set_major_locator(
mtick.MaxNLocator(integer=True, nbins=4, prune="lower")
)
# Set title and axis labels for entire figure
handles, labels = ax.get_legend_handles_labels()
by_label = dict(zip(labels, handles))
fig.legend(
by_label.values(),
by_label.keys(),
loc="upper right",
)
fig.suptitle("Wind Gust Distribution")
fig.supxlabel(
f"Wind Gust Speed ({self.env_analysis.unit_system['wind_speed']})"
)
fig.supylabel("Probability")
plt.show()
return None
[docs]
def animate_wind_gust_distribution(self):
"""Animation of how the wind gust distribution varies throughout the day.
Each frame is a histogram of the wind gust distribution for a specific hour.
Returns
-------
HTML : IPython.core.display.HTML
The animation as an HTML object
"""
module = import_optional_dependency("IPython.display")
HTML = module.HTML # this is a class
# Gather animation data
wind_gusts = self.env_analysis.surface_wind_gust_by_hour
# Create animation
fig, ax = plt.subplots(dpi=200)
# Initialize animation artists: histogram and hour text
hist_bins = np.linspace(
0, np.ceil(self.env_analysis.record_max_wind_gust), 25
) # Fix bins edges
_, _, bar_container = plt.hist(
[],
bins=hist_bins,
alpha=0.2,
label="Wind Gust Speed Distribution",
)
(ln,) = plt.plot(
[],
[],
"r-",
linewidth=2,
label="Weibull Distribution",
)
tx = plt.text(
x=0.95,
y=0.95,
s="",
verticalalignment="top",
horizontalalignment="right",
transform=ax.transAxes,
fontsize=24,
)
# Define function to initialize animation
max_probability_density = 1.2 * max(
stats.weibull_min.pdf(
np.linspace(0, np.ceil(self.env_analysis.record_max_wind_gust), 50),
*stats.weibull_min.fit(
wind_gusts[self.env_analysis.hours[0]], loc=0, scale=1
),
)
)
def init():
ax.set_xlim(0, np.ceil(self.env_analysis.record_max_wind_gust))
ax.set_ylim(0, max_probability_density)
ax.set_xlabel(
f"Wind Gust Speed ({self.env_analysis.unit_system['wind_speed']})"
)
ax.set_ylabel("Probability")
ax.set_title("Wind Gust Distribution")
# ax.grid(True)
return (ln, *bar_container.patches, tx)
# Define function which sets each animation frame
def update(frame):
# Update histogram
data = frame[1]
hist, _ = np.histogram(data, hist_bins, density=True)
for count, rect in zip(hist, bar_container.patches):
rect.set_height(count)
# Update weibull distribution
c, loc, scale = stats.weibull_min.fit(data, loc=0, scale=1)
x = np.linspace(0, np.ceil(self.env_analysis.record_max_wind_gust), 50)
y = stats.weibull_min.pdf(x, c, loc, scale)
ln.set_data(x, y)
# Update hour text
tx.set_text(f"{float(frame[0]):05.2f}".replace(".", ":"))
return (ln, *bar_container.patches, tx)
for frame in wind_gusts.items():
update(frame)
animation = FuncAnimation(
fig,
update,
frames=wind_gusts.items(),
interval=750,
init_func=init,
blit=True,
)
plt.close(fig)
return HTML(animation.to_jshtml())
[docs]
def surface_wind_speed_distribution_grid(self, wind_speed_limit=False):
"""Plots shown in the animation of how the sustained surface wind speed
distribution varies throughout the day. The plots are histograms of the
wind speed distribution for a specific hour. The plots are arranged in a
grid like plot.
Parameters
----------
wind_speed_limit : bool, optional
Whether to plot the wind speed limit as a vertical line
Returns
-------
None
"""
# Gather animation data
average_wind_speed_at_given_hour = self.env_analysis.surface_wind_speed_by_hour
# Create grid of plots for each hour
n_cols, n_rows = find_two_closest_integers(len(self.env_analysis.hours))
fig = plt.figure(figsize=(n_cols * 2, n_rows * 2.2))
gs = fig.add_gridspec(n_rows, n_cols, hspace=0, wspace=0, left=0.12)
axs = gs.subplots(sharex=True, sharey=True)
x_min, x_max, y_min, y_max = 0, 0, 0, 0
for i, j in [(i, j) for i in range(n_rows) for j in range(n_cols)]:
hour = self.env_analysis.hours[i * n_cols + j]
ax = axs[i, j]
ax.set_title(f"{float(hour):05.2f}".replace(".", ":"), y=0.8)
ax.hist(
average_wind_speed_at_given_hour[hour],
bins=int(len(average_wind_speed_at_given_hour[hour]) ** 0.5),
density=True,
histtype="stepfilled",
alpha=0.2,
label="Wind Speed",
)
ax.autoscale(enable=True, axis="y", tight=True)
# Plot weibull distribution
c, loc, scale = stats.weibull_min.fit(
average_wind_speed_at_given_hour[hour], loc=0, scale=1
)
x = np.linspace(
0,
np.ceil(self.env_analysis.max_surface_10m_wind_speed_list),
100,
)
ax.plot(
x,
stats.weibull_min.pdf(x, c, loc, scale),
"r-",
linewidth=2,
label="Weibull Distribution",
)
current_x_max = ax.get_xlim()[1]
current_y_max = ax.get_ylim()[1]
x_max = current_x_max if current_x_max > x_max else x_max
y_max = current_y_max if current_y_max > y_max else y_max
ax.label_outer()
ax.grid()
# Set x and y limits for the last axis. Since axes are shared, set to all
ax.set_xlim(x_min, x_max)
ax.set_ylim(y_min, y_max)
ax.xaxis.set_major_locator(
mtick.MaxNLocator(integer=True, nbins=5, prune="lower")
)
ax.yaxis.set_major_locator(
mtick.MaxNLocator(integer=True, nbins=4, prune="lower")
)
if wind_speed_limit:
for i, j in [(i, j) for i in range(n_rows) for j in range(n_cols)]:
# Clear Sky Range Altitude Limits j]
ax = axs[i, j]
ax.vlines(
convert_units(
20, "mph", self.env_analysis.unit_system["wind_speed"]
),
0,
ax.get_ylim()[1],
"g",
(0, (15, 5, 2, 5)),
label="Wind Speed Limits",
)
# Set title and axis labels for entire figure
handles, labels = ax.get_legend_handles_labels()
by_label = dict(zip(labels, handles))
fig.legend(
by_label.values(),
by_label.keys(),
loc="upper right",
)
fig.suptitle("Sustained Surface Wind Speed Distributions")
fig.supxlabel(
f"Sustained Surface Wind Speed ({self.env_analysis.unit_system['wind_speed']})"
)
fig.supylabel("Probability")
plt.show()
return None
[docs]
def animate_surface_wind_speed_distribution(self, wind_speed_limit=False):
"""Animation of how the sustained surface wind speed distribution varies
throughout the day. Each frame is a histogram of the wind speed distribution
for a specific hour.
Parameters
----------
wind_speed_limit : bool, optional
Whether to plot the wind speed limit as a vertical line
Returns
-------
HTML : IPython.core.display.HTML
"""
module = import_optional_dependency("IPython.display")
HTML = module.HTML # this is a class
# Gather animation data
surface_wind_speeds_at_given_hour = self.env_analysis.surface_wind_speed_by_hour
# Create animation
fig, ax = plt.subplots(dpi=200)
# Initialize animation artists: histogram and hour text
hist_bins = np.linspace(
0, np.ceil(self.env_analysis.record_max_surface_10m_wind_speed), 25
) # Fix bins edges
_, _, bar_container = plt.hist(
[],
bins=hist_bins,
alpha=0.2,
label="Sustained Surface Wind Speed Distribution",
)
(ln,) = plt.plot(
[],
[],
"r-",
linewidth=2,
label="Weibull Distribution",
)
tx = plt.text(
x=0.95,
y=0.95,
s="",
verticalalignment="top",
horizontalalignment="right",
transform=ax.transAxes,
fontsize=24,
)
maximum_probability_density = 1.2 * max(
stats.weibull_min.pdf(
np.linspace(
0,
np.ceil(self.env_analysis.record_max_surface_10m_wind_speed),
100,
),
*stats.weibull_min.fit(
surface_wind_speeds_at_given_hour[self.env_analysis.hours[0]],
loc=0,
scale=1,
),
)
)
# Define function to initialize animation
def init():
ax.set_xlim(0, np.ceil(self.env_analysis.record_max_surface_10m_wind_speed))
ax.set_ylim(0, maximum_probability_density)
ax.set_xlabel(
f"Sustained Surface Wind Speed ({self.env_analysis.unit_system['wind_speed']})"
)
ax.set_ylabel("Probability")
ax.set_title("Sustained Surface Wind Distribution")
if wind_speed_limit:
ax.vlines(
convert_units(
20, "mph", self.env_analysis.unit_system["wind_speed"]
),
0,
0.3,
"g",
(0, (15, 5, 2, 5)),
label="Wind Speed Limit",
)
return (ln, *bar_container.patches, tx)
# Define function which sets each animation frame
def update(frame):
# Update histogram
data = frame[1]
hist, _ = np.histogram(data, hist_bins, density=True)
for count, rect in zip(hist, bar_container.patches):
rect.set_height(count)
# Update weibull distribution
c, loc, scale = stats.weibull_min.fit(data, loc=0, scale=1)
x = np.linspace(
0,
np.ceil(self.env_analysis.record_max_surface_10m_wind_speed),
100,
)
y = stats.weibull_min.pdf(x, c, loc, scale)
ln.set_data(x, y)
# Update hour text
tx.set_text(f"{float(frame[0]):05.2f}".replace(".", ":"))
return (ln, *bar_container.patches, tx)
for frame in surface_wind_speeds_at_given_hour.items():
update(frame)
animation = FuncAnimation(
fig,
update,
frames=surface_wind_speeds_at_given_hour.items(),
interval=750,
init_func=init,
blit=True,
)
plt.close(fig)
return HTML(animation.to_jshtml())
[docs]
def wind_speed_profile_grid(self, clear_range_limits=False):
"""Creates a grid of plots with the wind profile over the average day.
Each subplot represents a different hour of the day.
Parameters
----------
clear_range_limits : bool, optional
Whether to clear the sky range limits or not, by default False
Returns
-------
None
"""
# Create grid of plots for each hour
n_cols, n_rows = find_two_closest_integers(len(self.env_analysis.hours))
fig = plt.figure(figsize=(n_cols * 2, n_rows * 2.2))
gs = fig.add_gridspec(n_rows, n_cols, hspace=0, wspace=0, left=0.12)
axs = gs.subplots(sharex=True, sharey=True)
x_min, x_max, y_min, y_max = 0, 0, np.inf, 0
for i, j in [(i, j) for i in range(n_rows) for j in range(n_cols)]:
hour = self.env_analysis.hours[i * n_cols + j]
ax = axs[i, j]
ax.plot(*self.env_analysis.average_wind_speed_profile_by_hour[hour], "r-")
ax.set_title(f"{float(hour):05.2f}".replace(".", ":"), y=0.8)
ax.autoscale(enable=True, axis="y", tight=True)
current_x_max = ax.get_xlim()[1]
current_y_min, current_y_max = ax.get_ylim()
x_max = current_x_max if current_x_max > x_max else x_max
y_max = current_y_max if current_y_max > y_max else y_max
y_min = current_y_min if current_y_min < y_min else y_min
if self.env_analysis.forecast:
forecast = self.env_analysis.forecast
y = self.env_analysis.average_wind_speed_profile_by_hour[hour][1]
x = forecast[hour].wind_speed.get_value(y) * convert_units(
1, "m/s", self.env_analysis.unit_system["wind_speed"]
)
ax.plot(x, y, "b--")
ax.label_outer()
ax.grid()
# Set x and y limits for the last axis. Since axes are shared, set to all
ax.set_xlim(x_min, x_max)
ax.set_ylim(y_min, y_max)
ax.xaxis.set_major_locator(
mtick.MaxNLocator(integer=True, nbins=5, prune="lower")
)
ax.yaxis.set_major_locator(
mtick.MaxNLocator(integer=True, nbins=4, prune="lower")
)
if clear_range_limits:
for i, j in [(i, j) for i in range(n_rows) for j in range(n_cols)]:
# Clear Sky Range Altitude Limits
ax = axs[i, j]
ax.fill_between(
[x_min, x_max],
0.7
* convert_units(
10000, "ft", self.env_analysis.unit_system["length"]
),
1.3
* convert_units(
10000, "ft", self.env_analysis.unit_system["length"]
),
color="g",
alpha=0.2,
label=f"10,000 {self.env_analysis.unit_system['length']} ± 30%",
)
ax.fill_between(
[x_min, x_max],
0.7
* convert_units(
30000, "ft", self.env_analysis.unit_system["length"]
),
1.3
* convert_units(
30000, "ft", self.env_analysis.unit_system["length"]
),
color="g",
alpha=0.2,
label=f"30,000 {self.env_analysis.unit_system['length']} ± 30%",
)
# Set title and axis labels for entire figure
fig.suptitle("Average Wind Profile")
fig.supxlabel(f"Wind speed ({self.env_analysis.unit_system['wind_speed']})")
fig.supylabel(f"Altitude AGL ({self.env_analysis.unit_system['length']})")
plt.show()
return None
[docs]
def wind_heading_profile_grid(self, clear_range_limits=False):
"""Creates a grid of plots with the wind heading profile over the
average day. Each subplot represents a different hour of the day.
Parameters
----------
clear_range_limits : bool, optional
Whether to clear the sky range limits or not, by default False. This
is useful when the launch site is constrained in terms or altitude.
Returns
-------
None
"""
# Create grid of plots for each hour
n_cols, n_rows = find_two_closest_integers(len(self.env_analysis.hours))
fig = plt.figure(figsize=(n_cols * 2, n_rows * 2.2))
gs = fig.add_gridspec(n_rows, n_cols, hspace=0, wspace=0, left=0.12)
axs = gs.subplots(sharex=True, sharey=True)
_, y_min, y_max = 0, np.inf, 0
for i, j in [(i, j) for i in range(n_rows) for j in range(n_cols)]:
hour = self.env_analysis.hours[i * n_cols + j]
ax = axs[i, j]
ax.plot(
*self.env_analysis.average_wind_heading_profile_by_hour[hour],
"r-",
)
ax.set_title(f"{float(hour):05.2f}".replace(".", ":"), y=0.8)
ax.autoscale(enable=True, axis="y", tight=True)
current_y_min, current_y_max = ax.get_ylim()
y_max = current_y_max if current_y_max > y_max else y_max
y_min = current_y_min if current_y_min < y_min else y_min
ax.label_outer()
ax.grid()
# Set x and y limits for the last axis. Since axes are shared, set to all
ax.set_xlim(0, 360)
ax.set_ylim(y_min, y_max)
ax.xaxis.set_major_locator(
mtick.MaxNLocator(integer=True, nbins=5, prune="lower")
)
ax.yaxis.set_major_locator(
mtick.MaxNLocator(integer=True, nbins=4, prune="lower")
)
if clear_range_limits:
for i, j in [(i, j) for i in range(n_rows) for j in range(n_cols)]:
# Clear Sky range limits
ax = axs[i, j]
ax.fill_between(
[0, 360],
0.7
* convert_units(
10000, "ft", self.env_analysis.unit_system["length"]
),
1.3
* convert_units(
10000, "ft", self.env_analysis.unit_system["length"]
),
color="g",
alpha=0.2,
label=f"10,000 {self.env_analysis.unit_system['length']} ± 30%",
)
ax.fill_between(
[0, 360],
0.7
* convert_units(
30000, "ft", self.env_analysis.unit_system["length"]
),
1.3
* convert_units(
30000, "ft", self.env_analysis.unit_system["length"]
),
color="g",
alpha=0.2,
label=f"30,000 {self.env_analysis.unit_system['length']} ± 30%",
)
# Set title and axis labels for entire figure
fig.suptitle("Average Wind Heading Profile")
fig.supxlabel(f"Wind heading ({self.env_analysis.unit_system['angle']})")
fig.supylabel(f"Altitude AGL ({self.env_analysis.unit_system['length']})")
plt.show()
return None
[docs]
def animate_wind_speed_profile(self, clear_range_limits=False):
"""Animation of how wind profile evolves throughout an average day.
Parameters
----------
clear_range_limits : bool, optional
Whether to clear the sky range limits or not, by default False. This
is useful when the launch site is constrained in terms or altitude.
"""
module = import_optional_dependency("IPython.display")
HTML = module.HTML # this is a class
# Create animation
fig, ax = plt.subplots(dpi=200)
# Initialize animation artists: curve and hour text
(ln,) = plt.plot([], [], "r-")
tx = plt.text(
x=0.95,
y=0.95,
s="",
verticalalignment="top",
horizontalalignment="right",
transform=ax.transAxes,
fontsize=24,
)
# Define function to initialize animation
def init():
ax.set_xlim(0, self.env_analysis.max_average_wind_speed_at_altitude + 5)
ax.set_ylim(*self.env_analysis.altitude_AGL_range)
ax.set_xlabel(f"Wind Speed ({self.env_analysis.unit_system['wind_speed']})")
ax.set_ylabel(f"Altitude AGL ({self.env_analysis.unit_system['length']})")
ax.set_title("Average Wind Profile")
ax.grid(True)
return ln, tx
# Define function which sets each animation frame
def update(frame):
x = frame[1][0]
y = frame[1][1]
ln.set_data(x, y)
tx.set_text(f"{float(frame[0]):05.2f}".replace(".", ":"))
return ln, tx
animation = FuncAnimation(
fig,
update,
frames=self.env_analysis.average_wind_speed_profile_by_hour.items(),
interval=1000,
init_func=init,
blit=True,
)
if clear_range_limits:
# Clear sky range limits
ax.fill_between(
[0, self.env_analysis.max_average_wind_speed_at_altitude + 5],
0.7
* convert_units(10000, "ft", self.env_analysis.unit_system["length"]),
1.3
* convert_units(10000, "ft", self.env_analysis.unit_system["length"]),
color="g",
alpha=0.2,
label=f"10,000 {self.env_analysis.unit_system['length']} ± 30%",
)
ax.fill_between(
[0, self.env_analysis.max_average_wind_speed_at_altitude + 5],
0.7
* convert_units(30000, "ft", self.env_analysis.unit_system["length"]),
1.3
* convert_units(30000, "ft", self.env_analysis.unit_system["length"]),
color="g",
alpha=0.2,
label=f"30,000 {self.env_analysis.unit_system['length']} ± 30%",
)
fig.legend(loc="upper right")
plt.close(fig)
return HTML(animation.to_jshtml())
[docs]
def animate_wind_heading_profile(self, clear_range_limits=False):
"""Animation of how the wind heading profile evolves throughout an
average day. Each frame is a different hour of the day.
Parameters
----------
clear_range_limits : bool, optional
Whether to clear the sky range limits or not, by default False. This
is useful when the launch site is constrained in terms or altitude.
"""
module = import_optional_dependency("IPython.display")
HTML = module.HTML # this is a class
# Create animation
fig, ax = plt.subplots(dpi=200)
# Initialize animation artists: curve and hour text
(ln,) = plt.plot([], [], "r-")
tx = plt.text(
x=0.95,
y=0.95,
s="",
verticalalignment="top",
horizontalalignment="right",
transform=ax.transAxes,
fontsize=24,
)
# Define function to initialize animation
def init():
ax.set_xlim(0, 360)
ax.set_ylim(*self.env_analysis.altitude_AGL_range)
ax.set_xlabel(f"Wind Heading ({self.env_analysis.unit_system['angle']})")
ax.set_ylabel(f"Altitude AGL ({self.env_analysis.unit_system['length']})")
ax.set_title("Average Wind Heading Profile")
ax.grid(True)
return ln, tx
# Define function which sets each animation frame
def update(frame):
x = frame[1][0]
y = frame[1][1]
ln.set_data(x, y)
tx.set_text(f"{float(frame[0]):05.2f}".replace(".", ":"))
return ln, tx
animation = FuncAnimation(
fig,
update,
frames=self.env_analysis.average_wind_heading_profile_by_hour.items(),
interval=1000,
init_func=init,
blit=True,
)
if clear_range_limits:
# Clear sky range limits
ax.fill_between(
[0, self.env_analysis.max_average_wind_speed_at_altitude + 5],
0.7
* convert_units(10000, "ft", self.env_analysis.unit_system["length"]),
1.3
* convert_units(10000, "ft", self.env_analysis.unit_system["length"]),
color="g",
alpha=0.2,
label=f"10,000 {self.env_analysis.unit_system['length']} ± 30%",
)
ax.fill_between(
[0, self.env_analysis.max_average_wind_speed_at_altitude + 5],
0.7
* convert_units(30000, "ft", self.env_analysis.unit_system["length"]),
1.3
* convert_units(30000, "ft", self.env_analysis.unit_system["length"]),
color="g",
alpha=0.2,
label=f"30,000 {self.env_analysis.unit_system['length']} ± 30%",
)
fig.legend(loc="upper right")
plt.close(fig)
return HTML(animation.to_jshtml())
[docs]
def all_animations(self):
"""Plots all the available animations together
Returns
-------
None"""
self.animate_average_wind_rose()
self.animate_wind_gust_distribution()
self.animate_surface_wind_speed_distribution()
self.animate_wind_heading_profile(clear_range_limits=True)
self.animate_wind_speed_profile()
return None
[docs]
def all_plots(self):
"""Plots all the available plots together, this avoids having animations
Returns
-------
None
"""
self.wind_gust_distribution()
self.surface10m_wind_speed_distribution()
self.average_surface_temperature_evolution()
self.average_surface10m_wind_speed_evolution()
self.average_surface100m_wind_speed_evolution()
self.average_wind_speed_profile()
self.average_wind_heading_profile()
self.average_pressure_profile()
self.average_wind_rose_grid()
self.wind_gust_distribution_grid()
self.surface_wind_speed_distribution_grid()
self.wind_speed_profile_grid()
self.wind_heading_profile_grid()
return None
[docs]
def info(self):
"""Plots only the most important plots together. This method simply
invokes the `wind_gust_distribution`, `average_wind_speed_profile`,
`wind_speed_profile_grid` and `wind_heading_profile_grid`
methods.
Returns
-------
None
"""
self.wind_gust_distribution()
self.average_wind_speed_profile()
self.wind_speed_profile_grid()
self.wind_heading_profile_grid()
return None
[docs]
def all(self):
"""Plots all the available plots and animations together. This method
simply invokes the `all_plots` and `all_animations` methods.
Returns
-------
None
"""
self.all_plots()
self.all_animations()
return None