|
|
""" |
|
|
ASHRAE 169 climate data module for HVAC Load Calculator. |
|
|
This module provides access to climate data for various locations based on ASHRAE 169 standard. |
|
|
""" |
|
|
|
|
|
from typing import Dict, List, Any, Optional, Tuple |
|
|
import pandas as pd |
|
|
import numpy as np |
|
|
import os |
|
|
import json |
|
|
from dataclasses import dataclass |
|
|
|
|
|
|
|
|
DATA_DIR = os.path.dirname(os.path.abspath(__file__)) |
|
|
|
|
|
|
|
|
@dataclass |
|
|
class ClimateLocation: |
|
|
"""Class representing a climate location with ASHRAE 169 data.""" |
|
|
|
|
|
id: str |
|
|
country: str |
|
|
state_province: str |
|
|
city: str |
|
|
latitude: float |
|
|
longitude: float |
|
|
elevation: float |
|
|
climate_zone: str |
|
|
heating_degree_days: float |
|
|
cooling_degree_days: float |
|
|
|
|
|
|
|
|
winter_design_temp: float |
|
|
summer_design_temp_db: float |
|
|
summer_design_temp_wb: float |
|
|
summer_daily_range: float |
|
|
|
|
|
|
|
|
monthly_temps: Dict[str, float] |
|
|
monthly_humidity: Dict[str, float] |
|
|
|
|
|
def to_dict(self) -> Dict[str, Any]: |
|
|
"""Convert the climate location to a dictionary.""" |
|
|
return { |
|
|
"id": self.id, |
|
|
"country": self.country, |
|
|
"state_province": self.state_province, |
|
|
"city": self.city, |
|
|
"latitude": self.latitude, |
|
|
"longitude": self.longitude, |
|
|
"elevation": self.elevation, |
|
|
"climate_zone": self.climate_zone, |
|
|
"heating_degree_days": self.heating_degree_days, |
|
|
"cooling_degree_days": self.cooling_degree_days, |
|
|
"winter_design_temp": self.winter_design_temp, |
|
|
"summer_design_temp_db": self.summer_design_temp_db, |
|
|
"summer_design_temp_wb": self.summer_design_temp_wb, |
|
|
"summer_daily_range": self.summer_daily_range, |
|
|
"monthly_temps": self.monthly_temps, |
|
|
"monthly_humidity": self.monthly_humidity |
|
|
} |
|
|
|
|
|
|
|
|
class ClimateData: |
|
|
"""Class for managing ASHRAE 169 climate data.""" |
|
|
|
|
|
def __init__(self): |
|
|
"""Initialize climate data.""" |
|
|
self.locations = self._load_climate_locations() |
|
|
self.countries = sorted(list(set(loc.country for loc in self.locations.values()))) |
|
|
self.country_states = self._group_locations_by_country_state() |
|
|
|
|
|
@staticmethod |
|
|
def get_design_conditions(climate_zone: str) -> Dict[str, Dict[str, float]]: |
|
|
""" |
|
|
Get design conditions for a specific climate zone. |
|
|
|
|
|
Args: |
|
|
climate_zone: ASHRAE climate zone (e.g., '1A', '3B', '5A') |
|
|
|
|
|
Returns: |
|
|
Dictionary with summer and winter design conditions |
|
|
""" |
|
|
|
|
|
design_conditions_by_zone = { |
|
|
|
|
|
"1A": { |
|
|
"summer": {"db": 35.0, "wb": 28.0, "dp": 25.5}, |
|
|
"winter": {"db": 12.0, "rh": 80.0} |
|
|
}, |
|
|
|
|
|
"1B": { |
|
|
"summer": {"db": 42.0, "wb": 24.0, "dp": 18.0}, |
|
|
"winter": {"db": 10.0, "rh": 40.0} |
|
|
}, |
|
|
|
|
|
"2A": { |
|
|
"summer": {"db": 34.0, "wb": 26.5, "dp": 24.0}, |
|
|
"winter": {"db": 8.0, "rh": 75.0} |
|
|
}, |
|
|
|
|
|
"2B": { |
|
|
"summer": {"db": 40.0, "wb": 23.0, "dp": 16.5}, |
|
|
"winter": {"db": 6.0, "rh": 45.0} |
|
|
}, |
|
|
|
|
|
"3A": { |
|
|
"summer": {"db": 33.0, "wb": 25.0, "dp": 22.5}, |
|
|
"winter": {"db": 2.0, "rh": 70.0} |
|
|
}, |
|
|
|
|
|
"3B": { |
|
|
"summer": {"db": 38.0, "wb": 22.0, "dp": 15.0}, |
|
|
"winter": {"db": 4.0, "rh": 40.0} |
|
|
}, |
|
|
|
|
|
"3C": { |
|
|
"summer": {"db": 28.0, "wb": 20.0, "dp": 17.0}, |
|
|
"winter": {"db": 5.0, "rh": 80.0} |
|
|
}, |
|
|
|
|
|
"4A": { |
|
|
"summer": {"db": 32.0, "wb": 24.0, "dp": 21.0}, |
|
|
"winter": {"db": -5.0, "rh": 70.0} |
|
|
}, |
|
|
|
|
|
"4B": { |
|
|
"summer": {"db": 35.0, "wb": 20.0, "dp": 13.0}, |
|
|
"winter": {"db": -3.0, "rh": 45.0} |
|
|
}, |
|
|
|
|
|
"4C": { |
|
|
"summer": {"db": 27.0, "wb": 19.0, "dp": 16.0}, |
|
|
"winter": {"db": -2.0, "rh": 80.0} |
|
|
}, |
|
|
|
|
|
"5A": { |
|
|
"summer": {"db": 31.0, "wb": 23.0, "dp": 20.0}, |
|
|
"winter": {"db": -15.0, "rh": 70.0} |
|
|
}, |
|
|
|
|
|
"5B": { |
|
|
"summer": {"db": 33.0, "wb": 18.0, "dp": 11.0}, |
|
|
"winter": {"db": -10.0, "rh": 45.0} |
|
|
}, |
|
|
|
|
|
"5C": { |
|
|
"summer": {"db": 25.0, "wb": 18.0, "dp": 15.0}, |
|
|
"winter": {"db": -5.0, "rh": 80.0} |
|
|
}, |
|
|
|
|
|
"6A": { |
|
|
"summer": {"db": 30.0, "wb": 22.0, "dp": 19.0}, |
|
|
"winter": {"db": -20.0, "rh": 70.0} |
|
|
}, |
|
|
|
|
|
"6B": { |
|
|
"summer": {"db": 31.0, "wb": 17.0, "dp": 10.0}, |
|
|
"winter": {"db": -15.0, "rh": 45.0} |
|
|
}, |
|
|
|
|
|
"7": { |
|
|
"summer": {"db": 28.0, "wb": 20.0, "dp": 17.0}, |
|
|
"winter": {"db": -25.0, "rh": 70.0} |
|
|
}, |
|
|
|
|
|
"8": { |
|
|
"summer": {"db": 25.0, "wb": 18.0, "dp": 15.0}, |
|
|
"winter": {"db": -30.0, "rh": 70.0} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if climate_zone in design_conditions_by_zone: |
|
|
return design_conditions_by_zone[climate_zone] |
|
|
else: |
|
|
|
|
|
return design_conditions_by_zone["4A"] |
|
|
|
|
|
@staticmethod |
|
|
def get_monthly_temperatures(climate_zone: str) -> Dict[int, Dict[str, float]]: |
|
|
""" |
|
|
Get monthly average temperatures for a specific climate zone. |
|
|
|
|
|
Args: |
|
|
climate_zone: ASHRAE climate zone (e.g., '1A', '3B', '5A') |
|
|
|
|
|
Returns: |
|
|
Dictionary with monthly average temperatures indexed by month number (1-12) |
|
|
Each month contains 'avg_db', 'max_db', and 'min_db' values |
|
|
""" |
|
|
|
|
|
def convert_month_format(month_data): |
|
|
month_names = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] |
|
|
result = {} |
|
|
|
|
|
for i, month in enumerate(month_names, 1): |
|
|
avg_temp = month_data[month] |
|
|
|
|
|
min_temp = avg_temp - 5.0 |
|
|
max_temp = avg_temp + 5.0 |
|
|
|
|
|
result[i] = { |
|
|
'avg_db': avg_temp, |
|
|
'min_db': min_temp, |
|
|
'max_db': max_temp |
|
|
} |
|
|
|
|
|
return result |
|
|
|
|
|
|
|
|
monthly_temps_raw = { |
|
|
|
|
|
"1A": { |
|
|
"Jan": 20.0, "Feb": 20.5, "Mar": 22.0, "Apr": 24.0, "May": 26.0, |
|
|
"Jun": 28.0, "Jul": 29.0, "Aug": 29.0, "Sep": 28.0, "Oct": 26.0, |
|
|
"Nov": 23.0, "Dec": 21.0 |
|
|
}, |
|
|
|
|
|
"1B": { |
|
|
"Jan": 15.0, "Feb": 17.0, "Mar": 22.0, "Apr": 27.0, "May": 32.0, |
|
|
"Jun": 35.0, "Jul": 37.0, "Aug": 36.0, "Sep": 33.0, "Oct": 28.0, |
|
|
"Nov": 22.0, "Dec": 17.0 |
|
|
}, |
|
|
|
|
|
"2A": { |
|
|
"Jan": 12.0, "Feb": 13.5, "Mar": 17.0, "Apr": 21.0, "May": 25.0, |
|
|
"Jun": 28.0, "Jul": 29.0, "Aug": 29.0, "Sep": 27.0, "Oct": 22.0, |
|
|
"Nov": 17.0, "Dec": 13.0 |
|
|
}, |
|
|
|
|
|
"2B": { |
|
|
"Jan": 13.0, "Feb": 15.0, "Mar": 18.0, "Apr": 23.0, "May": 28.0, |
|
|
"Jun": 33.0, "Jul": 35.0, "Aug": 34.0, "Sep": 31.0, "Oct": 25.0, |
|
|
"Nov": 18.0, "Dec": 13.0 |
|
|
}, |
|
|
|
|
|
"3A": { |
|
|
"Jan": 6.0, "Feb": 8.0, "Mar": 12.0, "Apr": 17.0, "May": 21.0, |
|
|
"Jun": 25.0, "Jul": 27.0, "Aug": 26.0, "Sep": 23.0, "Oct": 18.0, |
|
|
"Nov": 12.0, "Dec": 7.0 |
|
|
}, |
|
|
|
|
|
"3B": { |
|
|
"Jan": 14.6, "Feb": 15.1, "Mar": 15.8, "Apr": 17.1, "May": 18.3, |
|
|
"Jun": 20.1, "Jul": 22.3, "Aug": 22.9, "Sep": 22.1, "Oct": 20.3, |
|
|
"Nov": 17.2, "Dec": 14.9 |
|
|
}, |
|
|
|
|
|
"3C": { |
|
|
"Jan": 10.0, "Feb": 11.0, "Mar": 12.0, "Apr": 13.0, "May": 14.0, |
|
|
"Jun": 16.0, "Jul": 17.0, "Aug": 17.0, "Sep": 18.0, "Oct": 16.0, |
|
|
"Nov": 13.0, "Dec": 10.0 |
|
|
}, |
|
|
|
|
|
"4A": { |
|
|
"Jan": 0.5, "Feb": 2.1, "Mar": 6.3, "Apr": 12.5, "May": 18.2, |
|
|
"Jun": 23.1, "Jul": 25.8, "Aug": 24.9, "Sep": 20.7, "Oct": 14.3, |
|
|
"Nov": 8.2, "Dec": 2.4 |
|
|
}, |
|
|
|
|
|
"4B": { |
|
|
"Jan": 3.0, "Feb": 5.0, "Mar": 9.0, "Apr": 14.0, "May": 19.0, |
|
|
"Jun": 24.0, "Jul": 26.0, "Aug": 25.0, "Sep": 21.0, "Oct": 15.0, |
|
|
"Nov": 8.0, "Dec": 3.0 |
|
|
}, |
|
|
|
|
|
"4C": { |
|
|
"Jan": 5.0, "Feb": 6.0, "Mar": 8.0, "Apr": 10.0, "May": 13.0, |
|
|
"Jun": 16.0, "Jul": 18.0, "Aug": 18.0, "Sep": 16.0, "Oct": 12.0, |
|
|
"Nov": 8.0, "Dec": 5.0 |
|
|
}, |
|
|
|
|
|
"5A": { |
|
|
"Jan": -3.5, "Feb": -1.2, "Mar": 4.1, "Apr": 10.3, "May": 16.5, |
|
|
"Jun": 22.1, "Jul": 24.8, "Aug": 23.9, "Sep": 19.7, "Oct": 12.8, |
|
|
"Nov": 5.2, "Dec": -1.4 |
|
|
}, |
|
|
|
|
|
"5B": { |
|
|
"Jan": 0.0, "Feb": 2.0, "Mar": 6.0, "Apr": 10.0, "May": 15.0, |
|
|
"Jun": 20.0, "Jul": 23.0, "Aug": 22.0, "Sep": 18.0, "Oct": 12.0, |
|
|
"Nov": 5.0, "Dec": 0.0 |
|
|
}, |
|
|
|
|
|
"5C": { |
|
|
"Jan": 3.0, "Feb": 4.0, "Mar": 6.0, "Apr": 9.0, "May": 12.0, |
|
|
"Jun": 15.0, "Jul": 17.0, "Aug": 17.0, "Sep": 14.0, "Oct": 10.0, |
|
|
"Nov": 6.0, "Dec": 3.0 |
|
|
}, |
|
|
|
|
|
"6A": { |
|
|
"Jan": -9.0, "Feb": -6.0, "Mar": 0.0, "Apr": 8.0, "May": 15.0, |
|
|
"Jun": 20.0, "Jul": 23.0, "Aug": 22.0, "Sep": 17.0, "Oct": 10.0, |
|
|
"Nov": 1.0, "Dec": -6.0 |
|
|
}, |
|
|
|
|
|
"6B": { |
|
|
"Jan": -5.0, "Feb": -2.0, "Mar": 2.0, "Apr": 7.0, "May": 12.0, |
|
|
"Jun": 17.0, "Jul": 21.0, "Aug": 20.0, "Sep": 15.0, "Oct": 9.0, |
|
|
"Nov": 1.0, "Dec": -4.0 |
|
|
}, |
|
|
|
|
|
"7": { |
|
|
"Jan": -12.0, "Feb": -9.0, "Mar": -3.0, "Apr": 5.0, "May": 12.0, |
|
|
"Jun": 17.0, "Jul": 20.0, "Aug": 19.0, "Sep": 14.0, "Oct": 7.0, |
|
|
"Nov": -1.0, "Dec": -9.0 |
|
|
}, |
|
|
|
|
|
"8": { |
|
|
"Jan": -20.0, "Feb": -16.0, "Mar": -10.0, "Apr": 0.0, "May": 10.0, |
|
|
"Jun": 16.0, "Jul": 18.0, "Aug": 15.0, "Sep": 8.0, "Oct": -2.0, |
|
|
"Nov": -12.0, "Dec": -18.0 |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if climate_zone in monthly_temps_raw: |
|
|
return convert_month_format(monthly_temps_raw[climate_zone]) |
|
|
else: |
|
|
|
|
|
return convert_month_format(monthly_temps_raw["4A"]) |
|
|
|
|
|
def _load_climate_locations(self) -> Dict[str, ClimateLocation]: |
|
|
""" |
|
|
Load climate location data. |
|
|
|
|
|
Returns: |
|
|
Dictionary of climate locations indexed by ID |
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] |
|
|
|
|
|
|
|
|
ny_temps = { |
|
|
"Jan": 0.5, "Feb": 2.1, "Mar": 6.3, "Apr": 12.5, "May": 18.2, |
|
|
"Jun": 23.1, "Jul": 25.8, "Aug": 24.9, "Sep": 20.7, "Oct": 14.3, |
|
|
"Nov": 8.2, "Dec": 2.4 |
|
|
} |
|
|
|
|
|
|
|
|
ny_humidity = { |
|
|
"Jan": 65, "Feb": 62, "Mar": 58, "Apr": 55, "May": 60, |
|
|
"Jun": 65, "Jul": 68, "Aug": 70, "Sep": 68, "Oct": 63, |
|
|
"Nov": 67, "Dec": 68 |
|
|
} |
|
|
|
|
|
|
|
|
la_temps = { |
|
|
"Jan": 14.6, "Feb": 15.1, "Mar": 15.8, "Apr": 17.1, "May": 18.3, |
|
|
"Jun": 20.1, "Jul": 22.3, "Aug": 22.9, "Sep": 22.1, "Oct": 20.3, |
|
|
"Nov": 17.2, "Dec": 14.9 |
|
|
} |
|
|
|
|
|
|
|
|
la_humidity = { |
|
|
"Jan": 63, "Feb": 67, "Mar": 70, "Apr": 71, "May": 74, |
|
|
"Jun": 75, "Jul": 76, "Aug": 76, "Sep": 74, "Oct": 70, |
|
|
"Nov": 65, "Dec": 63 |
|
|
} |
|
|
|
|
|
|
|
|
chi_temps = { |
|
|
"Jan": -3.5, "Feb": -1.2, "Mar": 4.1, "Apr": 10.3, "May": 16.5, |
|
|
"Jun": 22.1, "Jul": 24.8, "Aug": 23.9, "Sep": 19.7, "Oct": 12.8, |
|
|
"Nov": 5.2, "Dec": -1.4 |
|
|
} |
|
|
|
|
|
|
|
|
chi_humidity = { |
|
|
"Jan": 72, "Feb": 70, "Mar": 65, "Apr": 60, "May": 64, |
|
|
"Jun": 67, "Jul": 70, "Aug": 73, "Sep": 71, "Oct": 68, |
|
|
"Nov": 72, "Dec": 75 |
|
|
} |
|
|
|
|
|
|
|
|
lon_temps = { |
|
|
"Jan": 5.2, "Feb": 5.5, "Mar": 7.4, "Apr": 9.9, "May": 13.3, |
|
|
"Jun": 16.7, "Jul": 18.7, "Aug": 18.3, "Sep": 15.9, "Oct": 12.2, |
|
|
"Nov": 8.3, "Dec": 5.9 |
|
|
} |
|
|
|
|
|
|
|
|
lon_humidity = { |
|
|
"Jan": 84, "Feb": 80, "Mar": 76, "Apr": 72, "May": 70, |
|
|
"Jun": 70, "Jul": 71, "Aug": 72, "Sep": 75, "Oct": 80, |
|
|
"Nov": 84, "Dec": 86 |
|
|
} |
|
|
|
|
|
|
|
|
syd_temps = { |
|
|
"Jan": 23.5, "Feb": 23.4, "Mar": 22.1, "Apr": 19.5, "May": 16.5, |
|
|
"Jun": 14.1, "Jul": 13.4, "Aug": 14.5, "Sep": 16.6, "Oct": 18.8, |
|
|
"Nov": 20.6, "Dec": 22.6 |
|
|
} |
|
|
|
|
|
|
|
|
syd_humidity = { |
|
|
"Jan": 65, "Feb": 68, "Mar": 68, "Apr": 67, "May": 70, |
|
|
"Jun": 70, "Jul": 68, "Aug": 63, "Sep": 60, "Oct": 60, |
|
|
"Nov": 62, "Dec": 63 |
|
|
} |
|
|
|
|
|
|
|
|
locations = { |
|
|
"US-NY-NYC": ClimateLocation( |
|
|
id="US-NY-NYC", |
|
|
country="United States", |
|
|
state_province="New York", |
|
|
city="New York", |
|
|
latitude=40.7128, |
|
|
longitude=-74.0060, |
|
|
elevation=10.0, |
|
|
climate_zone="4A", |
|
|
heating_degree_days=2600, |
|
|
cooling_degree_days=1200, |
|
|
winter_design_temp=-8.3, |
|
|
summer_design_temp_db=32.8, |
|
|
summer_design_temp_wb=25.6, |
|
|
summer_daily_range=8.3, |
|
|
monthly_temps=ny_temps, |
|
|
monthly_humidity=ny_humidity |
|
|
), |
|
|
"US-CA-LAX": ClimateLocation( |
|
|
id="US-CA-LAX", |
|
|
country="United States", |
|
|
state_province="California", |
|
|
city="Los Angeles", |
|
|
latitude=34.0522, |
|
|
longitude=-118.2437, |
|
|
elevation=93.0, |
|
|
climate_zone="3B", |
|
|
heating_degree_days=800, |
|
|
cooling_degree_days=1200, |
|
|
winter_design_temp=8.3, |
|
|
summer_design_temp_db=32.2, |
|
|
summer_design_temp_wb=23.3, |
|
|
summer_daily_range=6.7, |
|
|
monthly_temps=la_temps, |
|
|
monthly_humidity=la_humidity |
|
|
), |
|
|
"US-IL-CHI": ClimateLocation( |
|
|
id="US-IL-CHI", |
|
|
country="United States", |
|
|
state_province="Illinois", |
|
|
city="Chicago", |
|
|
latitude=41.8781, |
|
|
longitude=-87.6298, |
|
|
elevation=179.0, |
|
|
climate_zone="5A", |
|
|
heating_degree_days=3500, |
|
|
cooling_degree_days=1000, |
|
|
winter_design_temp=-16.7, |
|
|
summer_design_temp_db=33.3, |
|
|
summer_design_temp_wb=25.6, |
|
|
summer_daily_range=8.9, |
|
|
monthly_temps=chi_temps, |
|
|
monthly_humidity=chi_humidity |
|
|
), |
|
|
"UK-LDN": ClimateLocation( |
|
|
id="UK-LDN", |
|
|
country="United Kingdom", |
|
|
state_province="England", |
|
|
city="London", |
|
|
latitude=51.5074, |
|
|
longitude=-0.1278, |
|
|
elevation=35.0, |
|
|
climate_zone="4A", |
|
|
heating_degree_days=2500, |
|
|
cooling_degree_days=200, |
|
|
winter_design_temp=-3.9, |
|
|
summer_design_temp_db=28.3, |
|
|
summer_design_temp_wb=20.0, |
|
|
summer_daily_range=10.0, |
|
|
monthly_temps=lon_temps, |
|
|
monthly_humidity=lon_humidity |
|
|
), |
|
|
"AU-NSW-SYD": ClimateLocation( |
|
|
id="AU-NSW-SYD", |
|
|
country="Australia", |
|
|
state_province="New South Wales", |
|
|
city="Sydney", |
|
|
latitude=-33.8688, |
|
|
longitude=151.2093, |
|
|
elevation=3.0, |
|
|
climate_zone="3C", |
|
|
heating_degree_days=600, |
|
|
cooling_degree_days=900, |
|
|
winter_design_temp=7.2, |
|
|
summer_design_temp_db=31.1, |
|
|
summer_design_temp_wb=24.4, |
|
|
summer_daily_range=7.8, |
|
|
monthly_temps=syd_temps, |
|
|
monthly_humidity=syd_humidity |
|
|
) |
|
|
} |
|
|
|
|
|
return locations |
|
|
|
|
|
def _group_locations_by_country_state(self) -> Dict[str, Dict[str, List[str]]]: |
|
|
""" |
|
|
Group locations by country and state/province. |
|
|
|
|
|
Returns: |
|
|
Nested dictionary of countries, states, and cities |
|
|
""" |
|
|
result = {} |
|
|
|
|
|
for loc in self.locations.values(): |
|
|
if loc.country not in result: |
|
|
result[loc.country] = {} |
|
|
|
|
|
if loc.state_province not in result[loc.country]: |
|
|
result[loc.country][loc.state_province] = [] |
|
|
|
|
|
result[loc.country][loc.state_province].append(loc.city) |
|
|
|
|
|
|
|
|
for country in result: |
|
|
for state in result[country]: |
|
|
result[country][state] = sorted(result[country][state]) |
|
|
|
|
|
return result |
|
|
|
|
|
def get_location(self, location_id: str) -> Optional[ClimateLocation]: |
|
|
""" |
|
|
Get climate location by ID. |
|
|
|
|
|
Args: |
|
|
location_id: Location identifier |
|
|
|
|
|
Returns: |
|
|
ClimateLocation object or None if not found |
|
|
""" |
|
|
return self.locations.get(location_id) |
|
|
|
|
|
def find_location(self, country: str, state_province: str = None, city: str = None) -> Optional[ClimateLocation]: |
|
|
""" |
|
|
Find a climate location by country, state/province, and city. |
|
|
|
|
|
Args: |
|
|
country: Country name |
|
|
state_province: State or province name (optional) |
|
|
city: City name (optional) |
|
|
|
|
|
Returns: |
|
|
ClimateLocation object or None if not found |
|
|
""" |
|
|
for loc in self.locations.values(): |
|
|
if loc.country == country: |
|
|
if state_province is None or loc.state_province == state_province: |
|
|
if city is None or loc.city == city: |
|
|
return loc |
|
|
return None |
|
|
|
|
|
def find_locations_by_climate_zone(self, climate_zone: str) -> List[ClimateLocation]: |
|
|
""" |
|
|
Find climate locations by climate zone. |
|
|
|
|
|
Args: |
|
|
climate_zone: ASHRAE climate zone |
|
|
|
|
|
Returns: |
|
|
List of ClimateLocation objects |
|
|
""" |
|
|
return [loc for loc in self.locations.values() if loc.climate_zone == climate_zone] |
|
|
|
|
|
def get_states_for_country(self, country: str) -> List[str]: |
|
|
""" |
|
|
Get states/provinces for a country. |
|
|
|
|
|
Args: |
|
|
country: Country name |
|
|
|
|
|
Returns: |
|
|
List of state/province names |
|
|
""" |
|
|
if country in self.country_states: |
|
|
return sorted(self.country_states[country].keys()) |
|
|
return [] |
|
|
|
|
|
def get_cities_for_state(self, country: str, state_province: str) -> List[str]: |
|
|
""" |
|
|
Get cities for a state/province. |
|
|
|
|
|
Args: |
|
|
country: Country name |
|
|
state_province: State or province name |
|
|
|
|
|
Returns: |
|
|
List of city names |
|
|
""" |
|
|
if country in self.country_states and state_province in self.country_states[country]: |
|
|
return self.country_states[country][state_province] |
|
|
return [] |
|
|
|
|
|
def get_location_id(self, country: str, state_province: str, city: str) -> Optional[str]: |
|
|
""" |
|
|
Get location ID for a city. |
|
|
|
|
|
Args: |
|
|
country: Country name |
|
|
state_province: State or province name |
|
|
city: City name |
|
|
|
|
|
Returns: |
|
|
Location ID or None if not found |
|
|
""" |
|
|
for loc_id, loc in self.locations.items(): |
|
|
if (loc.country == country and |
|
|
loc.state_province == state_province and |
|
|
loc.city == city): |
|
|
return loc_id |
|
|
return None |
|
|
|
|
|
def export_to_json(self, file_path: str) -> None: |
|
|
""" |
|
|
Export all climate data to a JSON file. |
|
|
|
|
|
Args: |
|
|
file_path: Path to the output JSON file |
|
|
""" |
|
|
data = {loc_id: loc.to_dict() for loc_id, loc in self.locations.items()} |
|
|
|
|
|
with open(file_path, 'w') as f: |
|
|
json.dump(data, f, indent=4) |
|
|
|
|
|
@classmethod |
|
|
def from_json(cls, file_path: str) -> 'ClimateData': |
|
|
""" |
|
|
Create a ClimateData instance from a JSON file. |
|
|
|
|
|
Args: |
|
|
file_path: Path to the input JSON file |
|
|
|
|
|
Returns: |
|
|
A new ClimateData instance |
|
|
""" |
|
|
with open(file_path, 'r') as f: |
|
|
data = json.load(f) |
|
|
|
|
|
climate_data = cls() |
|
|
climate_data.locations = {} |
|
|
|
|
|
for loc_id, loc_dict in data.items(): |
|
|
climate_data.locations[loc_id] = ClimateLocation( |
|
|
id=loc_dict["id"], |
|
|
country=loc_dict["country"], |
|
|
state_province=loc_dict["state_province"], |
|
|
city=loc_dict["city"], |
|
|
latitude=loc_dict["latitude"], |
|
|
longitude=loc_dict["longitude"], |
|
|
elevation=loc_dict["elevation"], |
|
|
climate_zone=loc_dict["climate_zone"], |
|
|
heating_degree_days=loc_dict["heating_degree_days"], |
|
|
cooling_degree_days=loc_dict["cooling_degree_days"], |
|
|
winter_design_temp=loc_dict["winter_design_temp"], |
|
|
summer_design_temp_db=loc_dict["summer_design_temp_db"], |
|
|
summer_design_temp_wb=loc_dict["summer_design_temp_wb"], |
|
|
summer_daily_range=loc_dict["summer_daily_range"], |
|
|
monthly_temps=loc_dict["monthly_temps"], |
|
|
monthly_humidity=loc_dict["monthly_humidity"] |
|
|
) |
|
|
|
|
|
climate_data.countries = sorted(list(set(loc.country for loc in climate_data.locations.values()))) |
|
|
climate_data.country_states = climate_data._group_locations_by_country_state() |
|
|
|
|
|
return climate_data |
|
|
|
|
|
|
|
|
|
|
|
climate_data = ClimateData() |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
climate_data.export_to_json(os.path.join(DATA_DIR, "climate_data.json")) |
|
|
|