Skip to content

How to search for weather stations

The StationIndex provides fast searching and filtering of ~70,000 weather station dataset entries (covering ~17,300 unique physical stations) from climate.onebuilding.org.

Differs in JavaScript

The weather station index exists in both libraries and does not behave the same way in JavaScript. The ledger records it as Python complete, JavaScript partial, and what differs is stated here rather than left to be discovered.

Installation differs, and the difference is deliberate. pip install idfkit installs weather and its station index unconditionally, because Python extras gate dependencies rather than files, so a Python reader has weather whether or not they wanted it. npm install idfkit installs neither: weather is an opt-in peer there, added with npm install @idfkit/weather. Both libraries ship their own index once installed and neither retrieves one to get started (FR-043, FR-075, research R11). A JavaScript reader who follows a weather page without installing that package gets a resolution error, not a smaller feature, which is why the packaging is recorded here as a stated difference rather than left as an undescribed detail.

Freshness handling differs. Python fires a throttled nudge from StationIndex.load(): at most once every 24 hours it probes the upstream KML files, warns when the bundled or cached index is behind, records the check under the cache directory, and can be turned off with IDFKIT_NO_WEATHER_UPDATE_CHECK. JavaScript has no such nudge and no timestamp to throttle against. It exposes checkForUpdates and refreshStationIndex for a caller who asks, and does nothing on its own, because the nudge is built on a writable cache directory and a browser-targeted package has none. The consequence for a reader is concrete: a stale index goes unmentioned in JavaScript until they check for themselves.

The installation difference above is the whole weather surface's, not the index's alone. It is stated here because the index is where a reader meets it first, and weather-download and weather-file-cache refer back to it rather than repeat it.

The full entry, including the vocabulary this capability owns, is on the capability parity page.

How weather is installed

Weather comes with the library. pip install idfkit installs the station index whether or not you asked for it, because a Python extra gates dependencies rather than files.

pip install idfkit

Weather is an opt-in install and the shared name does not reach it. npm install idfkit places no station index on disk and no weather code in your bundle; add the package by name.

npm install @idfkit/weather

Loading the Index

from idfkit.weather import StationIndex

# Instant load from bundled data (no network needed)
index = StationIndex.load()

print(f"Stations: {len(index)}")
print(f"Countries: {len(index.countries)}")

Search by Name

Fuzzy text search across station names, cities, and WMO numbers:

# Search by name
results = index.search("chicago ohare")

for r in results[:5]:
    print(f"{r.station.display_name} (score={r.score:.2f})")

SearchResult Attributes

A text search result with relevance score.

match_field instance-attribute

Which field matched: "wmo", "name", "state", "country", or "filename".

score instance-attribute

Relevance score from 0.0 to 1.0, higher is better.

station instance-attribute

Search Tips

# City name
results = index.search("New York")

# Airport code pattern
results = index.search("JFK")

# WMO number
results = index.search("725300")

# Country + city
results = index.search("London UK")

Search by EPW Filename

search() automatically detects canonical EPW filenames and resolves them:

# EPW filenames are detected automatically by search()
results = index.search("USA_IL_Chicago.Ohare.Intl.AP.725300_TMYx.2009-2023")

# Extensions are tolerated
results = index.search("GBR_London.Heathrow.AP.037720_TMYx.epw")

# Works the same as any other search query
for r in results[:3]:
    print(f"{r.station.display_name} (score={r.score:.2f})")

For exact lookups when you know the precise filename, use get_by_filename():

# Exact lookup by EPW filename (case-insensitive, extension-tolerant)
stations = index.get_by_filename("USA_IL_Chicago.Ohare.Intl.AP.725300_TMYx.2009-2023")

for station in stations:
    print(f"{station.display_name}: {station.dataset_variant}")

Search by Coordinates

Find stations nearest to a location using great-circle distance:

# Nearest to downtown Chicago
results = index.nearest(41.88, -87.63)

