Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

This notebook demonstrates how to retrieve and work with point forecast data from the Spire Weather API.

import os
import requests
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime
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}

Basic Point Forecast Request

# Request a forecast for New York City
params = {
    'lat': 40.7128,
    'lon': -74.0060,
    'bundles': 'basic'
}

response = requests.get(
    f'{BASE_URL}/forecast/point',
    headers=HEADERS,
    params=params
)
response.raise_for_status()
data = response.json()

print(f"Received {len(data['data'])} forecast times")

Convert to DataFrame

def forecast_to_dataframe(response_data):
    """Convert API response to pandas DataFrame."""
    records = []
    for item in response_data['data']:
        record = {
            'valid_time': item['times']['valid_time'],
            'issuance_time': item['times']['issuance_time'],
            'lat': item['location']['coordinates']['lat'],
            'lon': item['location']['coordinates']['lon'],
            **item['values']
        }
        records.append(record)
    
    df = pd.DataFrame(records)
    df['valid_time'] = pd.to_datetime(df['valid_time'])
    df['issuance_time'] = pd.to_datetime(df['issuance_time'])
    return df

df = forecast_to_dataframe(data)
print(df.head())

Available Variables

# List available variables
value_columns = [col for col in df.columns if col not in 
                 ['valid_time', 'issuance_time', 'lat', 'lon']]

print("Available forecast variables:")
for col in value_columns:
    print(f"  - {col}")

Unit Conversion

The API returns SI units by default. Let’s add some helpful conversions:

# Temperature: Kelvin to Celsius
if 'air_temperature' in df.columns:
    df['temperature_celsius'] = df['air_temperature'] - 273.15
    df['temperature_fahrenheit'] = df['temperature_celsius'] * 9/5 + 32

# Wind: m/s to mph
if 'northward_wind' in df.columns and 'eastward_wind' in df.columns:
    import numpy as np
    df['wind_speed_ms'] = np.sqrt(df['northward_wind']**2 + df['eastward_wind']**2)
    df['wind_speed_mph'] = df['wind_speed_ms'] * 2.237
    df['wind_direction'] = (270 - np.degrees(np.arctan2(df['northward_wind'], 
                                                         df['eastward_wind']))) % 360

print(df[['valid_time', 'temperature_celsius', 'temperature_fahrenheit', 
          'wind_speed_mph', 'wind_direction']].head())

Plot Temperature Forecast

fig, ax = plt.subplots(figsize=(12, 5))

ax.plot(df['valid_time'], df['temperature_fahrenheit'], 'b-', linewidth=2)
ax.set_xlabel('Time')
ax.set_ylabel('Temperature (°F)')
ax.set_title('Temperature Forecast - New York City')
ax.grid(True, alpha=0.3)

plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

Multi-Variable Plot

fig, axes = plt.subplots(3, 1, figsize=(12, 10), sharex=True)

# Temperature
axes[0].plot(df['valid_time'], df['temperature_fahrenheit'], 'r-', linewidth=2)
axes[0].set_ylabel('Temperature (°F)')
axes[0].grid(True, alpha=0.3)

# Wind Speed
axes[1].plot(df['valid_time'], df['wind_speed_mph'], 'g-', linewidth=2)
axes[1].set_ylabel('Wind Speed (mph)')
axes[1].grid(True, alpha=0.3)

# Humidity
if 'relative_humidity' in df.columns:
    axes[2].plot(df['valid_time'], df['relative_humidity'], 'b-', linewidth=2)
    axes[2].set_ylabel('Relative Humidity (%)')
    axes[2].set_ylim(0, 100)
    axes[2].grid(True, alpha=0.3)

axes[2].set_xlabel('Time')
plt.suptitle('Weather Forecast - New York City', fontsize=14)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

Compare Multiple Locations

locations = {
    'New York': (40.7128, -74.0060),
    'Miami': (25.7617, -80.1918),
    'Chicago': (41.8781, -87.6298),
    'Denver': (39.7392, -104.9903)
}

location_data = {}

for city, (lat, lon) in locations.items():
    params = {'lat': lat, 'lon': lon, 'bundles': 'basic'}
    response = requests.get(f'{BASE_URL}/forecast/point', headers=HEADERS, params=params)
    response.raise_for_status()
    location_data[city] = forecast_to_dataframe(response.json())
    # Add temperature conversions
    if 'air_temperature' in location_data[city].columns:
        location_data[city]['temperature_f'] = (location_data[city]['air_temperature'] - 273.15) * 9/5 + 32

print(f"Loaded data for {len(location_data)} cities")
fig, ax = plt.subplots(figsize=(12, 6))

for city, city_df in location_data.items():
    ax.plot(city_df['valid_time'], city_df['temperature_f'], label=city, linewidth=2)

ax.set_xlabel('Time')
ax.set_ylabel('Temperature (°F)')
ax.set_title('Temperature Forecast Comparison')
ax.legend()
ax.grid(True, alpha=0.3)

plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

Using US Units Directly

# Request data in US customary units
params = {
    'lat': 40.7128,
    'lon': -74.0060,
    'bundles': 'basic',
    'unit_system': 'us'
}

response = requests.get(f'{BASE_URL}/forecast/point', headers=HEADERS, params=params)
response.raise_for_status()
us_data = response.json()

print(f"Unit system: {us_data['meta']['unit_system']}")
print(f"Sample temperature: {us_data['data'][0]['values'].get('air_temperature', 'N/A')}")

Summary Statistics

# Calculate forecast statistics
print("New York Forecast Summary")
print("=" * 40)

nyc_df = location_data['New York']

print(f"Forecast period: {nyc_df['valid_time'].min()} to {nyc_df['valid_time'].max()}")
print()
print("Temperature (°F):")
print(f"  Min: {nyc_df['temperature_f'].min():.1f}")
print(f"  Max: {nyc_df['temperature_f'].max():.1f}")
print(f"  Mean: {nyc_df['temperature_f'].mean():.1f}")

Next Steps