Geocoding¶
The weather module provides two ways to resolve coordinates without hard-coding them:
geocode(address)— convert a known street address to(lat, lon)via the free Nominatim (OpenStreetMap) service.detect_location()— auto-detect the current machine's approximate coordinates from its public IP.
Both return a (latitude, longitude) tuple and raise GeocodingError on
failure, so they compose interchangeably with
StationIndex.nearest().
Basic Usage¶
from idfkit.weather import geocode
# Get coordinates for an address
lat, lon = geocode("350 Fifth Avenue, New York, NY")
print(f"Empire State Building: {lat:.4f}, {lon:.4f}")
With Station Search¶
Combine with StationIndex.nearest() for address-based weather station lookup:
from idfkit.weather import StationIndex, geocode
index = StationIndex.load()
# One-liner using splat operator
results = index.nearest(*geocode("Willis Tower, Chicago, IL"))
# First result is the nearest station
station = results[0].station
print(f"Nearest: {station.display_name} ({results[0].distance_km:.1f} km)")
Address Formats¶
The geocoder accepts various address formats:
# Full address
lat, lon = geocode("123 Main Street, Springfield, IL 62701")
# Landmark
lat, lon = geocode("Eiffel Tower, Paris")
# City only
lat, lon = geocode("Tokyo, Japan")
# Partial address
lat, lon = geocode("Times Square, New York")
Error Handling¶
from idfkit.weather import geocode, GeocodingError
try:
lat, lon = geocode("Nonexistent Place XYZ123")
except GeocodingError as e:
print(f"Geocoding failed: {e}")
Common Errors¶
| Situation | Behavior |
|---|---|
| Address not found | Raises GeocodingError |
| Network error | Raises GeocodingError |
| Rate limited | Automatically retries with delay |
Rate Limiting¶
Nominatim requires a maximum of 1 request per second. The geocode()
function automatically handles rate limiting:
# These are automatically spaced 1 second apart
for address in addresses:
lat, lon = geocode(address) # Rate limited internally
print(f"{address}: {lat:.2f}, {lon:.2f}")
No API Key Required¶
Nominatim is a free service that doesn't require an API key. However:
- Be respectful of usage limits
- Avoid bulk geocoding (use batch geocoding services for large datasets)
- Cache results when possible
Caching Results¶
For repeated lookups, cache the coordinates:
from functools import lru_cache
@lru_cache(maxsize=100)
def cached_geocode(address: str) -> tuple[float, float]:
return geocode(address)
# Subsequent calls are instant
lat, lon = cached_geocode("123 Main St")
lat, lon = cached_geocode("123 Main St") # From cache
Complete Workflow¶
from idfkit import load_idf
from idfkit.weather import (
StationIndex,
WeatherDownloader,
DesignDayManager,
geocode,
)
from idfkit.simulation import simulate
# Project location
project_address = "1600 Pennsylvania Avenue, Washington, DC"
# Find nearest weather station
index = StationIndex.load()
lat, lon = geocode(project_address)
results = index.nearest(lat, lon)
station = results[0].station
print(f"Project location: {lat:.4f}, {lon:.4f}")
print(f"Nearest station: {station.display_name} ({results[0].distance_km:.1f} km)")
# Download weather data
downloader = WeatherDownloader()
files = downloader.download(station)
# Load model and apply design days
model = load_idf("building.idf")
ddm = DesignDayManager(files.ddy)
ddm.apply_to_model(model, heating="99.6%", cooling="1%", update_location=True)
# Run simulation
result = simulate(model, files.epw, design_day=True)
Accuracy Notes¶
- Geocoding accuracy varies by location and address specificity
- Results may vary slightly over time as OpenStreetMap data is updated
- For critical applications, verify coordinates manually
Alternative: Direct Coordinates¶
If you already know the coordinates, skip geocoding entirely:
Detect Location from IP¶
detect_location() resolves the running machine's approximate
coordinates from its public IP, so callers don't need to know or supply
an address. It's the "weather stations near me" companion to geocode().
from idfkit.weather import detect_location
# Detect approximate coordinates from this machine's public IP.
# Sent over HTTPS to ipapi.co; result cached on disk for 1 hour.
lat, lon = detect_location()
print(f"Approximate location: {lat:.4f}, {lon:.4f}")
Combining with Station Search¶
Like geocode(), the result splats directly into
StationIndex.nearest():
from idfkit.weather import StationIndex, detect_location
index = StationIndex.load()
# "Find weather stations near me" — one liner using the splat operator.
results = index.nearest(*detect_location())
station = results[0].station
print(f"Nearest: {station.display_name} ({results[0].distance_km:.1f} km)")
The same flow is exposed on the CLI as
idfkit tmy --nearby.
Caching¶
Calls hit ipapi.co over HTTPS and the result is cached on disk for
1 hour by default. Repeated calls within that window return the
cached value with no network access. The cache file lives under the same
platform cache directory used by WeatherDownloader (e.g.
~/.cache/idfkit/weather/ipgeo.json on Linux), and the TTL and location
are both configurable:
from idfkit.weather import detect_location
# Default: cache for 1 hour in the platform weather cache directory.
lat, lon = detect_location()
# Cache for 24 hours instead.
lat, lon = detect_location(max_age=timedelta(hours=24))
# Always re-fetch (skip the cache).
lat, lon = detect_location(max_age=0)
# Cache forever (until the file is deleted).
lat, lon = detect_location(max_age=None)
# Use a custom cache directory.
lat, lon = detect_location(cache_dir=Path("/tmp/my-cache"))
Error Handling¶
detect_location() raises GeocodingError
on any failure — network outage, ipapi.co rate limit, or an
unlocatable IP (e.g. some VPNs):
from idfkit.weather import GeocodingError, detect_location
try:
lat, lon = detect_location()
except GeocodingError as e:
# Network failure, ipapi.co rate limit, or unrecognised IP.
print(f"Could not detect location: {e}")
# Fall back to an explicit prompt or a hard-coded default.
lat, lon = 41.85, -87.65 # Chicago
Privacy and Accuracy Notes¶
- Calling
detect_location()sends your machine's public IP address to ipapi.co over HTTPS. If you'd rather not, usegeocode("city, country")or pass coordinates directly. - Accuracy is city-level. That is sufficient for choosing a TMYx station within ~50 km, but not for precise positioning.
- The cache is on the local filesystem; nothing is sent anywhere else.
See Also¶
- Station Search — Find weather stations
- Weather Downloads — Download weather files
- Weather Overview — Module overview
idfkit tmy --nearby— CLI shortcut arounddetect_location()