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.

METAR GeoParquet Interactive Visualization

University at Albany (SUNY)

Overview

Within this notebook, we will create interactive visualizations of METAR data at a single hour. We will use the following libraries for our visualizations:

  1. Geopandas

Prerequisites

ConceptsImportanceNotes
Cartopy IntroRequiredProjections and Features
PandasRequiredTabular Datasets
  • Time to learn: 20 minutes


Imports

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from datetime import datetime,timedelta
import geopandas as gpd
from lonboard import viz, Map, ScatterplotLayer, HeatmapLayer
import duckdb
import folium

By default, set the date and time to one hour prior to the current time. Or, specify a past date and hour.

# Use the current time, or set your own for a past time.
# Set current to False if you want to specify a past time.

nowTime = datetime.now()

current = True
#current = False
if (current):
    time_1 = datetime.now()
    offset = timedelta(hours = 1) # Worldwide METARS typically don't come in until ~15 minutes past the hour
    time_1 = time_1 - offset
else:
    year = 2026
    month = 1
    day = 1
    hour = 0
    time_1 = datetime(year, month, day, hour)

The timestamps of many hourly METAR reports are 5-10 minutes prior to the top of the hour. When we make our DuckDB query, we will specify a beginning and end time, one hour apart.

time_0 = time_1 - timedelta(hours=1)
YYYY_0 = time_0.strftime("%Y")
YYYY_1 = time_1.strftime("%Y")
date_string = time_1.strftime("%Y-%m-%d %H00 UTC")
print(time_0, time_1)
# Handle edge case when the two hours straddle the end/beginning of a yearw
if (YYYY_0 == YYYY_1):
    URLs = [f'https://data.source.coop/dynamical/asos-parquet/year={YYYY_1}/data.parquet']
else:
    URLs = [f'https://data.source.coop/dynamical/asos-parquet/year={YYYY_0}/data.parquet',
            f'https://data.source.coop/dynamical/asos-parquet/year={YYYY_1}/data.parquet']
2026-06-30 02:14:32.918677 2026-06-30 03:14:32.918677
date_string
'2026-06-30 0300 UTC'
URLs
['https://data.source.coop/dynamical/asos-parquet/year=2026/data.parquet']

Open the GeoParquet file for the specific year.

df = duckdb.execute("""
    SELECT *
    FROM read_parquet($1, hive_partitioning=true)
    WHERE 
---      country = 'FR' AND
---    station = 'ENGM' AND
    valid BETWEEN $2 AND $3
    ORDER BY country
""", [URLs, time_0, time_1]).fetchdf()
Loading...
df
Loading...
gdf = gpd.GeoDataFrame(df,geometry=gpd.points_from_xy(df.longitude,df.latitude))
gdf.explore()
Loading...
gdf.set_crs(epsg=4326, inplace=True, allow_override=True)
Loading...
gdf.explore()
Loading...

Now, let’s choose a specific variable ... in this case, tmpc (2-meter temperature in °C)

gdf.explore(column='tmpc')
Loading...

Plot hourly precipitation

gdf.explore(column='p01m')
Loading...
gdf.loc[gdf['p01m'] < 0.01, 'p01m'] = np.nan
gdf = gdf.dropna(subset=['p01m']).reset_index(drop=True)
gdf.explore(column='p01m')
Loading...

To-do: add title to the graphics

m = gdf.explore()

# Add title
title_string = f"Hourly Precip (mm), {date_string}"

title_html = f"""
<h3 align="center" style="font-size:20px">
    <b>{title_string}</b>
</h3>
"""

m.get_root().html.add_child(folium.Element(title_html))

# Save or display
#m.save("map.html")
<branca.element.Element at 0x7fb3a39e5350>
m
Loading...

References

What’s next?

We will use Lonboard as an additional interactive visualization tool.