From efef8a6983dbe0d59863fbd57a83dbfa3be05b9a Mon Sep 17 00:00:00 2001 From: Arno Schmerer Date: Fri, 24 May 2024 10:02:32 +0200 Subject: [PATCH] Add to_dict methods for JSON serialization - Implement to_dict methods in various classes to enable easy JSON serialization. - Update version to 1.0.0rc7 in setup.py for publication of the new version. These changes ensure that our objects can be easily converted to JSON, which is required for compatibility with Home Assistant. The version bump allows for the new features to be included in the next release. --- dwd_global_radiation/global_radiation.py | 123 +++++++++++++++++------ setup.py | 2 +- 2 files changed, 94 insertions(+), 31 deletions(-) diff --git a/dwd_global_radiation/global_radiation.py b/dwd_global_radiation/global_radiation.py index e5faba8..ffb6dbb 100644 --- a/dwd_global_radiation/global_radiation.py +++ b/dwd_global_radiation/global_radiation.py @@ -54,12 +54,29 @@ def __repr__(self): ] return ", ".join(parts) + def to_dict(self): + """Convert Location to a dictionary.""" + return { + "latitude": float(self.latitude), # Explicit conversion to float + "longitude": float(self.longitude), # Explicit conversion to float + "name": self.name, + "measurements": [measurement.to_dict() for measurement in self.measurements], + "forecasts": [forecast.to_dict() for forecast in self.forecasts] + } + @dataclass class MeasurementEntry: """Class for storing DWD global radiation measurement data.""" timestamp: float # assuming timestamp is a Unix time float sis: float # Solar Irradiance in W/m^2 + def to_dict(self): + """Convert MeasurementEntry to a dictionary.""" + return { + "timestamp": float(self.timestamp), # Explicit conversion to float + "sis": float(self.sis) # Explicit conversion to float + } + @dataclass class Measurement: @@ -69,7 +86,7 @@ class Measurement: grid_longitude: float distance: float nearest_index: int - measurement_values: list = field(default_factory=list) + measurement_values: list[MeasurementEntry] = field(default_factory=list) def __repr__(self): measurement_values_count=len(self.measurement_values) @@ -82,6 +99,16 @@ def __repr__(self): f"nearest_index={self.nearest_index}, " f"measurement_values=Count: {measurement_values_count})") + def to_dict(self): + """Convert Measurement to a dictionary.""" + return { + "grid_latitude": float(self.grid_latitude), # Explicit conversion to float + "grid_longitude": float(self.grid_longitude), # Explicit conversion to float + "distance": float(self.distance), # Explicit conversion to float + "nearest_index": int(self.nearest_index), # Explicit conversion to int + "measurement_values": [entry.to_dict() for entry in self.measurement_values] + } + def add_measurement_value(self, timestamp, sis): """Method for adding retrieved measurement values as SIS""" self.measurement_values.append(MeasurementEntry(timestamp, sis)) @@ -118,6 +145,17 @@ def __repr__(self): f"metadata=status '{metadata_status}')" ) + def to_dict(self): + """Convert Forecast to a dictionary.""" + return { + "issuance_time": float(self.issuance_time), # Explicit conversion to float + "grid_latitude": float(self.grid_latitude), # Explicit conversion to float + "grid_longitude": float(self.grid_longitude), # Explicit conversion to float + "distance": float(self.distance), # Explicit conversion to float + "entries": [entry.to_dict() for entry in self.entries], + "metadata": self.metadata + } + def set_metadata(self, standard_name=None, long_name=None, units=None): """Function for writing global radiation metadata of the DWD data file""" self.metadata["standard_name"] = standard_name @@ -146,6 +184,13 @@ class ForecastEntry: timestamp: str sis: float + def to_dict(self): + """Convert ForecastEntry to a dictionary.""" + return { + "timestamp": float(self.timestamp), # Explicit conversion to float + "sis": float(self.sis) # Explicit conversion to float + } + @dataclass class IndentConfig: """Handles indentation settings for pretty-printing data outputs.""" @@ -179,6 +224,18 @@ class GlobalRadiation: measurement_health_state: str = 'green' forecast_health_state: str = 'green' + def to_dict(self): + """Convert GlobalRadiation to a dictionary.""" + return { + "locations": [location.to_dict() for location in self.locations], + "last_measurement_fetch_date": self.last_measurement_fetch_date.isoformat( + ) if self.last_measurement_fetch_date else None, + "last_forecast_fetch_date": self.last_forecast_fetch_date.isoformat( + ) if self.last_forecast_fetch_date else None, + "measurement_health_state": self.measurement_health_state, + "forecast_health_state": self.forecast_health_state + } + def get_location_by_name(self, name): """Returns a location by its name, if exists.""" for location in self.locations: @@ -382,40 +439,46 @@ def _get_grid_data(self, all_grid_global_rad_data): return all_grid_global_rad_data, coordinates def _get_nearest_grid_point(self, latitude, longitude, grid_data): - all_grid_global_rad_data, _ = grid_data - - # Calculate approximate indices based on the grid resolution of 0.05 - lat_res = 0.05 - lon_res = 0.05 - - lat_min = np.floor(latitude / lat_res) * lat_res - lon_min = np.floor(longitude / lon_res) * lon_res + def calculate_candidate_points(lat, lon, lat_res=0.05, lon_res=0.05): + lat_min = np.floor(lat / lat_res) * lat_res + lon_min = np.floor(lon / lon_res) * lon_res + return [ + (lat_min, lon_min), + (lat_min, lon_min + lon_res), + (lat_min + lat_res, lon_min), + (lat_min + lat_res, lon_min + lon_res) + ] - # Four closest grid points to check - candidate_points = [ - (lat_min, lon_min), - (lat_min, lon_min + lon_res), - (lat_min + lat_res, lon_min), - (lat_min + lat_res, lon_min + lon_res) - ] + def find_nearest_point(lat, lon, candidate_points): + distances = [ + utils.haversine(lat, lon, c_lat, c_lon) + for c_lat, c_lon in candidate_points + ] + nearest_idx = np.argmin(distances) + return round(distances[nearest_idx], 3), candidate_points[nearest_idx] - # Compute distances for the four candidate points - distances = [ - utils.haversine(latitude, longitude, lat, lon) - for lat, lon in candidate_points - ] + def get_grid_indices(grid_lat, grid_lon, grid_data): + lat_var = grid_data.variables["lat"][:] + lon_var = grid_data.variables["lon"][:] + lat_idx = np.argmin(np.abs(lat_var - grid_lat)) + lon_idx = np.argmin(np.abs(lon_var - grid_lon)) + return lat_idx, lon_idx - nearest_index = np.argmin(distances) - nearest_distance = round(distances[nearest_index], 3) - grid_latitude, grid_longitude = candidate_points[nearest_index] + all_grid_global_rad_data, _ = grid_data + candidate_points = calculate_candidate_points(latitude, longitude) + nearest_distance, ( + grid_latitude, grid_longitude) = find_nearest_point( + latitude, longitude, candidate_points) + lat_index, lon_index = get_grid_indices( + grid_latitude, grid_longitude, all_grid_global_rad_data) - # Find the nearest index in the grid data - lat_var = all_grid_global_rad_data.variables["lat"][:] - lon_var = all_grid_global_rad_data.variables["lon"][:] - lat_index = np.argmin(np.abs(lat_var - grid_latitude)) - lon_index = np.argmin(np.abs(lon_var - grid_longitude)) + return ( + lat_index * len(all_grid_global_rad_data.variables["lon"][:]) + lon_index, + nearest_distance, + round(grid_latitude, 2), + round(grid_longitude, 2) + ) - return (lat_index * len(lon_var) + lon_index), nearest_distance, round(grid_latitude, 2), round(grid_longitude, 2) def _get_measurement_value_from_loaded_data( self, all_grid_global_rad_data, nearest_index diff --git a/setup.py b/setup.py index a4d253f..0da3d4c 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ setup( name='dwd_global_radiation', - version='1.0.0rc6', + version='1.0.0rc7', packages=find_packages(), description='Access and analyze DWD global radiation data and forecasts', long_description=long_description,