En route
Runnable examples for en-route weather inside a geographic bounding box: Winds Aloft, PIREPs, and AIRMET/SIGMET. For airport METAR/TAF workflows, see At the airport.
| # | Question | Data used |
|---|---|---|
| 7 | Cruise wind at FL180 near KJFK departure (US regional bbox)? | Winds Aloft |
| 8 | Any urgent pilot reports (UUA) along the route in the last 3 hours? | PIREPs |
| 9 | Active AIRMET polygons intersecting the route bbox? | AIRMET/SIGMET |
Requirements
x-api-key on every request (direct subscription). Learn more →Install
The scripts below use requests for API calls and rich for terminal output. Install once:
pip install requests rich7. Cruise wind at FL180 (winds aloft)
Question: Near KJFK departure, what is the nearest winds-aloft level to FL180?
Winds Aloft is US-only. For long routes, query regional bboxes along the US segment (a coast-to-coast box is slow and often hits 503). Below: a tight box around New York.
import os
import requests
from rich.console import Console
from rich.table import Table
console = Console()
HEADERS = {
"X-RapidAPI-Key": os.getenv("RAPIDAPI_KEY", "YOUR_RAPIDAPI_KEY"),
"X-RapidAPI-Host": "skylink-api.p.rapidapi.com",
}
BASE = "https://skylink-api.p.rapidapi.com"
# Regional box around KJFK - reliable for FB Winds (US only)
NYC_BBOX = "38,-76,42,-71"
TARGET_ALT_FT = 18_000
def fetch_winds_aloft(bbox: str, forecast: int = 12) -> dict | None:
r = requests.get(
f"{BASE}/weather/winds-aloft",
headers=HEADERS,
params={"bbox": bbox, "forecast": forecast, "level": "low"},
timeout=(10, 20),
)
if r.status_code in (404, 503):
return None
r.raise_for_status()
return r.json()
def print_cruise_wind(bbox: str, label: str, target_ft: int = TARGET_ALT_FT) -> None:
data = fetch_winds_aloft(bbox)
if not data:
console.print("[yellow]Winds aloft unavailable or no stations - retry or widen bbox.[/]")
return
if not data.get("stations"):
console.print("[yellow]No FB winds stations in this bbox.[/]")
return
station = data["stations"][0]
levels = station.get("winds") or []
if not levels:
console.print(f"[yellow]Station {station['station']} returned no wind levels.[/]")
return
best = min(levels, key=lambda w: abs(w["altitude_ft"] - target_ft))
table = Table(title=f"Cruise wind near FL{target_ft // 100} - {label}")
table.add_column("Field", style="cyan")
table.add_column("Value", justify="right")
table.add_row("Bbox", bbox)
table.add_row("Station", station["station"])
table.add_row("Level used", f"{best['altitude_ft']} ft MSL")
table.add_row("Direction", f"{best.get('wind_direction') or '-'}° true")
table.add_row("Speed", f"{best.get('wind_speed_kt') or '-'} kt")
temp = best.get("temperature_c")
table.add_row("Temperature", f"{temp} °C" if temp is not None else "not reported")
table.add_row("Valid time", data.get("valid_time") or "-")
console.print(table)
if __name__ == "__main__":
print_cruise_wind(NYC_BBOX, "KJFK departure area")Output: Table with nearest wind level to your target altitude from the first station in the bbox.
8. Urgent PIREPs along the route
Question: Were any UUA (urgent) pilot reports filed in the route bbox in the last 3 hours?
PIREPs are sparse - an empty list is common. When present, surface UUA above routine UA reports.
import os
import requests
from rich.console import Console
from rich.panel import Panel
console = Console()
HEADERS = {
"X-RapidAPI-Key": os.getenv("RAPIDAPI_KEY", "YOUR_RAPIDAPI_KEY"),
"X-RapidAPI-Host": "skylink-api.p.rapidapi.com",
}
BASE = "https://skylink-api.p.rapidapi.com"
KJFK = (40.6413, -73.7781)
KLAX = (33.9425, -118.4081)
def route_bbox(dep, arr, pad_deg: float = 2.0) -> str:
dep_lat, dep_lon = dep
arr_lat, arr_lon = arr
return (
f"{min(dep_lat, arr_lat) - pad_deg},{min(dep_lon, arr_lon) - pad_deg},"
f"{max(dep_lat, arr_lat) + pad_deg},{max(dep_lon, arr_lon) + pad_deg}"
)
def fetch_pireps(bbox: str, hours: int = 3) -> dict:
r = requests.get(
f"{BASE}/weather/pireps",
headers=HEADERS,
params={"bbox": bbox, "hours": hours},
timeout=(10, 20),
)
r.raise_for_status()
return r.json()
def print_urgent_pireps(dep, arr) -> None:
bbox = route_bbox(dep, arr)
data = fetch_pireps(bbox, hours=3)
reports = data.get("reports") or []
urgent = [r for r in reports if r.get("report_type") == "UUA"]
routine = [r for r in reports if r.get("report_type") != "UUA"]
if not reports:
console.print(Panel(
f"No PIREPs in bbox {bbox} (last 3 h).\n"
"This is normal on quiet routes - not an error.",
title="PIREPs - none filed",
style="blue",
))
return
lines = []
for r in urgent + routine[:5]:
tag = "[red]UUA[/]" if r.get("report_type") == "UUA" else "UA"
turb = r.get("turbulence") or "-"
icing = r.get("icing") or "-"
lines.append(f"{tag} {r.get('location') or '?'} @ {r.get('altitude') or '?'} - turb: {turb}, ice: {icing}")
console.print(Panel(
"\n".join(lines),
title=f"PIREPs in route bbox ({data['total']} total, {len(urgent)} urgent)",
))
if __name__ == "__main__":
print_urgent_pireps(KJFK, KLAX)Output: Panel listing urgent PIREPs first, then up to five routine reports; or a clear "none filed" message.
9. Active AIRMETs on the route
Question: Do any AIRMET advisories intersect the route bbox right now?
Use type=airmet for dispatch-style IFR/turbulence/icing bulletins. Draw observation.coords on a map when present.
import os
import requests
from rich.console import Console
from rich.table import Table
console = Console()
HEADERS = {
"X-RapidAPI-Key": os.getenv("RAPIDAPI_KEY", "YOUR_RAPIDAPI_KEY"),
"X-RapidAPI-Host": "skylink-api.p.rapidapi.com",
}
BASE = "https://skylink-api.p.rapidapi.com"
KJFK = (40.6413, -73.7781)
KLAX = (33.9425, -118.4081)
def route_bbox(dep, arr, pad_deg: float = 2.0) -> str:
dep_lat, dep_lon = dep
arr_lat, arr_lon = arr
return (
f"{min(dep_lat, arr_lat) - pad_deg},{min(dep_lon, arr_lon) - pad_deg},"
f"{max(dep_lat, arr_lat) + pad_deg},{max(dep_lon, arr_lon) + pad_deg}"
)
def fetch_airsigmets(bbox: str, advisory_type: str = "airmet") -> dict:
r = requests.get(
f"{BASE}/weather/airsigmet",
headers=HEADERS,
params={"bbox": bbox, "type": advisory_type},
timeout=(10, 25),
)
r.raise_for_status()
return r.json()
def print_route_airsigmets(dep, arr) -> None:
bbox = route_bbox(dep, arr)
data = fetch_airsigmets(bbox)
reports = data.get("reports") or []
if not reports:
console.print(f"[green]No active AIRMETs intersect bbox {bbox} - fair weather or outside advisory area.[/]")
return
table = Table(title=f"Active AIRMETs - {len(reports)} in bbox")
table.add_column("Type")
table.add_column("Valid")
table.add_column("Hazard")
table.add_column("Polygon points")
for r in reports:
obs = r.get("observation") or {}
hazard = obs.get("type") or r.get("report_type") or "-"
floor = obs.get("floor") or "-"
ceiling = obs.get("ceiling") or "-"
coords = obs.get("coords") or []
valid = f"{r.get('start_time') or '?'} → {r.get('end_time') or '?'}"
table.add_row(
r.get("report_type") or "AIRMET",
valid,
f"{hazard} ({floor}–{ceiling})",
str(len(coords)),
)
console.print(table)
console.print("[dim]Render observation.coords as map polygons; hide expired advisories past end_time.[/dim]")
if __name__ == "__main__":
print_route_airsigmets(KJFK, KLAX)Output: Table of active AIRMETs with validity window, hazard type, and polygon vertex count.
Related: METAR · TAF · Winds Aloft · PIREPs · AIRMET/SIGMET · Error handling · Caching & monitoring