for r in results[:5]:
    print(f"{r.station.display_name}: {r.distance_km:.1f} km")

Function Signature

def nearest(
    self,
    latitude: float,
    longitude: float,
    *,
    limit: int = 5,
    max_distance_km: float | None = None,
    country: str | None = None,
) -> list[SpatialResult]:

SpatialResult Attributes

A spatial proximity result with great-circle distance.

distance_km instance-attribute

Great-circle distance in kilometres.

station instance-attribute

Search by Address

Combine geocode() with nearest() for address-based search:

from idfkit.weather import StationIndex, geocode

index = StationIndex.load()

# One-liner using splat operator
results = index.nearest(*geocode("Willis Tower, Chicago, IL"))

# Or step by step
lat, lon = geocode("350 Fifth Avenue, New York, NY")
results = index.nearest(lat, lon)

Climate-zone-aware search

Each WeatherStation carries its ASHRAE HOF climate zone, design dry-bulb temperatures, HDD18, and CDD10. See Filter by Climate Zone below.

Filter by Country

# Get all stations in a country
us_stations = index.filter(country="USA")
print(f"US stations: {len(us_stations)}")

# Get all stations in a state/region
california = [s for s in us_stations if s.state == "CA"]

Filter by Coordinates

Use nearest() with max_distance_km to find stations within a geographic area:

# Find all stations within 100 km of a point
stations = index.nearest(
    41.0,
    -88.5,
    max_distance_km=100.0,
    limit=50,
)

Get by WMO Number

# Get specific station by WMO
results = index.get_by_wmo("725300")

for station in results:
    print(f"{station.display_name}: {station.source}")

Note: WMO numbers are not unique — multiple entries can share a WMO (different year ranges, data sources).

WeatherStation Attributes

Every field of WeatherStation, with its type and its default, is in the API reference. It is generated from the source, so it cannot fall behind the way the table that used to sit here did.

The five climate metrics (ashrae_climate_zone, the two design DBs, HDD18, and CDD10) are populated for every station in the bundled index. design_conditions_source_wmo is only set when a station inherits its design conditions from a neighbouring WMO station; otherwise it is None.

Filter by Climate Zone

Filter stations by ASHRAE climate zone using a plain list comprehension:

# Each WeatherStation carries its ASHRAE HOF climate zone.
zone_4a = [s for s in index.stations if s.ashrae_climate_zone.startswith("4A")]
print(f"Zone 4A stations: {len(zone_4a)}")

# Combine with country/state via the existing filter() to narrow further:
us_zone_5 = [s for s in index.filter(country="USA") if s.ashrae_climate_zone.startswith("5")]

# Pick the warmest design dry-bulb in a given zone:
hottest = max(us_zone_5, key=lambda s: s.cooling_design_db_c)
print(f"{hottest.display_name}: {hottest.cooling_design_db_c} °C / {hottest.cooling_design_db_f:.1f} °F")

Listing Countries

# Get all available countries
countries = index.countries

for country in sorted(countries)[:10]:
    count = len(index.filter(country=country))
    print(f"{country}: {count} stations")

Refreshing the Index

The bundled index works without network access. To get the latest data:

# Check if upstream has updates
if index.check_for_updates():
    print("Updates available")

    # Refresh from climate.onebuilding.org (stdlib only, no extras)
    index = StationIndex.refresh()

Refresh uses the Python standard library only — no third-party packages required. The same operation is available from the shell as idfkit tmy --refresh — see idfkit tmy.

Performance

The index uses efficient data structures for fast searching:

Operation Typical Time
load() ~100ms
search(query) ~10ms
nearest(lat, lon) ~50ms
filter(country=...) ~5ms

Best Practices

  1. Load once — Keep the index in memory for multiple searches
  2. Use spatial search — More accurate than name matching for locations
  3. Check multiple results — First result isn't always the best match
  4. Verify WMO — Same physical station may have multiple entries

See Also