This notebook demonstrates how to retrieve weather forecasts along a route with the Spire Weather API.
import os
import requests
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.environ.get('SPIRE_API_KEY')
BASE_URL = 'https://api.wx.spire.com'
HEADERS = {'spire-api-key': API_KEY}Define a Route¶
A route consists of waypoints with latitude, longitude, and expected arrival times.
# Example: Transatlantic shipping route from New York to Southampton
base_time = datetime.utcnow().replace(hour=12, minute=0, second=0, microsecond=0)
route = [
{
'name': 'New York',
'lat': 40.6892,
'lon': -74.0445,
'time': base_time.isoformat() + 'Z'
},
{
'name': 'Mid-Atlantic 1',
'lat': 42.0,
'lon': -60.0,
'time': (base_time + timedelta(hours=36)).isoformat() + 'Z'
},
{
'name': 'Mid-Atlantic 2',
'lat': 45.0,
'lon': -40.0,
'time': (base_time + timedelta(hours=72)).isoformat() + 'Z'
},
{
'name': 'Mid-Atlantic 3',
'lat': 48.0,
'lon': -20.0,
'time': (base_time + timedelta(hours=108)).isoformat() + 'Z'
},
{
'name': 'Southampton',
'lat': 50.9097,
'lon': -1.4044,
'time': (base_time + timedelta(hours=144)).isoformat() + 'Z'
}
]
print("Route waypoints:")
for wp in route:
print(f" {wp['name']}: ({wp['lat']:.2f}, {wp['lon']:.2f}) at {wp['time']}")Request Route Forecast¶
# Prepare route data for API (without names)
route_data = {
'route': [
{'lat': wp['lat'], 'lon': wp['lon'], 'time': wp['time']}
for wp in route
]
}
# Make the POST request
response = requests.post(
f'{BASE_URL}/forecast/route',
headers=HEADERS,
params={'bundles': 'basic'},
json=route_data
)
response.raise_for_status()
data = response.json()
print(f"Received {len(data['data'])} forecast points")Convert to DataFrame¶
def route_to_dataframe(response_data, waypoints):
"""Convert route forecast response to DataFrame with waypoint names."""
records = []
for i, item in enumerate(response_data['data']):
# Match with waypoint if possible
name = waypoints[i]['name'] if i < len(waypoints) else f"Point {i}"
record = {
'waypoint': name,
'lat': item['location']['coordinates']['lat'],
'lon': item['location']['coordinates']['lon'],
'valid_time': item['times']['valid_time'],
**item['values']
}
records.append(record)
df = pd.DataFrame(records)
df['valid_time'] = pd.to_datetime(df['valid_time'])
return df
df = route_to_dataframe(data, route)
print(df.head())Add Derived Variables¶
# Temperature conversion
if 'air_temperature' in df.columns:
df['temp_celsius'] = df['air_temperature'] - 273.15
# Wind speed and direction
if 'northward_wind' in df.columns and 'eastward_wind' in df.columns:
df['wind_speed'] = np.sqrt(df['northward_wind']**2 + df['eastward_wind']**2)
df['wind_direction'] = (270 - np.degrees(np.arctan2(df['northward_wind'],
df['eastward_wind']))) % 360
print(df[['waypoint', 'valid_time', 'temp_celsius', 'wind_speed']].head())Plot Route on Map¶
try:
import cartopy.crs as ccrs
import cartopy.feature as cfeature
fig, ax = plt.subplots(
figsize=(12, 8),
subplot_kw={'projection': ccrs.PlateCarree()}
)
ax.add_feature(cfeature.LAND, alpha=0.3)
ax.add_feature(cfeature.OCEAN, alpha=0.3)
ax.add_feature(cfeature.COASTLINE)
ax.add_feature(cfeature.BORDERS, linestyle=':')
# Plot route
ax.plot(df['lon'], df['lat'], 'b-o', transform=ccrs.PlateCarree(),
linewidth=2, markersize=8, label='Route')
# Add labels
for _, row in df.iterrows():
ax.annotate(row['waypoint'],
xy=(row['lon'], row['lat']),
xytext=(5, 5), textcoords='offset points',
fontsize=9, transform=ccrs.PlateCarree())
ax.set_extent([-80, 5, 35, 55])
ax.gridlines(draw_labels=True)
ax.set_title('Transatlantic Route')
plt.show()
except ImportError:
print("Cartopy not installed. Install with: conda install -c conda-forge cartopy")
# Simple fallback plot
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(df['lon'], df['lat'], 'b-o', linewidth=2, markersize=8)
for _, row in df.iterrows():
ax.annotate(row['waypoint'], xy=(row['lon'], row['lat']),
xytext=(5, 5), textcoords='offset points')
ax.set_xlabel('Longitude')
ax.set_ylabel('Latitude')
ax.set_title('Route')
plt.show()Weather Along Route¶
fig, axes = plt.subplots(2, 1, figsize=(12, 8), sharex=True)
# Temperature
axes[0].plot(df['waypoint'], df['temp_celsius'], 'r-o', linewidth=2, markersize=8)
axes[0].set_ylabel('Temperature (°C)')
axes[0].grid(True, alpha=0.3)
axes[0].set_title('Weather Conditions Along Route')
# Wind Speed
axes[1].plot(df['waypoint'], df['wind_speed'], 'g-o', linewidth=2, markersize=8)
axes[1].set_ylabel('Wind Speed (m/s)')
axes[1].grid(True, alpha=0.3)
axes[1].set_xlabel('Waypoint')
plt.tight_layout()
plt.show()Maritime Route with Waves¶
If you have maritime bundle access:
try:
# Request with maritime bundle
response = requests.post(
f'{BASE_URL}/forecast/route',
headers=HEADERS,
params={'bundles': 'basic,maritime'},
json=route_data
)
response.raise_for_status()
maritime_data = response.json()
maritime_df = route_to_dataframe(maritime_data, route)
if 'significant_wave_height' in maritime_df.columns:
print("Maritime data available!")
print(maritime_df[['waypoint', 'significant_wave_height']].head())
else:
print("Maritime variables not in response. Check subscription.")
except requests.exceptions.HTTPError as e:
print(f"Maritime bundle may not be available: {e}")Route Summary Table¶
# Create summary table
summary_cols = ['waypoint', 'valid_time', 'temp_celsius', 'wind_speed']
if 'relative_humidity' in df.columns:
summary_cols.append('relative_humidity')
summary = df[summary_cols].copy()
summary['temp_celsius'] = summary['temp_celsius'].round(1)
summary['wind_speed'] = summary['wind_speed'].round(1)
print("Route Weather Summary")
print("=" * 60)
print(summary.to_string(index=False))Next Steps¶
Data Visualization - Advanced visualizations
Maritime Insights API - Maritime-specific insights