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.
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¶
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¶
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¶
- Load once — Keep the index in memory for multiple searches
- Use spatial search — More accurate than name matching for locations
- Check multiple results — First result isn't always the best match
- Verify WMO — Same physical station may have multiple entries
See Also¶
- How to download weather files — Download files for a station
- How to geocode addresses — Convert addresses to coordinates
- Weather Pipeline — Architecture details