Weather API Overview¶
The weather module provides station search, file downloads, and design day management.
Quick Reference¶
| Class/Function | Description |
|---|---|
StationIndex |
Weather station search and filtering |
WeatherStation |
Station metadata container |
WeatherDownloader |
EPW/DDY file downloads |
WeatherFiles |
Downloaded file paths |
PartialWeatherFiles |
Selective-extraction file paths (each may be None) |
DesignDayManager |
Design day parsing and injection |
DesignDayType |
Design day type enumeration |
apply_ashrae_sizing() |
Quick design day application |
geocode() |
Address to coordinates |
detect_location() |
This machine's coordinates from public IP |
Module Contents¶
Weather station search, download, and design day injection.
This sub-package provides tools for:
- Searching the climate.onebuilding.org TMYx station index by name, coordinates, or metadata filters.
- Downloading EPW and DDY weather files with local caching.
- Parsing DDY files and injecting ASHRAE design day conditions into EnergyPlus models.
- Geocoding addresses to coordinates via the free Nominatim API.
Station Index¶
The bundled index contains ~70,000 dataset entries representing
~17,300 unique physical weather stations worldwide. The difference
is because each station may have multiple TMYx year-range variants
(e.g., TMYx.2007-2021, TMYx.2009-2023), each stored as a
separate entry with its own download URL.
Quick Start¶
Search by name:
```python
from idfkit.weather import StationIndex, WeatherDownloader
index = StationIndex.load() # Instant, no network needed
results = index.search("chicago ohare")
station = results[0].station
downloader = WeatherDownloader()
files = downloader.download(station)
print(files.epw, files.ddy)
```
Search by Address (Splat Pattern)¶
Combine geocode with nearest using the splat operator to find weather stations near any address:
```python
from idfkit.weather import StationIndex, geocode
index = StationIndex.load()
# Find stations near an address (one line!)
results = index.nearest(*geocode("350 Fifth Avenue, New York, NY"))
for r in results[:3]:
print(f"{r.station.display_name}: {r.distance_km:.0f} km")
# Output:
# New York La Guardia AP, NY, USA: 10 km
# New York J F Kennedy Intl AP, NY, USA: 18 km
# Newark Liberty Intl AP, NJ, USA: 22 km
```
The geocode function uses the free Nominatim (OpenStreetMap) service, which requires no API key. Requests are rate-limited to 1 per second.
Apply Design Days¶
Inject ASHRAE sizing design days into your model:
```python
from idfkit import load_idf
from idfkit.weather import StationIndex, apply_ashrae_sizing
model = load_idf("building.idf")
station = StationIndex.load().search("chicago ohare")[0].station
# Apply ASHRAE 90.1 design conditions
added = apply_ashrae_sizing(model, station, standard="90.1")
print(f"Added {len(added)} design days")
```
Index Freshness¶
StationIndex.load() is purely local and requires no extra dependencies.
Use StationIndex.refresh() to re-download the upstream KML indexes and
rebuild the local cache. Refresh is also stdlib-only — no third-party
packages required:
```python
if index.check_for_updates():
index = StationIndex.refresh()
```
NoDesignDaysError
¶
Bases: IdfKitError
Raised when a DDY file contains no SizingPeriod:DesignDay objects.
This typically occurs for weather stations that lack ASHRAE design conditions data in the climate.onebuilding.org database.
Attributes:
| Name | Type | Description |
|---|---|---|
station_name |
Display name of the station (if available). |
|
ddy_path |
Path to the DDY file that was parsed. |
|
nearby_suggestions |
List of nearby stations that may have design days. |
DesignDayManager
¶
Parse DDY files and inject design day conditions into IDF models.
A DDY file is a valid IDF-syntax file containing Site:Location and
SizingPeriod:DesignDay objects. This class uses [idfkit.load_idf][idfkit.load_idf]
to parse the file and classifies each design day by its ASHRAE condition
type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ddy_path
|
Path | str
|
Path to a |
required |
version
|
tuple[int, int, int] | None
|
EnergyPlus version to use for schema resolution. Defaults to the latest supported version (design day fields are stable across versions). |
None
|
all_design_days
property
¶
All SizingPeriod:DesignDay objects from the DDY file.
annual
property
¶
All classified annual design day objects.
monthly
property
¶
All monthly design day objects.
Monthly design days follow the naming pattern
{Location} {Month} {pct}% Condns {type} and are not classified
into the DesignDayType enum.
heating
property
¶
All heating design days.
cooling
property
¶
All cooling dry-bulb, wet-bulb, enthalpy, and dehumidification design days.
location
property
¶
The Site:Location object from the DDY file, if present.
from_station(station, *, dataset=None, version=None)
classmethod
¶
Download the DDY file for a station and parse it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
station
|
WeatherStation
|
The weather station. |
required |
dataset
|
str | None
|
TMYx variant to download. Defaults to the most recent. |
None
|
version
|
tuple[int, int, int] | None
|
EnergyPlus version for schema resolution. |
None
|
get(dd_type)
¶
Get a specific design day by type.
raise_if_empty()
¶
Raise :exc:NoDesignDaysError if this DDY has no design days.
When constructed via from_station, the error message includes suggestions for nearby stations that may have design day data.
Raises:
| Type | Description |
|---|---|
NoDesignDaysError
|
If |
apply_to_model(model, *, heating='99.6%', cooling='1%', include_wet_bulb=False, include_enthalpy=False, include_dehumidification=False, include_wind=False, update_location=True, replace_existing=True)
¶
Inject design day objects into an IDF model.
Selects the appropriate design days based on common ASHRAE sizing
practices and adds them as SizingPeriod:DesignDay objects.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
IDFDocument
|
The target IDFDocument. |
required |
heating
|
Literal['99.6%', '99%', 'both']
|
Which heating percentile to include. |
'99.6%'
|
cooling
|
Literal['0.4%', '1%', '2%', 'all']
|
Which cooling dry-bulb percentile to include. |
'1%'
|
include_wet_bulb
|
bool
|
Also include cooling wet-bulb design days. |
False
|
include_enthalpy
|
bool
|
Also include cooling enthalpy design days. |
False
|
include_dehumidification
|
bool
|
Also include dehumidification design days. |
False
|
include_wind
|
bool
|
Also include heating wind-speed design days. |
False
|
update_location
|
bool
|
Update the |
True
|
replace_existing
|
bool
|
Remove existing |
True
|
Returns:
| Type | Description |
|---|---|
list[str]
|
List of design day names that were added. |
summary()
¶
Human-readable summary of all design days in the DDY file.
DesignDayType
¶
Bases: Enum
Classification of ASHRAE annual design day conditions.
Values encode the condition type and annual percentile.
PartialWeatherFiles
dataclass
¶
Paths to a selectively extracted weather bundle.
Returned by WeatherDownloader.download(station, only=...). Any field
whose suffix was not requested and not already cached on disk will be
None.
Attributes:
| Name | Type | Description |
|---|---|---|
epw |
Path | None
|
Path to the |
ddy |
Path | None
|
Path to the |
stat |
Path | None
|
Path to the |
zip_path |
Path
|
Path to the original downloaded ZIP archive. |
station |
WeatherStation
|
The station this download corresponds to. |
WeatherDownloader
¶
Download and cache weather files from climate.onebuilding.org.
Downloaded ZIP archives are extracted and cached locally so that subsequent requests for the same station and dataset are served from disk without a network call.
Examples:
from idfkit.weather import StationIndex, WeatherDownloader
station = StationIndex.load().search("chicago ohare")[0].station
downloader = WeatherDownloader()
files = downloader.download(station)
print(files.epw)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cache_dir
|
Path | None
|
Override the default cache directory. |
None
|
max_age
|
timedelta | float | None
|
Maximum age of cached files before re-downloading.
Can be a timedelta or a number of seconds.
If |
None
|
Note
Extracted .ddy files are rewritten in place to drop
SizingPeriod:DesignDay objects whose numeric fields contain
non-numeric placeholder tokens (e.g. N, N/A). These appear
in some upstream archives when source data is unavailable and
would otherwise cause EnergyPlus to fail with a type-constraint
error.
Note
The cache has no size limit. For CI/CD environments with limited disk
space, consider using clear_cache periodically or setting
a max_age to force re-downloads of stale files.
download(station, *, only=None)
¶
Download and extract weather files for station.
If the files are already cached and not stale, no network request is made.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
station
|
WeatherStation
|
The weather station to download files for. |
required |
only
|
Iterable[str] | None
|
If given, extract only members whose suffix matches one of
these values (e.g. |
None
|
Returns:
| Type | Description |
|---|---|
WeatherFiles | PartialWeatherFiles
|
WeatherFiles for a full |
WeatherFiles | PartialWeatherFiles
|
extraction, or |
WeatherFiles | PartialWeatherFiles
|
|
WeatherFiles | PartialWeatherFiles
|
when |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the download or extraction fails, or if a full
extraction is missing a required |
get_epw(station)
¶
Download and return the path to the EPW file.
Extracts the full archive. To skip extraction of unwanted members,
call download(station, only={".epw"}).epw directly.
get_ddy(station)
¶
Download and return the path to the DDY file.
Extracts the full archive. To skip extraction of unwanted members,
call download(station, only={".ddy"}).ddy directly.
get_epw_by_filename(filename, *, index=None)
¶
Download and return the EPW path for an EPW filename.
Resolves the canonical EPW filename to a station via StationIndex.get_by_filename and downloads the corresponding weather files.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filename
|
str
|
EPW filename or stem (with or without extension). |
required |
index
|
StationIndex | None
|
A pre-loaded station index. If |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the filename does not match any station. |
get_ddy_by_filename(filename, *, index=None)
¶
Download and return the DDY path for an EPW filename.
Same as get_epw_by_filename but returns the DDY file path.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filename
|
str
|
EPW filename or stem (with or without extension). |
required |
index
|
StationIndex | None
|
A pre-loaded station index. |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the filename does not match any station. |
clear_cache()
¶
Remove all cached weather files.
This removes the entire files/ subdirectory within the cache,
which contains all downloaded ZIP archives and extracted files.
WeatherFiles
dataclass
¶
Paths to a fully extracted weather bundle.
Returned by WeatherDownloader.download(station) (no only=).
epw and ddy are guaranteed non-None — a missing one raises
during download.
Attributes:
| Name | Type | Description |
|---|---|---|
epw |
Path
|
Path to the |
ddy |
Path
|
Path to the |
stat |
Path | None
|
Path to the |
zip_path |
Path
|
Path to the original downloaded ZIP archive. |
station |
WeatherStation
|
The station this download corresponds to. |
StationIndex
¶
Searchable index of weather stations from climate.onebuilding.org.
Use load to load the bundled (or user-refreshed) station index. No network access or third-party dependencies are required for load.
Use check_for_updates to see if upstream data has changed, and refresh to re-download and rebuild the index.
Examples:
index = StationIndex.load()
results = index.search("chicago ohare", limit=3)
for r in results:
print(r.station.display_name, r.score)
stations
property
¶
All stations in the index.
countries
property
¶
Sorted list of unique country codes in the index.
load(*, cache_dir=None)
classmethod
¶
Load the station index from a local compressed file.
Checks for a user-refreshed cache first, then falls back to the bundled index shipped with the package. No network access is required.
If the cached file is from an older idfkit version and fails to
deserialise (e.g. missing schema fields after a format migration),
it is quarantined to stations.json.gz.stale and the bundled
index is used instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cache_dir
|
Path | None
|
Override the default cache directory. |
None
|
from_stations(stations)
classmethod
¶
Create an index from an explicit list of stations (useful for tests).
refresh(*, cache_dir=None)
classmethod
¶
Re-download the regional KML indexes from climate.onebuilding.org and rebuild the cache.
Uses the Python standard library only — no third-party dependencies.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cache_dir
|
Path | None
|
Override the default cache directory. |
None
|
check_for_updates()
¶
Check if upstream KML files have changed since this index was built.
Sends lightweight HEAD requests to climate.onebuilding.org.
Returns True if any file has a newer Last-Modified date.
Returns False if all files match or if the check fails (offline,
timeout, etc.).
get_by_wmo(wmo)
¶
Look up stations by WMO number.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
wmo
|
str
|
WMO station number as a string (e.g. |
required |
Returns a list because a single WMO number can correspond to multiple stations or dataset variants.
get_by_filename(filename)
¶
Look up stations by EPW filename.
The filename can include or omit the extension (.zip,
.epw, .ddy). Matching is case-insensitive.
When the exact filename matches an indexed entry, those stations are returned. Otherwise the method extracts the WMO number from the filename and falls back to get_by_wmo.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filename
|
str
|
An EPW filename or stem, e.g.
|
required |
Returns:
| Type | Description |
|---|---|
list[WeatherStation]
|
Matching stations (may be empty). |
search(query, *, limit=10, country=None)
¶
Fuzzy-search stations by name, city, state, WMO number, or EPW filename.
Matching is case-insensitive and uses substring / token-prefix
heuristics (no external NLP dependencies). Canonical EPW
filenames (e.g.
"USA_IL_Chicago.Ohare.Intl.AP.725300_TMYx.2009-2023") are
detected automatically and resolved via
get_by_filename.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
query
|
str
|
Free-text search query or EPW filename. |
required |
limit
|
int
|
Maximum number of results to return. |
10
|
country
|
str | None
|
If given, restrict to stations in this country code. |
None
|
nearest(latitude, longitude, *, limit=5, max_distance_km=None, country=None)
¶
Find stations nearest to a geographic coordinate.
Uses the Haversine formula for great-circle distance. A bounding-box pre-filter is applied when max_distance_km is specified to avoid computing distances for stations that are obviously too far.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
latitude
|
float
|
Decimal degrees, north positive. |
required |
longitude
|
float
|
Decimal degrees, east positive. |
required |
limit
|
int
|
Maximum results to return. |
5
|
max_distance_km
|
float | None
|
Exclude stations farther than this. |
None
|
country
|
str | None
|
If given, restrict to this country code. |
None
|
filter(*, country=None, state=None, wmo_region=None)
¶
Filter stations by metadata criteria.
All specified criteria must match (logical AND).
SearchResult
dataclass
¶
SpatialResult
dataclass
¶
A spatial proximity result with great-circle distance.
distance_km
instance-attribute
¶
Great-circle distance in kilometres.
WeatherStation
dataclass
¶
Metadata for a single weather file entry from climate.onebuilding.org.
Each instance represents one downloadable weather dataset. The same physical
station may appear multiple times with different source or year-range
variants (e.g. TMYx.2007-2021 vs TMYx.2009-2023).
Attributes:
| Name | Type | Description |
|---|---|---|
country |
str
|
ISO 3166 country code (e.g. |
state |
str
|
State or province abbreviation (e.g. |
city |
str
|
City or station name as it appears in the index
(e.g. |
wmo |
str
|
WMO station number as a string to preserve leading zeros
(e.g. |
source |
str
|
Dataset source identifier (e.g. |
latitude |
float
|
Decimal degrees, north positive. |
longitude |
float
|
Decimal degrees, east positive. |
timezone |
float
|
Hours offset from GMT (e.g. |
elevation |
float
|
Meters above sea level. |
url |
str
|
Full download URL for the ZIP archive. |
ashrae_climate_zone |
str
|
ASHRAE HOF climate zone label
(e.g. |
heating_design_db_c |
float
|
99% heating design dry-bulb temperature in °C. |
cooling_design_db_c |
float
|
1% cooling design dry-bulb temperature in °C. |
hdd18 |
int
|
Heating degree-days base 18 °C. |
cdd10 |
int
|
Cooling degree-days base 10 °C. |
design_conditions_source_wmo |
str | None
|
When the station inherits design
conditions from a neighbouring station, this holds that
station's WMO number; otherwise |
display_name
property
¶
Human-readable station name with location context.
Dots in the city name are replaced with spaces for readability.
filename_stem
property
¶
The canonical EPW filename stem derived from the download URL.
Returns the ZIP filename without the .zip extension, e.g.
"USA_IL_Chicago.Ohare.Intl.AP.725300_TMYx.2009-2023".
dataset_variant
property
¶
Extract the TMYx dataset variant from the download URL.
Returns a string like "TMYx", "TMYx.2007-2021", or
"TMYx.2009-2023".
heating_design_db_f
property
¶
99% heating design dry-bulb temperature in °F.
cooling_design_db_f
property
¶
1% cooling design dry-bulb temperature in °F.
to_dict()
¶
Serialize to a plain dictionary for JSON storage.
from_dict(data)
classmethod
¶
Deserialize from a plain dictionary.
The required climate fields use data[key] so a stale or
corrupt payload fails loudly. design_conditions_source_wmo
is genuinely optional.
apply_ashrae_sizing(model, station, *, standard='general', version=None)
¶
Apply standard ASHRAE sizing design days to a model.
This is the one-line convenience function for the most common use case.
Presets
"90.1": Heating 99.6% + Cooling 1% DB + Cooling 1% WB (per ASHRAE Standard 90.1 requirements)."general": Heating 99.6% + Cooling 0.4% DB (conservative general practice).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
IDFDocument
|
The IDFDocument to modify. |
required |
station
|
WeatherStation
|
Weather station whose DDY file to use. |
required |
standard
|
Literal['90.1', 'general']
|
ASHRAE preset to apply. |
'general'
|
version
|
tuple[int, int, int] | None
|
EnergyPlus version for schema resolution. |
None
|
Returns:
| Type | Description |
|---|---|
list[str]
|
List of design day names that were added. |
Raises:
| Type | Description |
|---|---|
NoDesignDaysError
|
If the station's DDY file contains no design days. The exception includes suggestions for nearby stations that may have design day data. |
detect_location(*, cache_dir=None, max_age=timedelta(hours=1))
¶
Detect approximate (latitude, longitude) from this machine's public IP.
Sends the machine's public IP to ipapi.co <https://ipapi.co/>_ over
HTTPS and parses the response. Accuracy is city-level — sufficient
for finding the nearest TMYx weather stations within a few tens of
kilometres, but not for precise positioning.
The result is cached to disk for max_age (default: 1 hour) so that
repeated invocations don't hammer the upstream service. Pass
max_age=None to cache indefinitely, or max_age=0 to disable
caching entirely.
This function is thread-safe; concurrent calls are serialised by the same rate limiter pattern used for Nominatim.
Composable with spatial search: Use the splat operator to combine with nearest for "weather stations near me" lookups:
```python
from idfkit.weather import StationIndex, detect_location
results = StationIndex.load().nearest(*detect_location())
for r in results[:3]:
print(f"{r.station.display_name}: {r.distance_km:.0f} km")
```
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cache_dir
|
Path | None
|
Directory for the cache file. Defaults to the platform
weather cache directory (same as :class: |
None
|
max_age
|
timedelta | float | None
|
Maximum cache age before re-fetching. |
timedelta(hours=1)
|
Returns:
| Type | Description |
|---|---|
tuple[float, float]
|
A |
Raises:
| Type | Description |
|---|---|
GeocodingError
|
If the IP cannot be located or the service is unreachable. |
Note
Calling this function sends your public IP address to ipapi.co
over HTTPS. Use --lat / --lon (or --near ADDRESS) on the
CLI if you'd rather not.