diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd11ffc89..a24bea6a2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,6 +74,6 @@ jobs: needs: build-and-test with: core_branch: ${{ github.ref }} - petals_branch: develop + petals_branch: feature/exposures_crs permissions: checks: write diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bf7b460f..19cb7818a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,12 +13,26 @@ Code freeze date: YYYY-MM-DD ### Added - `climada.util.interpolation` module for inter- and extrapolation util functions used in local exceedance intensity and return period functions [#930](https://github.com/CLIMADA-project/climada_python/pull/930) +- `climada.exposures.exposures.Exposures.geometry` property +- `climada.exposures.exposures.Exposures.latitude` property +- `climada.exposures.exposures.Exposures.longitude` property +- `climada.exposures.exposures.Exposures.value` property +- `climada.exposures.exposures.Exposures.region_id` property +- `climada.exposures.exposures.Exposures.category_id` property +- `climada.exposures.exposures.Exposures.cover` property +- `climada.exposures.exposures.Exposures.hazard_impf` method +- `climada.exposures.exposures.Exposures.hazard_centroids` method ### Changed - Improved scaling factors implemented in `climada.hazard.trop_cyclone.apply_climate_scenario_knu` to model the impact of climate changes to tropical cyclones [#734](https://github.com/CLIMADA-project/climada_python/pull/734) - In `climada.util.plot.geo_im_from_array`, NaNs are plotted in gray while cells with no centroid are not plotted [#929](https://github.com/CLIMADA-project/climada_python/pull/929) - Renamed `climada.util.plot.subplots_from_gdf` to `climada.util.plot.plot_from_gdf` [#929](https://github.com/CLIMADA-project/climada_python/pull/929) +- Exposures complete overhaul. Notably + - the _geometry_ column of the inherent `GeoDataFrame` is set up at initialization + - latitude and longitude column are no longer present there (the according arrays can be retrieved as properties of the Exposures object: `exp.latitude` instead of `exp.gdf.latitude.values`). + - `Exposures.gdf` has been renamed to `Exposures.data` (it still works though, as it is a property now pointing to the latter) + - the `check` method does not add a default "IMPF_" column to the GeoDataFrame anymore ### Fixed @@ -27,6 +41,10 @@ Code freeze date: YYYY-MM-DD ### Deprecated +- `climada.entity.exposures.Exposures.meta` attribute +- `climada.entity.exposures.Exposures.set_lat_lon` method +- `climada.entity.exposures.Exposures.set_geometry_points` method + ### Removed ## 5.0.0 diff --git a/climada/engine/forecast.py b/climada/engine/forecast.py index f123a67ed..49847cf5e 100644 --- a/climada/engine/forecast.py +++ b/climada/engine/forecast.py @@ -187,7 +187,7 @@ def __init__( if exposure_name is None: try: self.exposure_name = u_coord.country_to_iso( - exposure.gdf["region_id"].unique()[0], "name" + np.unique(exposure.region_id)[0], "name" ) except (KeyError, AttributeError): self.exposure_name = "custom" diff --git a/climada/engine/impact.py b/climada/engine/impact.py index 58292ab9c..b38e8c79c 100644 --- a/climada/engine/impact.py +++ b/climada/engine/impact.py @@ -259,10 +259,7 @@ def from_eih(cls, exposures, hazard, at_event, eai_exp, aai_agg, imp_mat=None): date=hazard.date, frequency=hazard.frequency, frequency_unit=hazard.frequency_unit, - coord_exp=np.stack( - [exposures.gdf["latitude"].values, exposures.gdf["longitude"].values], - axis=1, - ), + coord_exp=np.stack([exposures.latitude, exposures.longitude], axis=1), crs=exposures.crs, unit=exposures.value_unit, tot_value=exposures.centroids_total_value(hazard), @@ -733,9 +730,6 @@ def plot_raster_eai_exposure( cartopy.mpl.geoaxes.GeoAxesSubplot """ eai_exp = self._build_exp() - # we need to set geometry points because the `plot_raster` method accesses the - # exposures' `gdf.crs` property, which raises an error when geometry is not set - eai_exp.set_geometry_points() axis = eai_exp.plot_raster( res, raster_res, diff --git a/climada/engine/impact_calc.py b/climada/engine/impact_calc.py index 713cda324..d344750cf 100644 --- a/climada/engine/impact_calc.py +++ b/climada/engine/impact_calc.py @@ -119,6 +119,7 @@ def impact( apply_deductible_to_mat : apply deductible to impact matrix apply_cover_to_mat : apply cover to impact matrix """ + # TODO: consider refactoring, making use of Exposures.hazard_impf # check for compatibility of exposures and hazard type if all( name not in self.exposures.gdf.columns diff --git a/climada/engine/test/test_impact.py b/climada/engine/test/test_impact.py index 6c901f989..54e98e3eb 100644 --- a/climada/engine/test/test_impact.py +++ b/climada/engine/test/test_impact.py @@ -112,8 +112,7 @@ def test_from_eih_pass(self): np.testing.assert_array_almost_equal(imp.eai_exp, fake_eai_exp) np.testing.assert_array_almost_equal(imp.at_event, fake_at_event) np.testing.assert_array_almost_equal( - imp.coord_exp, - np.stack([exp.gdf["latitude"].values, exp.gdf["longitude"].values], axis=1), + imp.coord_exp, np.stack([exp.latitude, exp.longitude], axis=1) ) def test_pyproj_crs(self): @@ -987,9 +986,9 @@ def test__build_exp(self): imp = dummy_impact() exp = imp._build_exp() - np.testing.assert_array_equal(imp.eai_exp, exp.gdf["value"]) - np.testing.assert_array_equal(imp.coord_exp[:, 0], exp.gdf["latitude"]) - np.testing.assert_array_equal(imp.coord_exp[:, 1], exp.gdf["longitude"]) + np.testing.assert_array_equal(imp.eai_exp, exp.value) + np.testing.assert_array_equal(imp.coord_exp[:, 0], exp.latitude) + np.testing.assert_array_equal(imp.coord_exp[:, 1], exp.longitude) self.assertTrue(u_coord.equal_crs(exp.crs, imp.crs)) self.assertEqual(exp.value_unit, imp.unit) self.assertEqual(exp.ref_year, 0) @@ -1000,9 +999,9 @@ def test__exp_build_event(self): imp = dummy_impact() event_id = imp.event_id[1] exp = imp._build_exp_event(event_id=event_id) - np.testing.assert_array_equal(imp.imp_mat[1].todense().A1, exp.gdf["value"]) - np.testing.assert_array_equal(imp.coord_exp[:, 0], exp.gdf["latitude"]) - np.testing.assert_array_equal(imp.coord_exp[:, 1], exp.gdf["longitude"]) + np.testing.assert_array_equal(imp.imp_mat[1].todense().A1, exp.value) + np.testing.assert_array_equal(imp.coord_exp[:, 0], exp.latitude) + np.testing.assert_array_equal(imp.coord_exp[:, 1], exp.longitude) self.assertTrue(u_coord.equal_crs(exp.crs, imp.crs)) self.assertEqual(exp.value_unit, imp.unit) self.assertEqual(exp.ref_year, 0) diff --git a/climada/engine/test/test_impact_calc.py b/climada/engine/test/test_impact_calc.py index 3f19e2632..8a004cf67 100644 --- a/climada/engine/test/test_impact_calc.py +++ b/climada/engine/test/test_impact_calc.py @@ -50,8 +50,8 @@ def check_impact(self, imp, haz, exp, aai_agg, eai_exp, at_event, imp_mat_array= """Test properties of imapcts""" self.assertEqual(len(haz.event_id), len(imp.at_event)) self.assertIsInstance(imp, Impact) - np.testing.assert_allclose(imp.coord_exp[:, 0], exp.gdf["latitude"]) - np.testing.assert_allclose(imp.coord_exp[:, 1], exp.gdf["longitude"]) + np.testing.assert_allclose(imp.coord_exp[:, 0], exp.latitude) + np.testing.assert_allclose(imp.coord_exp[:, 1], exp.longitude) self.assertAlmostEqual(imp.aai_agg, aai_agg, 3) np.testing.assert_allclose(imp.eai_exp, eai_exp, rtol=1e-5) np.testing.assert_allclose(imp.at_event, at_event, rtol=1e-5) @@ -490,7 +490,12 @@ def test_minimal_exp_gdf(self): def test_stitch_impact_matrix(self): """Check how sparse matrices from a generator are stitched together""" icalc = ImpactCalc( - Exposures({"blank": [1, 2, 3, 4]}), ImpactFuncSet(), Hazard() + Exposures( + {"blank": [1, 2, 3, 4]}, + geometry=[], + ), + ImpactFuncSet(), + Hazard(), ) icalc.hazard.event_id = np.array([1, 2, 3]) icalc._orig_exp_idx = np.array([0, 1, 2, 3]) @@ -524,7 +529,14 @@ def test_apply_deductible_to_mat(self): def test_stitch_risk_metrics(self): """Test computing risk metrics from an impact matrix generator""" - icalc = ImpactCalc(Exposures({"blank": [1, 2, 3]}), ImpactFuncSet(), Hazard()) + icalc = ImpactCalc( + Exposures( + {"blank": [1, 2, 3]}, + geometry=[], + ), + ImpactFuncSet(), + Hazard(), + ) icalc.hazard.event_id = np.array([1, 2]) icalc.hazard.frequency = np.array([2, 0.5]) icalc._orig_exp_idx = np.array([0, 1, 2]) diff --git a/climada/engine/unsequa/calc_impact.py b/climada/engine/unsequa/calc_impact.py index 061b3e3a2..93d3fc658 100644 --- a/climada/engine/unsequa/calc_impact.py +++ b/climada/engine/unsequa/calc_impact.py @@ -240,7 +240,9 @@ def uncertainty( if calc_eai_exp: exp = self.exp_input_var.evaluate() - coord_df = exp.gdf[["latitude", "longitude"]] + coord_df = pd.DataFrame( + dict(latitude=exp.latitude, longitude=exp.longitude) + ) else: coord_df = pd.DataFrame([]) diff --git a/climada/entity/exposures/base.py b/climada/entity/exposures/base.py index 5087a237f..0c62af1b5 100644 --- a/climada/entity/exposures/base.py +++ b/climada/entity/exposures/base.py @@ -21,6 +21,7 @@ __all__ = ["Exposures", "add_sea", "INDICATOR_IMPF", "INDICATOR_CENTR"] + import copy import logging import warnings @@ -32,7 +33,8 @@ import numpy as np import pandas as pd import rasterio -from geopandas import GeoDataFrame +from deprecation import deprecated +from geopandas import GeoDataFrame, GeoSeries, points_from_xy from mpl_toolkits.axes_grid1 import make_axes_locatable from rasterio.warp import Resampling @@ -81,7 +83,7 @@ class Exposures: - """geopandas GeoDataFrame with metada and columns (pd.Series) defined in + """geopandas GeoDataFrame with metadata and columns (pd.Series) defined in Attributes. Attributes @@ -89,44 +91,19 @@ class Exposures: description : str metadata - description of content and origin of the data ref_year : int - metada - reference year + metadata - reference year value_unit : str - metada - unit of the exposures values - latitude : pd.Series - latitude - longitude : pd.Series - longitude - value : pd.Series - a value for each exposure - impf_SUFFIX : pd.Series, optional - e.g. impf_TC. impact functions id for hazard TC. - There might be different hazards defined: impf_TC, impf_FL, ... - If not provided, set to default ``impf_`` with ids 1 in check(). - geometry : pd.Series, optional - geometry of type Point of each instance. - Computed in method set_geometry_points(). - meta : dict - dictionary containing corresponding raster properties (if any): - width, height, crs and transform must be present at least (transform needs - to contain upper left corner!). Exposures might not contain all the points - of the corresponding raster. Not used in internal computations. - deductible : pd.Series, optional - deductible value for each exposure - cover : pd.Series, optional - cover value for each exposure - category_id : pd.Series, optional - category id for each exposure - region_id : pd.Series, optional - region id for each exposure - centr_SUFFIX : pd.Series, optional - e.g. centr_TC. centroids index for hazard - TC. There might be different hazards defined: centr_TC, centr_FL, ... - Computed in method assign_centroids(). + metadata - unit of the exposures values + data : GeoDataFrame + containing at least the columns 'geometry' and 'value' for locations and assets + optionally more, a.o., 'region_id', 'category_id', columns for (hazard specific) assigned + centroids and (hazard specific) impact funcitons. """ - _metadata = ["description", "ref_year", "value_unit", "meta"] + _metadata = ["description", "ref_year", "value_unit"] + """List of attributes, which are by default read, e.g., from hdf5""" - vars_oblig = ["value", "latitude", "longitude"] + vars_oblig = ["value", "geometry"] """Name of the variables needed to compute the impact.""" vars_def = [INDICATOR_IMPF, INDICATOR_IMPF_OLD] @@ -145,110 +122,308 @@ class Exposures: @property def crs(self): """Coordinate Reference System, refers to the crs attribute of the inherent GeoDataFrame""" + return self.data.geometry.crs + + @property + def gdf(self): + """Inherent GeoDataFrame""" + return self.data + + @property + def latitude(self): + """Latitude array of exposures""" + return self.data.geometry.y.values + + @property + def longitude(self): + """Longitude array of exposures""" + return self.data.geometry.x.values + + @property + def geometry(self): + """Geometry array of exposures""" + return self.data.geometry.values + + @property + def value(self): + """Geometry array of exposures""" + if "value" in self.data.columns: + return self.data["value"].values + return None + + @property + def region_id(self): + """Region id for each exposure + + Returns + ------- + np.array of int + """ + if "region_id" in self.data.columns: + return self.data["region_id"].values + return None + + @property + def category_id(self): + """Category id for each exposure + + Returns + ------- + np.array + """ + if "category_id" in self.data.columns: + return self.data["category_id"].values + return None + + @property + def cover(self): + """Cover value for each exposures + + Returns + ------- + np.array of float + """ + if "cover" in self.data.columns: + return self.data["cover"].values + return None + + @property + def deductible(self): + """Deductible value for each exposures + + Returns + ------- + np.array of float + """ + if "deductible" in self.data.columns: + return self.data["deductible"].values + return None + + def hazard_impf(self, haz_type=""): + """Get impact functions for a given hazard + + Parameters + ---------- + haz_type : str + hazard type, as in the hazard's.haz_type + which is the HAZ_TYPE constant of the hazard's module + + Returns + ------- + np.array of int + impact functions for the given hazard + """ + col_name = self.get_impf_column(haz_type) + return self.data[col_name].values + + def hazard_centroids(self, haz_type=""): + """Get centroids for a given hazard + + Parameters + ---------- + haz_type : str + hazard type, as in the hazard's.haz_type + which is the HAZ_TYPE constant of the hazard's module + + Returns + ------- + np.array of int + centroids index for the given hazard + """ + if haz_type and INDICATOR_CENTR + haz_type in self.data.columns: + return self.data[INDICATOR_CENTR + haz_type].values + if INDICATOR_CENTR in self.data.columns: + return self.data[INDICATOR_CENTR].values + raise ValueError("Missing hazard centroids.") + + def derive_raster(self): + """Metadata dictionary, containing raster information, derived from the geometry""" + if not self.data.size: + return None + _r, meta = u_coord.points_to_raster(self.data) + return meta + + @staticmethod + def _consolidate( + alternative_data, name, value, default=None, equals=lambda x, y: x == y + ): + """helper function for __init__ for consolidation of arguments. + Most arguments of the __init__ function can be provided either explicitly, by themselves + or as part of the input data. + This method finds the specific argument from these alternative sources. In case of ambiguity + it checks for any discrepancy. In case of missing souorces it returns the default. + + Parameters + ---------- + alternative_data: + container of general data with named items + name: + name of the item in the alternative_data + value: + specific data, could be None + default: + default value in case of both specific and general data don't yield a result + equals: + the equality function to check for discrepancies + """ + altvalue = alternative_data.get(name) + if value is None and altvalue is None: + return default + if value is None: + return altvalue + if altvalue is None: + return value try: - return self.gdf.geometry.crs or self.meta.get("crs") - except AttributeError: # i.e., no geometry, crs is assumed to be a property - # In case of gdf without geometry, empty or before set_geometry_points was called - return self.meta.get("crs") + if all(equals(altvalue, value)): + return value + except TypeError: + if equals(altvalue, value): + return value + raise ValueError( + f"conflicting arguments: the given {name}" + " is different from their corresponding value(s) in meta or data" + ) def __init__( self, - *args, + data=None, + index=None, + columns=None, + dtype=None, + copy=False, # pylint: disable=redefined-outer-name + geometry=None, + crs=None, meta=None, description=None, - ref_year=DEF_REF_YEAR, - value_unit=DEF_VALUE_UNIT, - crs=None, - **kwargs, + ref_year=None, + value_unit=None, + value=None, + lat=None, + lon=None, ): - """Creates an Exposures object from a GeoDataFrame - + """ Parameters ---------- - args : - Arguments of the GeoDataFrame constructor - kwargs : - Named arguments of the GeoDataFrame constructor, additionally + data : dict, iterable, DataFrame, GeoDataFrame, ndarray + data of the initial DataFrame, see ``pandas.DataFrame()``. + Used to initialize values for "region_id", "category_id", "cover", "deductible", + "value", "geometry", "impf_[hazard type]". + columns : Index or array, optional + Columns of the initial DataFrame, see ``pandas.DataFrame()``. + To be provided if `data` is an array + index : Index or array, optional + Columns of the initial DataFrame, see ``pandas.DataFrame()``. + can optionally be provided if `data` is an array or for defining a specific row index + dtype : dtype, optional + data type of the initial DataFrame, see ``pandas.DataFrame()``. + Can be used to assign specific data types to the columns in `data` + copy : bool, optional + Whether to make a copy of the input `data`, see ``pandas.DataFrame()``. + Default is False, i.e. by default `data` may be altered by the ``Exposures`` object. + geometry : array, optional + Geometry column, see ``geopandas.GeoDataFrame()``. + Must be provided if `lat` and `lon` are None and `data` has no "geometry" column. + crs : value, optional + Coordinate Reference System, see ``geopandas.GeoDataFrame()``. meta : dict, optional - Metadata dictionary. Default: {} (empty dictionary) + Metadata dictionary. Default: {} (empty dictionary). + May be used to provide any of `description`, `ref_year`, `value_unit` and `crs` description : str, optional Default: None ref_year : int, optional Reference Year. Defaults to the entry of the same name in `meta` or 2018. value_unit : str, optional Unit of the exposed value. Defaults to the entry of the same name in `meta` or 'USD'. - crs : object, anything accepted by pyproj.CRS.from_user_input - Coordinate reference system. Defaults to the entry of the same name in `meta`, or to - the CRS of the GeoDataFrame (if provided) or to 'epsg:4326'. + value : array, optional + Exposed value column. + Must be provided if `data` has no "value" column + lat : array, optional + Latitude column. + Can be provided together with `lon`, alternative to `geometry` + lon : array, optional + Longitude column. + Can be provided together with `lat`, alternative to `geometry` """ - # meta data - self.meta = {} if meta is None else meta - if not isinstance(self.meta, dict): - raise ValueError("meta must be a dictionary") - self.description = ( - self.meta.get("description") if description is None else description - ) - self.ref_year = ( - self.meta.get("ref_year", DEF_REF_YEAR) if ref_year is None else ref_year - ) - self.value_unit = ( - self.meta.get("value_unit", DEF_VALUE_UNIT) - if value_unit is None - else value_unit + geodata = GeoDataFrame( + data=data, index=index, columns=columns, dtype=dtype, copy=False ) - # remaining generic attributes from derived classes - for mda in type(self)._metadata: - if mda not in Exposures._metadata: - if mda in kwargs: - setattr(self, mda, kwargs.pop(mda)) - elif mda in self.meta: - setattr(self, mda, self.meta[mda]) - else: - setattr(self, mda, None) - - # crs (property) and geometry - data = args[0] if args else kwargs.get("data", {}) - try: - data_crs = data.geometry.crs - except AttributeError: - data_crs = None - if data_crs and data.crs and not u_coord.equal_crs(data_crs, data.crs): - raise ValueError("Inconsistent crs definition in data and data.geometry") - - crs = ( - crs - if crs is not None - else ( - self.meta["crs"] - if "crs" in self.meta - else data_crs if data_crs else None - ) + geometry = self._consolidate(geodata, "geometry", geometry) + value = self._consolidate(geodata, "value", value) + + # both column names are accepted, lat and latitude, respectively lon and longitude. + lat = self._consolidate(geodata, "latitude", lat) + lat = self._consolidate(geodata, "lat", lat) + lon = self._consolidate(geodata, "longitude", lon) + lon = self._consolidate(geodata, "lon", lon) + + # if lat then lon and vice versa: not xor + if (lat is None) ^ (lon is None): + raise ValueError("either provide both, lat and lon, or none of them") + # either geometry or lat/lon + if (lat is None) and (geometry is None): + if geodata.shape[0] == 0: + geodata = geodata.set_geometry([]) + geometry = geodata.geometry + else: + raise ValueError("either provide geometry or lat/lon") + + meta = meta or {} + if not isinstance(meta, dict): + raise TypeError("meta must be of type dict") + + self.description = self._consolidate(meta, "description", description) + self.ref_year = self._consolidate(meta, "ref_year", ref_year, DEF_REF_YEAR) + self.value_unit = self._consolidate( + meta, "value_unit", value_unit, DEF_VALUE_UNIT ) - if "crs" in self.meta and not u_coord.equal_crs(self.meta["crs"], crs): - raise ValueError( - "Inconsistent CRS definition, crs and meta arguments don't match" - ) - if data_crs and not u_coord.equal_crs(data_crs, crs): - raise ValueError( - "Inconsistent CRS definition, data doesn't match meta or crs argument" - ) - if not crs: - crs = DEF_CRS - geometry = kwargs.get("geometry") - if geometry and isinstance(geometry, str): - raise ValueError( + crs = self._consolidate(meta, "crs", crs, equals=u_coord.equal_crs) + + # finalize geometry, set crs + if geometry is None: # -> calculate from lat/lon + geometry = points_from_xy(x=lon, y=lat, crs=crs or DEF_CRS) + elif isinstance(geometry, str): # -> raise exception + raise TypeError( "Exposures is not able to handle customized 'geometry' column names." ) + elif isinstance(geometry, GeoSeries): # -> set crs if necessary + if crs and not u_coord.equal_crs(crs, geometry.crs): + geometry = geometry.set_crs(crs, allow_override=True) + if not crs and not geometry.crs: + geometry = geometry.set_crs(DEF_CRS) + else: # e.g. a list of Points -> turn into GeoSeries + geometry = GeoSeries(geometry, crs=crs or DEF_CRS) + + self.data = GeoDataFrame( + data=geodata.loc[ + :, + [ + c + for c in geodata.columns + if c not in ["geometry", "latitude", "longitude", "lat", "lon"] + ], + ], + copy=copy, + geometry=geometry, + ) - # make the data frame - self.set_gdf(GeoDataFrame(*args, **kwargs), crs=crs) + # add a 'value' column in case it is not already part of data + if value is not None and self.data.get("value") is None: + self.data["value"] = value def __str__(self): return "\n".join( [f"{md}: {self.__dict__[md]}" for md in type(self)._metadata] - + [f"crs: {self.crs}", "data:", str(self.gdf)] + + [ + f"crs: {self.crs}", + f"data: ({self.data.shape[0]} entries)", + ( + str(self.data) + if self.data.shape[0] < 10 + else str(pd.concat([self.data[:4], self.data[-4:]])) + ), + ] ) def _access_item(self, *args): @@ -266,8 +441,6 @@ def check(self): """Check Exposures consistency. Reports missing columns in log messages. - If no ``impf_*`` column is present in the dataframe, a default column ``impf_`` is added - with default impact function id 1. """ # mandatory columns for var in self.vars_oblig: @@ -293,8 +466,7 @@ def check(self): for col in self.gdf.columns if col.startswith(INDICATOR_IMPF) or col.startswith(INDICATOR_IMPF_OLD) ]: - LOGGER.info("Setting %s to default impact functions ids 1.", INDICATOR_IMPF) - self.gdf[INDICATOR_IMPF] = 1 + LOGGER.warning("There are no impact functions assigned to the exposures") # optional columns except centr_* for var in sorted(set(self.vars_opt).difference([INDICATOR_CENTR])): @@ -308,50 +480,16 @@ def check(self): elif not any([col.startswith(INDICATOR_CENTR) for col in self.gdf.columns]): LOGGER.info("%s not set.", INDICATOR_CENTR) - # check if CRS is consistent - if self.crs != self.meta.get("crs"): - raise ValueError( - f"Inconsistent CRS definition, gdf ({self.crs}) attribute doesn't " - f"match meta ({self.meta.get('crs')}) attribute." - ) - - # check whether geometry corresponds to lat/lon - try: - if ( - self.gdf.geometry.values[0].x != self.gdf["longitude"].values[0] - or self.gdf.geometry.values[0].y != self.gdf["latitude"].values[0] - ): - raise ValueError( - "Geometry values do not correspond to latitude and" - + " longitude. Use set_geometry_points() or set_lat_lon()." - ) - except AttributeError: # no geometry column - pass - - def set_crs(self, crs=None): + def set_crs(self, crs=DEF_CRS): """Set the Coordinate Reference System. If the epxosures GeoDataFrame has a 'geometry' column it will be updated too. Parameters ---------- crs : object, optional - anything anything accepted by pyproj.CRS.from_user_input - if the original value is None it will be set to the default CRS. + anything anything accepted by pyproj.CRS.from_user_input. """ - # clear the meta dictionary entry - if "crs" in self.meta: - old_crs = self.meta.pop("crs") - crs = crs if crs else self.crs if self.crs else DEF_CRS - # adjust the dataframe - if "geometry" in self.gdf.columns: - try: - self.gdf.set_crs(crs, inplace=True) - except ValueError: - # restore popped crs and leave - self.meta["crs"] = old_crs - raise - # store the value - self.meta["crs"] = crs + self.data.geometry.set_crs(crs, inplace=True, allow_override=True) def set_gdf(self, gdf: GeoDataFrame, crs=None): """Set the `gdf` GeoDataFrame and update the CRS @@ -367,9 +505,7 @@ def set_gdf(self, gdf: GeoDataFrame, crs=None): if not isinstance(gdf, GeoDataFrame): raise ValueError("gdf is not a GeoDataFrame") # set the dataframe - self.gdf = gdf - # update the coordinate reference system - self.set_crs(crs) + self.data = Exposures(data=gdf, crs=crs).data def get_impf_column(self, haz_type=""): """Find the best matching column name in the exposures dataframe for a given hazard type, @@ -503,30 +639,30 @@ def assign_centroids( ) self.gdf[centr_haz] = assigned_centr + @deprecated( + details="Obsolete method call. As of climada 5.0, geometry points are set during" + " object initialization" + ) def set_geometry_points(self, scheduler=None): - """Set geometry attribute of GeoDataFrame with Points from latitude and - longitude attributes. - - Parameters - ---------- - scheduler : str, optional - used for dask map_partitions. - “threads”, “synchronous” or “processes” - """ - u_coord.set_df_geometry_points(self.gdf, scheduler=scheduler, crs=self.crs) + """obsolete and deprecated since climada 5.0""" + @deprecated( + details="latitude and longitude columns are no longer meaningful in Exposures`" + " GeoDataFrames. They can be retrieved from Exposures.latitude and .longitude" + " properties" + ) def set_lat_lon(self): """Set latitude and longitude attributes from geometry attribute.""" LOGGER.info("Setting latitude and longitude attributes.") - self.gdf["latitude"] = self.gdf.geometry[:].y - self.gdf["longitude"] = self.gdf.geometry[:].x + self.data["latitude"] = self.latitude + self.data["longitude"] = self.longitude + @deprecated( + details="The use of Exposures.set_from_raster is deprecated." + " Use Exposures.from_raster instead." + ) def set_from_raster(self, *args, **kwargs): """This function is deprecated, use Exposures.from_raster instead.""" - LOGGER.warning( - "The use of Exposures.set_from_raster is deprecated." - "Use Exposures.from_raster instead." - ) self.__dict__ = Exposures.from_raster(*args, **kwargs).__dict__ @classmethod @@ -658,11 +794,11 @@ def plot_scatter( pos_vals = self.gdf["value"][mask].values > 0 else: pos_vals = np.ones((self.gdf["value"][mask].values.size,), dtype=bool) - value = self.gdf["value"][mask][pos_vals].values + value = self.gdf.value[mask][pos_vals].values coord = np.stack( [ - self.gdf["latitude"][mask][pos_vals].values, - self.gdf["longitude"][mask][pos_vals].values, + self.gdf.geometry[mask][pos_vals].y.values, + self.gdf.geometry[mask][pos_vals].x.values, ], axis=1, ) @@ -747,8 +883,8 @@ def plot_hexbin( value = self.gdf["value"][mask][pos_vals].values coord = np.stack( [ - self.gdf["latitude"][mask][pos_vals].values, - self.gdf["longitude"][mask][pos_vals].values, + self.gdf.geometry[mask][pos_vals].y.values, + self.gdf.geometry[mask][pos_vals].x.values, ], axis=1, ) @@ -820,27 +956,22 @@ def plot_raster( ------- matplotlib.figure.Figure, cartopy.mpl.geoaxes.GeoAxesSubplot """ - if self.meta and self.meta.get("height", 0) * self.meta.get("height", 0) == len( - self.gdf - ): - raster = self.gdf["value"].values.reshape( - (self.meta["height"], self.meta["width"]) - ) - # check raster starts by upper left corner - if self.gdf["latitude"].values[0] < self.gdf["latitude"].values[-1]: - raster = np.flip(raster, axis=0) - if self.gdf["longitude"].values[0] > self.gdf["longitude"].values[-1]: - raise ValueError("Points are not ordered according to meta raster.") - else: - raster, meta = u_coord.points_to_raster( - self.gdf, ["value"], res, raster_res, scheduler - ) - raster = raster.reshape((meta["height"], meta["width"])) + + raster, meta = u_coord.points_to_raster( + points_df=self.data, + val_names=["value"], + res=res, + raster_res=raster_res, + crs=self.crs, + scheduler=None, + ) + raster = raster.reshape((meta["height"], meta["width"])) + # save tiff if save_tiff is not None: with rasterio.open( - save_tiff, - "w", + fp=save_tiff, + mode="w", driver="GTiff", height=meta["height"], width=meta["width"], @@ -856,15 +987,15 @@ def plot_raster( if isinstance(proj_data, ccrs.PlateCarree): # use different projections for plot and data to shift the central lon in the plot xmin, ymin, xmax, ymax = u_coord.latlon_bounds( - self.gdf["latitude"].values, self.gdf["longitude"].values + lat=self.latitude, lon=self.longitude ) proj_plot = ccrs.PlateCarree(central_longitude=0.5 * (xmin + xmax)) else: xmin, ymin, xmax, ymax = ( - self.gdf["longitude"].min(), - self.gdf["latitude"].min(), - self.gdf["longitude"].max(), - self.gdf["latitude"].max(), + self.longitude.min(), + self.latitude.min(), + self.longitude.max(), + self.latitude.max(), ) if not axis: @@ -945,8 +1076,6 @@ def plot_basemap( ------- matplotlib.figure.Figure, cartopy.mpl.geoaxes.GeoAxesSubplot """ - if "geometry" not in self.gdf: - self.set_geometry_points() crs_ori = self.crs self.to_crs(epsg=3857, inplace=True) axis = self.plot_scatter( @@ -988,6 +1117,7 @@ def write_hdf5(self, file_name): var_meta = {} for var in type(self)._metadata: var_meta[var] = getattr(self, var) + var_meta["crs"] = self.crs store.get_storer("exposures").attrs.metadata = var_meta store.close() @@ -1009,8 +1139,9 @@ def from_hdf5(cls, file_name): file_name : str (path and) file name to read from. additional_vars : list - list of additional variable names to read that - are not in exposures.base._metadata + list of additional variable names, other than the attributes of the Exposures class, + whose values are to be read into the Exposures object + class. Returns ------- @@ -1118,9 +1249,7 @@ def to_crs(self, crs=None, epsg=None, inplace=False): raise ValueError("one of crs or epsg must be None") if inplace: - self.gdf.to_crs(crs, epsg, True) - self.meta["crs"] = crs or f"EPSG:{epsg}" - self.set_lat_lon() + self.data.to_crs(crs, epsg, True) return None exp = self.copy() @@ -1157,21 +1286,10 @@ def write_raster(self, file_name, value_name="value", scheduler=None): file_name : str name output file in tif format """ - if self.meta and self.meta["height"] * self.meta["width"] == len(self.gdf): - raster = self.gdf[value_name].values.reshape( - (self.meta["height"], self.meta["width"]) - ) - # check raster starts by upper left corner - if self.gdf["latitude"].values[0] < self.gdf["latitude"].values[-1]: - raster = np.flip(raster, axis=0) - if self.gdf["longitude"].values[0] > self.gdf["longitude"].values[-1]: - raise ValueError("Points are not ordered according to meta raster.") - u_coord.write_raster(file_name, raster, self.meta) - else: - raster, meta = u_coord.points_to_raster( - self.gdf, [value_name], scheduler=scheduler - ) - u_coord.write_raster(file_name, raster, meta) + raster, meta = u_coord.points_to_raster( + self.gdf, [value_name], scheduler=scheduler + ) + u_coord.write_raster(file_name, raster, meta) @staticmethod def concat(exposures_list): @@ -1322,10 +1440,10 @@ def add_sea(exposures, sea_res, scheduler=None): sea_res = (sea_res[0] / ONE_LAT_KM, sea_res[1] / ONE_LAT_KM) - min_lat = max(-90, float(exposures.gdf["latitude"].min()) - sea_res[0]) - max_lat = min(90, float(exposures.gdf["latitude"].max()) + sea_res[0]) - min_lon = max(-180, float(exposures.gdf["longitude"].min()) - sea_res[0]) - max_lon = min(180, float(exposures.gdf["longitude"].max()) + sea_res[0]) + min_lat = max(-90, float(exposures.latitude.min()) - sea_res[0]) + max_lat = min(90, float(exposures.latitude.max()) + sea_res[0]) + min_lon = max(-180, float(exposures.longitude.min()) - sea_res[0]) + max_lon = min(180, float(exposures.longitude.max()) + sea_res[0]) lat_arr = np.arange(min_lat, max_lat + sea_res[1], sea_res[1]) lon_arr = np.arange(min_lon, max_lon + sea_res[1], sea_res[1]) @@ -1355,7 +1473,6 @@ def add_sea(exposures, sea_res, scheduler=None): crs=exposures.crs, ref_year=exposures.ref_year, value_unit=exposures.value_unit, - meta=exposures.meta, description=exposures.description, ) diff --git a/climada/entity/exposures/litpop/litpop.py b/climada/entity/exposures/litpop/litpop.py index 372e58533..f70beea4c 100644 --- a/climada/entity/exposures/litpop/litpop.py +++ b/climada/entity/exposures/litpop/litpop.py @@ -44,7 +44,7 @@ class LitPop(Exposures): """ - Holds geopandas GeoDataFrame with metada and columns (pd.Series) defined in + Holds geopandas GeoDataFrame with metadata and columns (pd.Series) defined in Attributes of Exposures class. LitPop exposure values are disaggregated proportional to a combination of nightlight intensity (NASA) and Gridded Population data (SEDAC). @@ -71,6 +71,23 @@ class LitPop(Exposures): _metadata = Exposures._metadata + ["exponents", "fin_mode", "gpw_version"] + def __init__( + self, + *args, + meta=None, + exponents=None, + fin_mode=None, + gpw_version=None, + **kwargs, + ): + super().__init__(*args, meta=meta, **kwargs) + meta = meta or {} + self.exponents = Exposures._consolidate(meta, "exponents", exponents, (1, 1)) + self.fin_mode = Exposures._consolidate(meta, "fin_mode", fin_mode, "pc") + self.gpw_version = Exposures._consolidate( + meta, "gpw_version", gpw_version, GPW_VERSION + ) + def set_countries(self, *args, **kwargs): """This function is deprecated, use LitPop.from_countries instead.""" LOGGER.warning( @@ -235,12 +252,12 @@ def from_countries( try: rows, cols, ras_trans = u_coord.pts_to_raster_meta( ( - exp.gdf["longitude"].min(), - exp.gdf["latitude"].min(), - exp.gdf["longitude"].max(), - exp.gdf["latitude"].max(), + exp.longitude.min(), + exp.latitude.min(), + exp.longitude.max(), + exp.latitude.max(), ), - u_coord.get_resolution(exp.gdf["longitude"], exp.gdf["latitude"]), + u_coord.get_resolution(exp.longitude, exp.latitude), ) exp.meta = { "width": cols, @@ -554,12 +571,12 @@ def from_shape_and_countries( try: rows, cols, ras_trans = u_coord.pts_to_raster_meta( ( - exp.gdf["longitude"].min(), - exp.gdf["latitude"].min(), - exp.gdf["longitude"].max(), - exp.gdf["latitude"].max(), + exp.longitude.min(), + exp.latitude.min(), + exp.longitude.max(), + exp.latitude.max(), ), - u_coord.get_resolution(exp.gdf["longitude"], exp.gdf["latitude"]), + u_coord.get_resolution(exp.longitude, exp.latitude), ) exp.meta = { "width": cols, @@ -688,18 +705,19 @@ def from_shape( ) if ( - min(len(exp.gdf["latitude"].unique()), len(exp.gdf["longitude"].unique())) - > 1 + exp.gdf.shape[0] > 1 + and exp.longitude.max() > exp.longitude.min() + and exp.latitude.max() > exp.latitude.min() ): - # if exp.gdf.shape[0] > 1 and len(exp.gdf.latitude.unique()) > 1: + # if exp.gdf.shape[0] > 1 and len(exp.latitude.unique()) > 1: rows, cols, ras_trans = u_coord.pts_to_raster_meta( ( - exp.gdf["longitude"].min(), - exp.gdf["latitude"].min(), - exp.gdf["longitude"].max(), - exp.gdf["latitude"].max(), + exp.longitude.min(), + exp.latitude.min(), + exp.longitude.max(), + exp.latitude.max(), ), - u_coord.get_resolution(exp.gdf["longitude"], exp.gdf["latitude"]), + u_coord.get_resolution(exp.longitude, exp.latitude), ) exp.meta = { "width": cols, @@ -949,7 +967,7 @@ def _get_litpop_single_polygon( gdf["region_id"] = region_id else: gdf["region_id"] = u_coord.get_country_code( - gdf["latitude"], gdf["longitude"], gridded=True + gdf.geometry.y, gdf.geometry.x, gridded=True ) # remove entries outside polygon with `dropna` and return GeoDataFrame: return gdf.dropna(), meta_out diff --git a/climada/entity/exposures/test/test_base.py b/climada/entity/exposures/test/test_base.py index 6650719a5..7c79f4a22 100644 --- a/climada/entity/exposures/test/test_base.py +++ b/climada/entity/exposures/test/test_base.py @@ -27,6 +27,7 @@ import rasterio import scipy as sp from rasterio.windows import Window +from shapely.geometry import Point from sklearn.metrics import DistanceMetric import climada.util.coordinates as u_coord @@ -74,20 +75,20 @@ def test_assign_pass(self): ) ncentroids = haz.centroids.size - exp = Exposures(crs=haz.centroids.crs) - - # some are matching exactly, some are geographically close - exp.gdf["longitude"] = np.concatenate( - [ - haz.centroids.lon, - haz.centroids.lon + 0.001 * (-0.5 + np_rand.rand(ncentroids)), - ] - ) - exp.gdf["latitude"] = np.concatenate( - [ - haz.centroids.lat, - haz.centroids.lat + 0.001 * (-0.5 + np_rand.rand(ncentroids)), - ] + exp = Exposures( + crs=haz.centroids.crs, + lon=np.concatenate( + [ + haz.centroids.lon, + haz.centroids.lon + 0.001 * (-0.5 + np_rand.rand(ncentroids)), + ] + ), + lat=np.concatenate( + [ + haz.centroids.lat, + haz.centroids.lat + 0.001 * (-0.5 + np_rand.rand(ncentroids)), + ] + ), ) expected_result = np.concatenate([np.arange(ncentroids), np.arange(ncentroids)]) @@ -96,25 +97,21 @@ def test_assign_pass(self): haz.centroids.gdf["lat"] = haz.centroids.lat.astype(test_dtype) haz.centroids.gdf["lon"] = haz.centroids.lon.astype(test_dtype) exp.assign_centroids(haz) - self.assertEqual(exp.gdf.shape[0], len(exp.gdf[INDICATOR_CENTR + "FL"])) - np.testing.assert_array_equal( - exp.gdf[INDICATOR_CENTR + "FL"].values, expected_result - ) + self.assertEqual(exp.gdf.shape[0], len(exp.hazard_centroids("FL"))) + np.testing.assert_array_equal(exp.hazard_centroids("FL"), expected_result) exp.assign_centroids(Hazard(), overwrite=False) - self.assertEqual(exp.gdf.shape[0], len(exp.gdf[INDICATOR_CENTR + "FL"])) - np.testing.assert_array_equal( - exp.gdf[INDICATOR_CENTR + "FL"].values, expected_result - ) + self.assertEqual(exp.gdf.shape[0], len(exp.hazard_centroids("FL"))) + np.testing.assert_array_equal(exp.hazard_centroids("FL"), expected_result) def test__init__meta_type(self): """Check if meta of type list raises a ValueError in __init__""" - with self.assertRaises(ValueError) as cm: - Exposures(meta=[]) - self.assertEqual("meta must be a dictionary", str(cm.exception)) + with self.assertRaises(TypeError) as cm: + Exposures(meta="{}") + self.assertEqual("meta must be of type dict", str(cm.exception)) def test__init__geometry_type(self): """Check that initialization fails when `geometry` is given as a `str` argument""" - with self.assertRaises(ValueError) as cm: + with self.assertRaises(TypeError) as cm: Exposures(geometry="myname") self.assertEqual( "Exposures is not able to handle customized 'geometry' column names.", @@ -135,17 +132,17 @@ def test_read_raster_pass(self): exp.check() self.assertTrue(u_coord.equal_crs(exp.crs, DEF_CRS)) self.assertAlmostEqual( - exp.gdf["latitude"].max(), 10.248220966978932 - 0.009000000000000341 / 2 + exp.latitude.max(), 10.248220966978932 - 0.009000000000000341 / 2 ) self.assertAlmostEqual( - exp.gdf["latitude"].min(), + exp.latitude.min(), 10.248220966978932 - 0.009000000000000341 / 2 - 59 * 0.009000000000000341, ) self.assertAlmostEqual( - exp.gdf["longitude"].min(), -69.2471495969998 + 0.009000000000000341 / 2 + exp.longitude.min(), -69.2471495969998 + 0.009000000000000341 / 2 ) self.assertAlmostEqual( - exp.gdf["longitude"].max(), + exp.longitude.max(), -69.2471495969998 + 0.009000000000000341 / 2 + 49 * 0.009000000000000341, ) self.assertEqual(len(exp.gdf), 60 * 50) @@ -166,9 +163,9 @@ def test_assign_raster_pass(self): haz = Hazard("FL", centroids=Centroids.from_meta(meta)) # explicit points with known results (see `expected_result` for details) - exp = Exposures(crs=DEF_CRS) - exp.gdf["longitude"] = np.array( - [ + exp = Exposures( + crs=DEF_CRS, + lon=[ -20.1, -20.0, -19.8, @@ -190,10 +187,8 @@ def test_assign_raster_pass(self): -6.4, 9.8, 0.0, - ] - ) - exp.gdf["latitude"] = np.array( - [ + ], + lat=[ 7.3, 7.3, 7.3, @@ -215,7 +210,7 @@ def test_assign_raster_pass(self): -1.9, -1.7, 0.0, - ] + ], ) exp.assign_centroids(haz) @@ -269,8 +264,8 @@ def test_assign_raster_same_pass(self): def test_assign_large_hazard_subset_pass(self): """Test assign_centroids with raster hazard""" exp = Exposures.from_raster(HAZ_DEMO_FL, window=Window(10, 20, 50, 60)) - exp.gdf["latitude"][[0, 1]] = exp.gdf["latitude"][[1, 0]] - exp.gdf["longitude"][[0, 1]] = exp.gdf["longitude"][[1, 0]] + exp.latitude[[0, 1]] = exp.latitude[[1, 0]] + exp.longitude[[0, 1]] = exp.longitude[[1, 0]] exp.check() haz = Hazard.from_raster([HAZ_DEMO_FL], haz_type="FL") exp.assign_centroids(haz) @@ -278,10 +273,10 @@ def test_assign_large_hazard_subset_pass(self): sel_cen=exp.gdf[INDICATOR_CENTR + "FL"].values ) np.testing.assert_array_equal( - np.unique(assigned_centroids.lat), np.unique(exp.gdf["latitude"]) + np.unique(assigned_centroids.lat), np.unique(exp.latitude) ) np.testing.assert_array_equal( - np.unique(assigned_centroids.lon), np.unique(exp.gdf["longitude"]) + np.unique(assigned_centroids.lon), np.unique(exp.longitude) ) def test_affected_total_value(self): @@ -340,60 +335,34 @@ class TestChecker(unittest.TestCase): def test_error_logs_fail(self): """Wrong exposures definition""" expo = good_exposures() - expo.gdf.drop(["longitude"], inplace=True, axis=1) + expo.gdf.drop(["value"], inplace=True, axis=1) with self.assertRaises(ValueError) as cm: expo.check() - self.assertIn("longitude missing", str(cm.exception)) + self.assertIn("value missing", str(cm.exception)) def test_error_logs_wrong_crs(self): """Ambiguous crs definition""" - expo = good_exposures() - expo.set_geometry_points() # sets crs to 4326 + expo = good_exposures() # epsg:4326 # all good _expo = Exposures(expo.gdf, meta={"crs": 4326}, crs=DEF_CRS) + self.assertEqual(expo.crs, _expo.crs) - with self.assertRaises(ValueError) as cm: - _expo = Exposures(expo.gdf, meta={"crs": 4230}, crs=4326) - self.assertIn( - "Inconsistent CRS definition, crs and meta arguments don't match", - str(cm.exception), - ) - - with self.assertRaises(ValueError) as cm: - _expo = Exposures(expo.gdf, meta={"crs": 4230}) - self.assertIn( - "Inconsistent CRS definition, data doesn't match meta or crs argument", - str(cm.exception), - ) + # still good: crs in argument and meta override crs from data frame + _expo = Exposures(expo.gdf, meta={"crs": 4230}) + self.assertNotEqual(expo.crs, _expo.crs) - with self.assertRaises(ValueError) as cm: - _expo = Exposures(expo.gdf, crs="epsg:4230") - self.assertIn( - "Inconsistent CRS definition, data doesn't match meta or crs argument", - str(cm.exception), - ) + _expo = Exposures(expo.gdf, crs="epsg:4230") + self.assertTrue(u_coord.equal_crs(_expo.crs, 4230)) - _expo = Exposures(expo.gdf) - _expo.meta["crs"] = "epsg:4230" + # bad: direct and indirect (meta) argument conflict with self.assertRaises(ValueError) as cm: - _expo.check() + _expo = Exposures(expo.gdf, meta={"crs": 4230}, crs=4326) self.assertIn( - "Inconsistent CRS definition, gdf (EPSG:4326) attribute doesn't match " - "meta (epsg:4230) attribute.", - str(cm.exception), + "conflicting arguments: the given crs is different", str(cm.exception) ) - def test_error_geometry_fail(self): - """Wrong exposures definition""" - expo = good_exposures() - expo.set_geometry_points() - expo.gdf["latitude"].values[0] = 5 - - with self.assertRaises(ValueError): - expo.check() - class TestIO(unittest.TestCase): """Check constructor Exposures through DataFrames readers""" @@ -410,7 +379,6 @@ def test_read_template_pass(self): def test_io_hdf5_pass(self): """write and read hdf5""" exp_df = Exposures(pd.read_excel(ENT_TEMPLATE_XLS), crs="epsg:32632") - exp_df.set_geometry_points() exp_df.check() # set metadata exp_df.ref_year = 2020 @@ -430,49 +398,40 @@ def test_io_hdf5_pass(self): self.assertEqual(exp_df.ref_year, exp_read.ref_year) self.assertEqual(exp_df.value_unit, exp_read.value_unit) - self.assertDictEqual(exp_df.meta, exp_read.meta) - self.assertTrue(u_coord.equal_crs(exp_df.crs, exp_read.crs)) - self.assertTrue(u_coord.equal_crs(exp_df.gdf.crs, exp_read.gdf.crs)) self.assertEqual(exp_df.description, exp_read.description) + np.testing.assert_array_equal(exp_df.latitude, exp_read.latitude) + np.testing.assert_array_equal(exp_df.longitude, exp_read.longitude) + np.testing.assert_array_equal(exp_df.value, exp_read.value) np.testing.assert_array_equal( - exp_df.gdf["latitude"].values, exp_read.gdf["latitude"].values + exp_df.data["deductible"].values, exp_read.data["deductible"].values ) np.testing.assert_array_equal( - exp_df.gdf["longitude"].values, exp_read.gdf["longitude"].values + exp_df.data["cover"].values, exp_read.data["cover"].values ) np.testing.assert_array_equal( - exp_df.gdf["value"].values, exp_read.gdf["value"].values + exp_df.data["region_id"].values, exp_read.data["region_id"].values ) np.testing.assert_array_equal( - exp_df.gdf["deductible"].values, exp_read.gdf["deductible"].values + exp_df.data["category_id"].values, exp_read.data["category_id"].values ) np.testing.assert_array_equal( - exp_df.gdf["cover"].values, exp_read.gdf["cover"].values + exp_df.data["impf_TC"].values, exp_read.data["impf_TC"].values ) np.testing.assert_array_equal( - exp_df.gdf["region_id"].values, exp_read.gdf["region_id"].values + exp_df.data["centr_TC"].values, exp_read.data["centr_TC"].values ) np.testing.assert_array_equal( - exp_df.gdf["category_id"].values, exp_read.gdf["category_id"].values + exp_df.data["impf_FL"].values, exp_read.data["impf_FL"].values ) np.testing.assert_array_equal( - exp_df.gdf["impf_TC"].values, exp_read.gdf["impf_TC"].values - ) - np.testing.assert_array_equal( - exp_df.gdf["centr_TC"].values, exp_read.gdf["centr_TC"].values - ) - np.testing.assert_array_equal( - exp_df.gdf["impf_FL"].values, exp_read.gdf["impf_FL"].values - ) - np.testing.assert_array_equal( - exp_df.gdf["centr_FL"].values, exp_read.gdf["centr_FL"].values + exp_df.data["centr_FL"].values, exp_read.data["centr_FL"].values ) - for point_df, point_read in zip( - exp_df.gdf.geometry.values, exp_read.gdf.geometry.values - ): - self.assertEqual(point_df.x, point_read.x) - self.assertEqual(point_df.y, point_read.y) + self.assertTrue( + u_coord.equal_crs(exp_df.crs, exp_read.crs), + f"{exp_df.crs} and {exp_read.crs} are different", + ) + self.assertTrue(u_coord.equal_crs(exp_df.gdf.crs, exp_read.gdf.crs)) class TestAddSea(unittest.TestCase): @@ -480,16 +439,20 @@ class TestAddSea(unittest.TestCase): def test_add_sea_pass(self): """Test add_sea function with fake data.""" - exp = Exposures() - exp.gdf["value"] = np.arange(0, 1.0e6, 1.0e5) min_lat, max_lat = 27.5, 30 min_lon, max_lon = -18, -12 - exp.gdf["latitude"] = np.linspace(min_lat, max_lat, 10) - exp.gdf["longitude"] = np.linspace(min_lon, max_lon, 10) - exp.gdf["region_id"] = np.ones(10) - exp.gdf["impf_TC"] = np.ones(10) - exp.ref_year = 2015 - exp.value_unit = "XSD" + + exp = Exposures( + data=dict( + value=np.arange(0, 1.0e6, 1.0e5), + latitude=np.linspace(min_lat, max_lat, 10), + longitude=np.linspace(min_lon, max_lon, 10), + region_id=np.ones(10), + impf_TC=np.ones(10), + ), + ref_year=2015, + value_unit="XSD", + ) exp.check() sea_coast = 100 @@ -505,16 +468,14 @@ def test_add_sea_pass(self): max_lat = max_lat + sea_coast min_lon = min_lon - sea_coast max_lon = max_lon + sea_coast - self.assertEqual(np.min(exp_sea.gdf["latitude"]), min_lat) - self.assertEqual(np.min(exp_sea.gdf["longitude"]), min_lon) - np.testing.assert_array_equal( - exp_sea.gdf.value.values[:10], np.arange(0, 1.0e6, 1.0e5) - ) + self.assertEqual(np.min(exp_sea.latitude), min_lat) + self.assertEqual(np.min(exp_sea.longitude), min_lon) + np.testing.assert_array_equal(exp_sea.value[:10], np.arange(0, 1.0e6, 1.0e5)) self.assertEqual(exp_sea.ref_year, exp.ref_year) self.assertEqual(exp_sea.value_unit, exp.value_unit) - on_sea_lat = exp_sea.gdf["latitude"].values[11:] - on_sea_lon = exp_sea.gdf["longitude"].values[11:] + on_sea_lat = exp_sea.latitude[11:] + on_sea_lon = exp_sea.longitude[11:] res_on_sea = u_coord.coord_on_land(on_sea_lat, on_sea_lon) res_on_sea = ~res_on_sea self.assertTrue(np.all(res_on_sea)) @@ -523,14 +484,8 @@ def test_add_sea_pass(self): self.assertAlmostEqual( dist.pairwise( [ - [ - exp_sea.gdf["longitude"].values[-1], - exp_sea.gdf["latitude"].values[-1], - ], - [ - exp_sea.gdf["longitude"].values[-2], - exp_sea.gdf["latitude"].values[-2], - ], + [exp_sea.longitude[-1], exp_sea.latitude[-1]], + [exp_sea.longitude[-2], exp_sea.latitude[-2]], ] )[0][1], sea_res_km, @@ -541,16 +496,20 @@ class TestConcat(unittest.TestCase): """Check constructor Exposures through DataFrames readers""" def setUp(self): - exp = Exposures(crs="epsg:3395") - exp.gdf["value"] = np.arange(0, 1.0e6, 1.0e5) min_lat, max_lat = 27.5, 30 min_lon, max_lon = -18, -12 - exp.gdf["latitude"] = np.linspace(min_lat, max_lat, 10) - exp.gdf["longitude"] = np.linspace(min_lon, max_lon, 10) - exp.gdf["region_id"] = np.ones(10) - exp.gdf["impf_TC"] = np.ones(10) - exp.ref_year = 2015 - exp.value_unit = "XSD" + exp = Exposures( + crs="epsg:3395", + value=np.arange(0, 1.0e6, 1.0e5), + lat=np.linspace(min_lat, max_lat, 10), + lon=np.linspace(min_lon, max_lon, 10), + ref_year=2015, + value_unit="XSD", + data=dict( + region_id=np.ones(10), + impf_TC=np.ones(10), + ), + ) self.dummy = exp def test_concat_pass(self): @@ -566,8 +525,11 @@ def test_concat_pass(self): self.dummy, ] ) - self.assertEqual(self.dummy.gdf.shape, (10, 5)) - self.assertEqual(catexp.gdf.shape, (40, 5)) + self.assertEqual( + list(self.dummy.gdf.columns), ["region_id", "impf_TC", "geometry", "value"] + ) + self.assertEqual(self.dummy.gdf.shape, (10, 4)) + self.assertEqual(catexp.gdf.shape, (40, 4)) self.assertTrue(u_coord.equal_crs(catexp.crs, "epsg:3395")) def test_concat_fail(self): @@ -592,17 +554,12 @@ def test_copy_pass(self): self.assertEqual(exp_copy.ref_year, exp.ref_year) self.assertEqual(exp_copy.value_unit, exp.value_unit) self.assertEqual(exp_copy.description, exp.description) - np.testing.assert_array_equal( - exp_copy.gdf["latitude"].values, exp.gdf["latitude"].values - ) - np.testing.assert_array_equal( - exp_copy.gdf["longitude"].values, exp.gdf["longitude"].values - ) + np.testing.assert_array_equal(exp_copy.latitude, exp.latitude) + np.testing.assert_array_equal(exp_copy.longitude, exp.longitude) def test_to_crs_inplace_pass(self): """Test to_crs function inplace.""" exp = good_exposures() - exp.set_geometry_points() exp.check() exp.to_crs("epsg:3395", inplace=True) self.assertIsInstance(exp, Exposures) @@ -614,7 +571,6 @@ def test_to_crs_inplace_pass(self): def test_to_crs_pass(self): """Test to_crs function copy.""" exp = good_exposures() - exp.set_geometry_points() exp.check() exp_tr = exp.to_crs("epsg:3395") self.assertIsInstance(exp, Exposures) @@ -626,12 +582,13 @@ def test_to_crs_pass(self): def test_constructor_pass(self): """Test initialization with input GeoDataFrame""" - in_gpd = gpd.GeoDataFrame() - in_gpd["value"] = np.zeros(10) - in_gpd.ref_year = 2015 + in_gpd = gpd.GeoDataFrame( + dict(latitude=range(10), longitude=[0] * 10, value=np.zeros(10)) + ) in_exp = Exposures(in_gpd, ref_year=2015) self.assertEqual(in_exp.ref_year, 2015) - np.testing.assert_array_equal(in_exp.gdf["value"], np.zeros(10)) + np.testing.assert_array_equal(in_exp.value, np.zeros(10)) + self.assertEqual(in_exp.gdf.geometry[0], Point(0, 0)) def test_error_on_access_item(self): """Test error output when trying to access items as in CLIMADA 1.x""" @@ -647,33 +604,34 @@ def test_set_gdf(self): gdf_without_geometry = good_exposures().gdf good_exp = good_exposures() good_exp.set_crs(crs="epsg:3395") - good_exp.set_geometry_points() gdf_with_geometry = good_exp.gdf probe = Exposures() self.assertRaises(ValueError, probe.set_gdf, pd.DataFrame()) probe.set_gdf(empty_gdf) - self.assertTrue(probe.gdf.equals(gpd.GeoDataFrame())) + self.assertTrue(probe.gdf.equals(gpd.GeoDataFrame().set_geometry([]))) self.assertTrue(u_coord.equal_crs(DEF_CRS, probe.crs)) - self.assertFalse(hasattr(probe.gdf, "crs")) + self.assertTrue(u_coord.equal_crs(DEF_CRS, probe.gdf.crs)) probe.set_gdf(gdf_with_geometry) self.assertTrue(probe.gdf.equals(gdf_with_geometry)) - self.assertTrue(u_coord.equal_crs("epsg:3395", probe.crs)) + self.assertTrue(u_coord.equal_crs("epsg:3395", gdf_with_geometry.crs)) + self.assertTrue( + u_coord.equal_crs("epsg:3395", probe.crs), f"unexpected: {probe.crs}" + ) self.assertTrue(u_coord.equal_crs("epsg:3395", probe.gdf.crs)) probe.set_gdf(gdf_without_geometry) self.assertTrue(probe.gdf.equals(good_exposures().gdf)) self.assertTrue(u_coord.equal_crs(DEF_CRS, probe.crs)) - self.assertFalse(hasattr(probe.gdf, "crs")) + self.assertTrue(u_coord.equal_crs(DEF_CRS, probe.gdf.crs)) def test_set_crs(self): """Test setting the CRS""" empty_gdf = gpd.GeoDataFrame() gdf_without_geometry = good_exposures().gdf good_exp = good_exposures() - good_exp.set_geometry_points() gdf_with_geometry = good_exp.gdf probe = Exposures(gdf_without_geometry) @@ -685,8 +643,8 @@ def test_set_crs(self): self.assertTrue(u_coord.equal_crs(DEF_CRS, probe.crs)) probe.set_crs(DEF_CRS) self.assertTrue(u_coord.equal_crs(DEF_CRS, probe.crs)) - self.assertRaises(ValueError, probe.set_crs, "epsg:3395") - self.assertTrue(u_coord.equal_crs("EPSG:4326", probe.meta.get("crs"))) + probe.set_crs("epsg:3395") + self.assertTrue(u_coord.equal_crs("epsg:3395", probe.crs)) def test_to_crs_epsg_crs(self): """Check that if crs and epsg are both provided a ValueError is raised""" @@ -708,30 +666,28 @@ def test_get_impf_column(self): self.assertRaises(ValueError, expo.get_impf_column, "HAZ") # removed impf column - expo.gdf.drop(columns="impf_NA", inplace=True) + expo.data.drop(columns="impf_NA", inplace=True) self.assertRaises(ValueError, expo.get_impf_column, "NA") self.assertRaises(ValueError, expo.get_impf_column) # default (anonymous) impf column - expo.check() + expo.data["impf_"] = 1 self.assertEqual("impf_", expo.get_impf_column()) self.assertEqual("impf_", expo.get_impf_column("HAZ")) # rename impf column to old style column name - expo.gdf.rename(columns={"impf_": "if_"}, inplace=True) - expo.check() + expo.data.rename(columns={"impf_": "if_"}, inplace=True) self.assertEqual("if_", expo.get_impf_column()) self.assertEqual("if_", expo.get_impf_column("HAZ")) # rename impf column to old style column name - expo.gdf.rename(columns={"if_": "if_NA"}, inplace=True) - expo.check() + expo.data.rename(columns={"if_": "if_NA"}, inplace=True) self.assertEqual("if_NA", expo.get_impf_column("NA")) self.assertRaises(ValueError, expo.get_impf_column) self.assertRaises(ValueError, expo.get_impf_column, "HAZ") # add anonymous impf column - expo.gdf["impf_"] = expo.gdf["region_id"] + expo.data["impf_"] = expo.region_id self.assertEqual("if_NA", expo.get_impf_column("NA")) self.assertEqual("impf_", expo.get_impf_column()) self.assertEqual("impf_", expo.get_impf_column("HAZ")) diff --git a/climada/entity/exposures/test/test_mat.py b/climada/entity/exposures/test/test_mat.py index 540b92c88..4993b3cde 100644 --- a/climada/entity/exposures/test/test_mat.py +++ b/climada/entity/exposures/test/test_mat.py @@ -43,40 +43,40 @@ def test_read_demo_pass(self): self.assertEqual(expo.gdf.index[0], 0) self.assertEqual(expo.gdf.index[n_expos - 1], n_expos - 1) - self.assertEqual(expo.gdf["value"].shape, (n_expos,)) - self.assertEqual(expo.gdf["value"][0], 13927504367.680632) - self.assertEqual(expo.gdf["value"][n_expos - 1], 12624818493.687229) + self.assertEqual(expo.value.shape, (n_expos,)) + self.assertEqual(expo.value[0], 13927504367.680632) + self.assertEqual(expo.value[n_expos - 1], 12624818493.687229) - self.assertEqual(expo.gdf["deductible"].shape, (n_expos,)) - self.assertEqual(expo.gdf["deductible"][0], 0) - self.assertEqual(expo.gdf["deductible"][n_expos - 1], 0) + self.assertEqual(expo.deductible.shape, (n_expos,)) + self.assertEqual(expo.deductible[0], 0) + self.assertEqual(expo.deductible[n_expos - 1], 0) - self.assertEqual(expo.gdf["cover"].shape, (n_expos,)) - self.assertEqual(expo.gdf["cover"][0], 13927504367.680632) - self.assertEqual(expo.gdf["cover"][n_expos - 1], 12624818493.687229) + self.assertEqual(expo.cover.shape, (n_expos,)) + self.assertEqual(expo.cover[0], 13927504367.680632) + self.assertEqual(expo.cover[n_expos - 1], 12624818493.687229) - self.assertIn("int", str(expo.gdf["impf_"].dtype)) - self.assertEqual(expo.gdf["impf_"].shape, (n_expos,)) - self.assertEqual(expo.gdf["impf_"][0], 1) - self.assertEqual(expo.gdf["impf_"][n_expos - 1], 1) + self.assertIn("int", str(expo.hazard_impf().dtype)) + self.assertEqual(expo.hazard_impf().shape, (n_expos,)) + self.assertEqual(expo.hazard_impf()[0], 1) + self.assertEqual(expo.hazard_impf()[n_expos - 1], 1) - self.assertIn("int", str(expo.gdf["category_id"].dtype)) - self.assertEqual(expo.gdf["category_id"].shape, (n_expos,)) - self.assertEqual(expo.gdf["category_id"][0], 1) - self.assertEqual(expo.gdf["category_id"][n_expos - 1], 1) + self.assertIn("int", str(expo.category_id.dtype)) + self.assertEqual(expo.category_id.shape, (n_expos,)) + self.assertEqual(expo.category_id[0], 1) + self.assertEqual(expo.category_id[n_expos - 1], 1) - self.assertIn("int", str(expo.gdf["centr_"].dtype)) - self.assertEqual(expo.gdf["centr_"].shape, (n_expos,)) - self.assertEqual(expo.gdf["centr_"][0], 47) - self.assertEqual(expo.gdf["centr_"][n_expos - 1], 46) + self.assertIn("int", str(expo.hazard_centroids().dtype)) + self.assertEqual(expo.hazard_centroids().shape, (n_expos,)) + self.assertEqual(expo.hazard_centroids()[0], 47) + self.assertEqual(expo.hazard_centroids()[n_expos - 1], 46) self.assertTrue("region_id" not in expo.gdf) - self.assertEqual(expo.gdf["latitude"].shape, (n_expos,)) - self.assertEqual(expo.gdf["latitude"][0], 26.93389900000) - self.assertEqual(expo.gdf["latitude"][n_expos - 1], 26.34795700000) - self.assertEqual(expo.gdf["longitude"][0], -80.12879900000) - self.assertEqual(expo.gdf["longitude"][n_expos - 1], -80.15885500000) + self.assertEqual(expo.latitude.size, n_expos) + self.assertEqual(expo.latitude[0], 26.93389900000) + self.assertEqual(expo.latitude[n_expos - 1], 26.34795700000) + self.assertEqual(expo.longitude[0], -80.12879900000) + self.assertEqual(expo.longitude[n_expos - 1], -80.15885500000) self.assertEqual(expo.ref_year, 2016) self.assertEqual(expo.value_unit, "USD") diff --git a/climada/entity/measures/base.py b/climada/entity/measures/base.py index 93505feb3..c29e78fff 100755 --- a/climada/entity/measures/base.py +++ b/climada/entity/measures/base.py @@ -324,11 +324,9 @@ def _change_all_exposures(self, exposures): ) if not np.array_equal( - np.unique(exposures.gdf["latitude"].values), - np.unique(new_exp.gdf["latitude"].values), + np.unique(exposures.latitude), np.unique(new_exp.latitude) ) or not np.array_equal( - np.unique(exposures.gdf["longitude"].values), - np.unique(new_exp.gdf["longitude"].values), + np.unique(exposures.longitude), np.unique(new_exp.longitude) ): LOGGER.warning("Exposures locations have changed.") @@ -433,7 +431,7 @@ def _cutoff_hazard_damage(self, exposures, impf_set, hazard): if self.exp_region_id: # compute impact only in selected region in_reg = np.logical_or.reduce( - [exposures.gdf["region_id"].values == reg for reg in self.exp_region_id] + [exposures.region_id == reg for reg in self.exp_region_id] ) exp_imp = Exposures(exposures.gdf[in_reg], crs=exposures.crs) else: diff --git a/climada/entity/measures/test/test_base.py b/climada/entity/measures/test/test_base.py index 4f14f4a5a..d8688e4bf 100644 --- a/climada/entity/measures/test/test_base.py +++ b/climada/entity/measures/test/test_base.py @@ -29,7 +29,7 @@ import climada.util.coordinates as u_coord from climada import CONFIG from climada.entity.entity_def import Entity -from climada.entity.exposures.base import INDICATOR_IMPF, Exposures +from climada.entity.exposures.base import Exposures from climada.entity.impact_funcs.base import ImpactFunc from climada.entity.impact_funcs.impact_func_set import ImpactFuncSet from climada.entity.measures.base import IMPF_ID_FACT, Measure @@ -236,25 +236,14 @@ def test_change_exposures_impf_pass(self): self.assertEqual(new_exp.ref_year, exp.ref_year) self.assertEqual(new_exp.value_unit, exp.value_unit) self.assertEqual(new_exp.description, exp.description) + self.assertTrue(np.array_equal(new_exp.value, exp.value)) + self.assertTrue(np.array_equal(new_exp.latitude, exp.latitude)) + self.assertTrue(np.array_equal(new_exp.longitude, exp.longitude)) self.assertTrue( - np.array_equal(new_exp.gdf["value"].values, exp.gdf["value"].values) + np.array_equal(exp.hazard_impf("TC"), np.ones(new_exp.gdf.shape[0])) ) self.assertTrue( - np.array_equal(new_exp.gdf["latitude"].values, exp.gdf["latitude"].values) - ) - self.assertTrue( - np.array_equal(new_exp.gdf["longitude"].values, exp.gdf["longitude"].values) - ) - self.assertTrue( - np.array_equal( - exp.gdf[INDICATOR_IMPF + "TC"].values, np.ones(new_exp.gdf.shape[0]) - ) - ) - self.assertTrue( - np.array_equal( - new_exp.gdf[INDICATOR_IMPF + "TC"].values, - np.ones(new_exp.gdf.shape[0]) * 3, - ) + np.array_equal(new_exp.hazard_impf("TC"), np.ones(new_exp.gdf.shape[0]) * 3) ) def test_change_all_hazard_pass(self): @@ -290,19 +279,9 @@ def test_change_all_exposures_pass(self): self.assertEqual(new_exp.ref_year, ref_exp.ref_year) self.assertEqual(new_exp.value_unit, ref_exp.value_unit) self.assertEqual(new_exp.description, ref_exp.description) - self.assertTrue( - np.array_equal(new_exp.gdf["value"].values, ref_exp.gdf["value"].values) - ) - self.assertTrue( - np.array_equal( - new_exp.gdf["latitude"].values, ref_exp.gdf["latitude"].values - ) - ) - self.assertTrue( - np.array_equal( - new_exp.gdf["longitude"].values, ref_exp.gdf["longitude"].values - ) - ) + self.assertTrue(np.array_equal(new_exp.value, ref_exp.value)) + self.assertTrue(np.array_equal(new_exp.latitude, ref_exp.latitude)) + self.assertTrue(np.array_equal(new_exp.longitude, ref_exp.longitude)) def test_not_filter_exposures_pass(self): """Test _filter_exposures method with []""" @@ -369,17 +348,11 @@ def test_filter_exposures_pass(self): self.assertEqual(res_exp.value_unit, exp.value_unit) self.assertEqual(res_exp.description, exp.description) self.assertTrue(u_coord.equal_crs(res_exp.crs, exp.crs)) - self.assertFalse(hasattr(exp.gdf, "crs")) - self.assertFalse(hasattr(res_exp.gdf, "crs")) # regions (that is just input data, no need for testing, but it makes the changed and unchanged parts obious) - self.assertTrue(np.array_equal(res_exp.gdf["region_id"].values[0], 4)) - self.assertTrue( - np.array_equal(res_exp.gdf["region_id"].values[1:25], np.ones(24) * 3) - ) - self.assertTrue( - np.array_equal(res_exp.gdf["region_id"].values[25:], np.ones(25)) - ) + self.assertTrue(np.array_equal(res_exp.region_id[0], 4)) + self.assertTrue(np.array_equal(res_exp.region_id[1:25], np.ones(24) * 3)) + self.assertTrue(np.array_equal(res_exp.region_id[25:], np.ones(25))) # changed exposures self.assertTrue( @@ -402,17 +375,8 @@ def test_filter_exposures_pass(self): ) ) ) - self.assertTrue( - np.array_equal( - res_exp.gdf["latitude"].values[:25], new_exp.gdf["latitude"].values[:25] - ) - ) - self.assertTrue( - np.array_equal( - res_exp.gdf["longitude"].values[:25], - new_exp.gdf["longitude"].values[:25], - ) - ) + self.assertTrue(np.array_equal(res_exp.latitude[:25], new_exp.latitude[:25])) + self.assertTrue(np.array_equal(res_exp.longitude[:25], new_exp.longitude[:25])) # unchanged exposures self.assertTrue( @@ -432,16 +396,8 @@ def test_filter_exposures_pass(self): res_exp.gdf["impf_TC"].values[25:], exp.gdf["impf_TC"].values[25:] ) ) - self.assertTrue( - np.array_equal( - res_exp.gdf["latitude"].values[25:], exp.gdf["latitude"].values[25:] - ) - ) - self.assertTrue( - np.array_equal( - res_exp.gdf["longitude"].values[25:], exp.gdf["longitude"].values[25:] - ) - ) + self.assertTrue(np.array_equal(res_exp.latitude[25:], exp.latitude[25:])) + self.assertTrue(np.array_equal(res_exp.longitude[25:], exp.longitude[25:])) # unchanged impact functions self.assertEqual(list(res_ifs.get_func().keys()), [meas.haz_type]) @@ -657,12 +613,8 @@ def test_calc_impact_pass(self): self.assertAlmostEqual(imp.at_event[12], 1.470194187501225e07) self.assertAlmostEqual(imp.at_event[41], 4.7226357936631286e08) self.assertAlmostEqual(imp.at_event[11890], 1.742110428135755e07) - self.assertTrue( - np.array_equal(imp.coord_exp[:, 0], entity.exposures.gdf["latitude"]) - ) - self.assertTrue( - np.array_equal(imp.coord_exp[:, 1], entity.exposures.gdf["longitude"]) - ) + self.assertTrue(np.array_equal(imp.coord_exp[:, 0], entity.exposures.latitude)) + self.assertTrue(np.array_equal(imp.coord_exp[:, 1], entity.exposures.longitude)) self.assertAlmostEqual(imp.eai_exp[0], 1.15677655725858e08) self.assertAlmostEqual(imp.eai_exp[-1], 7.528669956120645e07) self.assertAlmostEqual(imp.tot_value, 6.570532945599105e11) diff --git a/climada/hazard/centroids/centr.py b/climada/hazard/centroids/centr.py index c1e8bb68b..b0c6365c7 100644 --- a/climada/hazard/centroids/centr.py +++ b/climada/hazard/centroids/centr.py @@ -133,6 +133,8 @@ def geometry(self): @property def on_land(self): """Get the on_land property""" + if "on_land" not in self.gdf: + return None if self.gdf["on_land"].isna().all(): return None return self.gdf["on_land"].values @@ -140,6 +142,8 @@ def on_land(self): @property def region_id(self): """Get the assigned region_id""" + if "region_id" not in self.gdf: + return None if self.gdf["region_id"].isna().all(): return None return self.gdf["region_id"].values @@ -284,33 +288,15 @@ def from_exposures(cls, exposures): ------ ValueError """ + # exclude exposures specific columns col_names = [ column for column in exposures.gdf.columns if not any(pattern in column for pattern in EXP_SPECIFIC_COLS) ] - # Legacy behaviour - # Exposures can be without geometry column - # TODO: remove once exposures is real geodataframe with geometry. - if "geometry" in exposures.gdf.columns: - gdf = exposures.gdf[col_names] - return cls.from_geodataframe(gdf) - - if "latitude" in exposures.gdf.columns and "longitude" in exposures.gdf.columns: - gdf = exposures.gdf[col_names] - return cls( - lat=exposures.gdf["latitude"], - lon=exposures.gdf["longitude"], - crs=exposures.crs, - **dict(gdf.items()), - ) - - raise ValueError( - "The given exposures object has no coordinates information." - "The exposures' GeoDataFrame must have either point geometries" - " or latitude and longitude values." - ) + gdf = exposures.gdf[col_names] + return cls.from_geodataframe(gdf) @classmethod def from_pnt_bounds(cls, points_bounds, res, crs=DEF_CRS): diff --git a/climada/hazard/centroids/test/test_centr.py b/climada/hazard/centroids/test/test_centr.py index a41060bae..778d9383e 100644 --- a/climada/hazard/centroids/test/test_centr.py +++ b/climada/hazard/centroids/test/test_centr.py @@ -392,7 +392,7 @@ def test_set_region_id_implementationerror(self): centroids.set_region_id(level="continent", overwrite=True) def test_set_geometry_points_pass(self): - """Test set_geometry_points""" + """Test geometry is set""" centr_ras = Centroids.from_raster_file(HAZ_DEMO_FL, window=Window(0, 0, 50, 60)) x_flat = np.arange(-69.3326495969998, -68.88264959699978, 0.009000000000000341) y_flat = np.arange(10.423720966978939, 9.883720966978919, -0.009000000000000341) @@ -708,11 +708,13 @@ def test_from_exposures_without_region_id(self): False, ) - def test_from_exposure_exceptions(self): + def test_from_empty_exposures(self): gdf = gpd.GeoDataFrame({}) exposures = Exposures(gdf) - with self.assertRaises(ValueError): - Centroids.from_exposures(exposures) + centroids = Centroids.from_exposures(exposures) + self.assertEqual( + centroids.gdf.shape, (0, 1) + ) # there is an empty geometry column def test_read_write_hdf5(self): tmpfile = Path("test_write_hdf5.out.hdf5") diff --git a/climada/hazard/tc_tracks.py b/climada/hazard/tc_tracks.py index 3f2fb85b8..963d282cd 100644 --- a/climada/hazard/tc_tracks.py +++ b/climada/hazard/tc_tracks.py @@ -340,9 +340,9 @@ def tracks_in_exp(self, exposure, buffer=1.0): if buffer <= 0.0: raise ValueError(f"buffer={buffer} is invalid, must be above zero.") try: - exposure.gdf.geometry + exposure.geometry except AttributeError: - exposure.set_geometry_points() + raise Exception("this is not an Exposures object") exp_buffer = exposure.gdf.buffer(distance=buffer, resolution=0) exp_buffer = exp_buffer.unary_union diff --git a/climada/test/test_api_client.py b/climada/test/test_api_client.py index 6bd86ed4f..26ce163fd 100644 --- a/climada/test/test_api_client.py +++ b/climada/test/test_api_client.py @@ -181,7 +181,7 @@ def test_get_exposures(self): dump_dir=DATA_DIR, ) self.assertEqual(len(exposures.gdf), 5782) - self.assertEqual(np.unique(exposures.gdf["region_id"]), 40) + self.assertEqual(np.unique(exposures.region_id), 40) self.assertEqual( exposures.description, "LitPop Exposure for ['AUT'] at 150 as, year: 2018, financial mode: pop, exp: [0, 1], admin1_calc: False", @@ -266,7 +266,7 @@ def test_get_litpop(self): client = Client() litpop = client.get_litpop(country="LUX", version="v1", dump_dir=DATA_DIR) self.assertEqual(len(litpop.gdf), 188) - self.assertEqual(np.unique(litpop.gdf["region_id"]), 442) + self.assertEqual(np.unique(litpop.region_id), 442) self.assertEqual( litpop.description, "LitPop Exposure for ['LUX'] at 150 as, year: 2018, financial mode: pc, exp: [1, 1], admin1_calc: False", diff --git a/climada/test/test_litpop_integr.py b/climada/test/test_litpop_integr.py index 0390a4538..2c2ddba88 100644 --- a/climada/test/test_litpop_integr.py +++ b/climada/test/test_litpop_integr.py @@ -76,8 +76,8 @@ def test_switzerland300_pass(self): ) self.assertIn("LitPop: Init Exposure for country: CHE", cm.output[0]) - self.assertEqual(ent.gdf["region_id"].min(), 756) - self.assertEqual(ent.gdf["region_id"].max(), 756) + self.assertEqual(ent.region_id.min(), 756) + self.assertEqual(ent.region_id.max(), 756) # confirm that the total value is equal to GDP * (income_group+1): self.assertAlmostEqual( ent.gdf["value"].sum() / gdp("CHE", 2016)[1], @@ -115,9 +115,9 @@ def test_switzerland30normPop_pass(self): ) # print(cm) self.assertIn("LitPop: Init Exposure for country: CHE", cm.output[0]) - self.assertEqual(ent.gdf["region_id"].min(), 756) - self.assertEqual(ent.gdf["region_id"].max(), 756) - self.assertEqual(ent.gdf["value"].sum(), 1.0) + self.assertEqual(ent.region_id.min(), 756) + self.assertEqual(ent.region_id.max(), 756) + self.assertEqual(ent.value.sum(), 1.0) self.assertEqual(ent.ref_year, 2015) def test_suriname30_nfw_pass(self): @@ -128,8 +128,8 @@ def test_suriname30_nfw_pass(self): country_name, reference_year=2016, fin_mode=fin_mode ) - self.assertEqual(ent.gdf["region_id"].min(), 740) - self.assertEqual(ent.gdf["region_id"].max(), 740) + self.assertEqual(ent.region_id.min(), 740) + self.assertEqual(ent.region_id.max(), 740) self.assertEqual(ent.ref_year, 2016) def test_switzerland300_admin1_pc2016_pass(self): @@ -164,23 +164,23 @@ def test_from_shape_zurich_pass(self): ent = lp.LitPop.from_shape( shape, total_value, res_arcsec=30, reference_year=2016 ) - self.assertEqual(ent.gdf["value"].sum(), 1000.0) - self.assertEqual(ent.gdf["value"].min(), 0.0) - self.assertEqual(ent.gdf["region_id"].min(), 756) - self.assertEqual(ent.gdf["region_id"].max(), 756) - self.assertAlmostEqual(ent.gdf["latitude"].min(), 47.20416666666661) + self.assertEqual(ent.value.sum(), 1000.0) + self.assertEqual(ent.value.min(), 0.0) + self.assertEqual(ent.region_id.min(), 756) + self.assertEqual(ent.region_id.max(), 756) + self.assertAlmostEqual(ent.latitude.min(), 47.20416666666661) # index and coord. of largest value: self.assertEqual( ent.gdf.loc[ent.gdf["value"] == ent.gdf["value"].max()].index[0], 482 ) self.assertAlmostEqual( - ent.gdf.loc[ent.gdf["value"] == ent.gdf["value"].max()]["latitude"].values[ + ent.gdf.loc[ent.gdf["value"] == ent.gdf["value"].max()].geometry.y.values[ 0 ], 47.34583333333325, ) self.assertAlmostEqual( - ent.gdf.loc[ent.gdf["value"] == ent.gdf["value"].max()]["longitude"].values[ + ent.gdf.loc[ent.gdf["value"] == ent.gdf["value"].max()].geometry.x.values[ 0 ], 8.529166666666658, @@ -193,22 +193,22 @@ def test_from_shape_and_countries_zurich_pass(self): ent = lp.LitPop.from_shape_and_countries( shape, "Switzerland", res_arcsec=30, reference_year=2016 ) - self.assertEqual(ent.gdf["value"].min(), 0.0) - self.assertEqual(ent.gdf["region_id"].min(), 756) - self.assertEqual(ent.gdf["region_id"].max(), 756) - self.assertAlmostEqual(ent.gdf["latitude"].min(), 47.20416666666661) + self.assertEqual(ent.value.min(), 0.0) + self.assertEqual(ent.region_id.min(), 756) + self.assertEqual(ent.region_id.max(), 756) + self.assertAlmostEqual(ent.latitude.min(), 47.20416666666661) # coord of largest value: self.assertEqual( - ent.gdf.loc[ent.gdf["value"] == ent.gdf["value"].max()].index[0], 434 + ent.gdf.loc[ent.gdf.value == ent.gdf.value.max()].index[0], 434 ) self.assertAlmostEqual( - ent.gdf.loc[ent.gdf["value"] == ent.gdf["value"].max()]["latitude"].values[ + ent.gdf.loc[ent.gdf["value"] == ent.gdf["value"].max()].geometry.y.values[ 0 ], 47.34583333333325, ) self.assertAlmostEqual( - ent.gdf.loc[ent.gdf["value"] == ent.gdf["value"].max()]["longitude"].values[ + ent.gdf.loc[ent.gdf["value"] == ent.gdf["value"].max()].geometry.x.values[ 0 ], 8.529166666666658, @@ -220,10 +220,10 @@ def test_Liechtenstein_15_lit_pass(self): ref_year = 2016 ent = lp.LitPop.from_nightlight_intensity(country_name, reference_year=ref_year) - self.assertEqual(ent.gdf["value"].sum(), 36469.0) - self.assertEqual(ent.gdf["region_id"][1], 438) + self.assertEqual(ent.value.sum(), 36469.0) + self.assertEqual(ent.region_id[1], 438) self.assertEqual(ent.value_unit, "") - self.assertAlmostEqual(ent.gdf["latitude"].max(), 47.260416666666664) + self.assertAlmostEqual(ent.latitude.max(), 47.260416666666664) self.assertAlmostEqual(ent.meta["transform"][4], -15 / 3600) def test_Liechtenstein_30_pop_pass(self): @@ -232,10 +232,10 @@ def test_Liechtenstein_30_pop_pass(self): ref_year = 2015 ent = lp.LitPop.from_population(country_name, reference_year=ref_year) - self.assertEqual(ent.gdf["value"].sum(), 30068.970703125) - self.assertEqual(ent.gdf["region_id"][1], 438) + self.assertEqual(ent.value.sum(), 30068.970703125) + self.assertEqual(ent.region_id[1], 438) self.assertEqual(ent.value_unit, "people") - self.assertAlmostEqual(ent.gdf["latitude"].max(), 47.2541666666666) + self.assertAlmostEqual(ent.latitude.max(), 47.2541666666666) self.assertAlmostEqual(ent.meta["transform"][0], 30 / 3600) def test_from_nightlight_intensity(self): @@ -331,8 +331,8 @@ def test_calc_admin1(self): ) self.assertEqual(ent.gdf.shape[0], 699) - self.assertEqual(ent.gdf["region_id"][88], 756) - self.assertAlmostEqual(ent.gdf["latitude"].max(), 47.708333333333336) + self.assertEqual(ent.region_id[88], 756) + self.assertAlmostEqual(ent.latitude.max(), 47.708333333333336) # shape must be same as with admin1_calc = False, otherwise there # is a problem with handling of the admin1 shapes: ent_adm0 = lp.LitPop.from_countries( diff --git a/climada/test/test_plot.py b/climada/test/test_plot.py index 082f38e1b..04131741f 100644 --- a/climada/test/test_plot.py +++ b/climada/test/test_plot.py @@ -161,12 +161,8 @@ def test_impact_pass(self): def test_ctx_osm_pass(self): """Test basemap function using osm images""" - myexp = Exposures() - myexp.gdf["latitude"] = np.array([30, 40, 50]) - myexp.gdf["longitude"] = np.array([0, 0, 0]) - myexp.gdf["value"] = np.array([1, 1, 1]) + myexp = Exposures(lat=[30, 40, 50], lon=[0, 0, 0], value=[1, 1, 1]) myexp.check() - myexp.plot_basemap(url=ctx.providers.OpenStreetMap.Mapnik) def test_disc_rates(self): diff --git a/climada/util/coordinates.py b/climada/util/coordinates.py index e160965b1..cec74b512 100644 --- a/climada/util/coordinates.py +++ b/climada/util/coordinates.py @@ -1100,7 +1100,7 @@ def match_centroids( Parameters ---------- coord_gdf : gpd.GeoDataFrame - GeoDataframe with defined latitude/longitude column and crs + GeoDataframe with defined geometry column and crs centroids : Centroids (Hazard) centroids to match (as raster or vector centroids). distance : str, optional @@ -1146,7 +1146,7 @@ def match_centroids( pass assigned = match_coordinates( - np.stack([coord_gdf["latitude"].values, coord_gdf["longitude"].values], axis=1), + np.stack([coord_gdf.geometry.y.values, coord_gdf.geometry.x.values], axis=1), centroids.coord, distance=distance, threshold=threshold, @@ -1911,6 +1911,8 @@ def equal_crs(crs_one, crs_two): """ if crs_one is None: return crs_two is None + if crs_two is None: + return False return rasterio.crs.CRS.from_user_input( crs_one ) == rasterio.crs.CRS.from_user_input(crs_two) @@ -2615,8 +2617,9 @@ def points_to_raster( Parameters ---------- - points_df : GeoDataFrame - contains columns latitude, longitude and those listed in the parameter `val_names`. + points_df : GeoDataFrame | DataFrame + contains columns listed in the parameter `val_names` and 'geometry' if it is a GeoDataFrame + or 'latitude' and 'longitude' if it is a DataFrame. val_names : list of str, optional The names of columns in `points_df` containing values. The raster will contain one band per column. Default: ['value'] @@ -2642,15 +2645,25 @@ def points_to_raster( """ if not val_names: val_names = ["value"] + + if "geometry" in points_df: + latval = points_df.geometry.y + lonval = points_df.geometry.x + else: + latval = points_df["latitude"].values + lonval = points_df["longitude"].values + if not res: - res = np.abs( - get_resolution(points_df["latitude"].values, points_df["longitude"].values) - ).min() + res = np.abs(get_resolution(latval, lonval)).min() if not raster_res: raster_res = res - def apply_box(df_exp): + if "geometry" in points_df: + fun = lambda r: r.geometry.buffer(res / 2).envelope + else: fun = lambda r: Point(r["longitude"], r["latitude"]).buffer(res / 2).envelope + + def apply_box(df_exp): return df_exp.apply(fun, axis=1) LOGGER.info("Raster from resolution %s to %s.", res, raster_res) @@ -2680,9 +2693,7 @@ def apply_box(df_exp): # renormalize longitude if necessary if equal_crs(df_poly.crs, DEF_CRS): - xmin, ymin, xmax, ymax = latlon_bounds( - points_df["latitude"].values, points_df["longitude"].values - ) + xmin, ymin, xmax, ymax = latlon_bounds(latval, lonval) x_mid = 0.5 * (xmin + xmax) # we don't really change the CRS when rewrapping, so we reset the CRS attribute afterwards df_poly = df_poly.to_crs({"proj": "longlat", "lon_wrap": x_mid}).set_crs( @@ -2690,10 +2701,10 @@ def apply_box(df_exp): ) else: xmin, ymin, xmax, ymax = ( - points_df["longitude"].min(), - points_df["latitude"].min(), - points_df["longitude"].max(), - points_df["latitude"].max(), + lonval.min(), + latval.min(), + lonval.max(), + latval.max(), ) # construct raster diff --git a/climada/util/lines_polys_handler.py b/climada/util/lines_polys_handler.py index 244658b18..ee2058a68 100755 --- a/climada/util/lines_polys_handler.py +++ b/climada/util/lines_polys_handler.py @@ -420,10 +420,9 @@ def exp_geom_to_pnt(exp, res, to_meters, disagg_met, disagg_val): if disagg_met is DisaggMethod.DIV: gdf_pnt = _disagg_values_div(gdf_pnt) - # set lat lon and centroids + # set dataframe exp_pnt = exp.copy(deep=False) exp_pnt.set_gdf(gdf_pnt) - exp_pnt.set_lat_lon() return exp_pnt diff --git a/climada/util/test/test_lines_polys_handler.py b/climada/util/test/test_lines_polys_handler.py index 8800d6d06..c320e894a 100644 --- a/climada/util/test/test_lines_polys_handler.py +++ b/climada/util/test/test_lines_polys_handler.py @@ -68,9 +68,7 @@ def check_unchanged_geom_gdf(self, gdf_geom, gdf_pnt): sub_gdf_pnt = gdf_pnt.xs(n, level=1) rows_sel = sub_gdf_pnt.index.to_numpy() sub_gdf = gdf_geom.loc[rows_sel] - self.assertTrue( - np.alltrue(sub_gdf.geometry.geom_equals(sub_gdf_pnt.geometry_orig)) - ) + self.assertTrue(np.all(sub_gdf.geometry.geom_equals(sub_gdf_pnt.geometry_orig))) for col in gdf_pnt.columns: if col not in COL_CHANGING: np.testing.assert_allclose(gdf_pnt[col].unique(), gdf_geom[col].unique()) @@ -139,7 +137,7 @@ def test_point_exposure_from_polygons(self): 3.83689000e10, ] ) - np.testing.assert_allclose(exp_pnt.gdf["value"], val_avg) + np.testing.assert_allclose(exp_pnt.value, val_avg) lat = np.array( [ 53.15019278, @@ -160,7 +158,7 @@ def test_point_exposure_from_polygons(self): 52.11286591, ] ) - np.testing.assert_allclose(exp_pnt.gdf["latitude"], lat) + np.testing.assert_allclose(exp_pnt.latitude, lat) # to_meters=TRUE, FIX, dissag_val res = 20000 @@ -173,7 +171,7 @@ def test_point_exposure_from_polygons(self): ) self.check_unchanged_exp(EXP_POLY, exp_pnt) val = res**2 - self.assertEqual(np.unique(exp_pnt.gdf["value"])[0], val) + self.assertEqual(np.unique(exp_pnt.value)[0], val) lat = np.array( [ 53.13923671, @@ -252,7 +250,7 @@ def test_point_exposure_from_polygons(self): 52.23308448, ] ) - np.testing.assert_allclose(exp_pnt.gdf["latitude"], lat) + np.testing.assert_allclose(exp_pnt.latitude, lat) # projected crs, to_meters=TRUE, FIX, dissag_val res = 20000 @@ -336,8 +334,10 @@ def test_point_exposure_from_polygons_on_grid(self): disagg_val=None, ) self.check_unchanged_exp(exp_poly, exp_pnt_grid) - for col in ["value", "latitude", "longitude"]: - np.testing.assert_allclose(exp_pnt.gdf[col], exp_pnt_grid.gdf[col]) + + np.testing.assert_allclose(exp_pnt.value, exp_pnt_grid.value) + np.testing.assert_allclose(exp_pnt.latitude, exp_pnt_grid.latitude) + np.testing.assert_allclose(exp_pnt.longitude, exp_pnt_grid.longitude) x_grid = np.append(x_grid, x_grid + 10) y_grid = np.append(y_grid, y_grid + 10) @@ -356,8 +356,10 @@ def test_point_exposure_from_polygons_on_grid(self): disagg_val=None, ) self.check_unchanged_exp(exp_poly, exp_pnt_grid) - for col in ["value", "latitude", "longitude"]: - np.testing.assert_allclose(exp_pnt.gdf[col], exp_pnt_grid.gdf[col]) + + np.testing.assert_allclose(exp_pnt.value, exp_pnt_grid.value) + np.testing.assert_allclose(exp_pnt.latitude, exp_pnt_grid.latitude) + np.testing.assert_allclose(exp_pnt.longitude, exp_pnt_grid.longitude) def test_point_exposure_from_lines(self): """Test disaggregation of lines to points""" @@ -428,7 +430,7 @@ def test_point_exposure_from_lines(self): 50.9105503, ] ) - np.testing.assert_allclose(exp_pnt.gdf["latitude"], lat) + np.testing.assert_allclose(exp_pnt.latitude, lat) class TestGeomImpactCalcs(unittest.TestCase): @@ -568,7 +570,7 @@ def test_calc_geom_impact_points(self): aai_agg1 = 0.0470814 exp = EXP_POINT.copy() - exp.set_lat_lon() + # exp.set_lat_lon() imp11 = ImpactCalc(exp, IMPF_SET, HAZ).impact() check_impact(self, imp1, HAZ, EXP_POINT, aai_agg1, imp11.eai_exp) @@ -1180,7 +1182,7 @@ def test_swap_geom_cols(self): gdf_orig = GDF_POLY.copy() gdf_orig["new_geom"] = gdf_orig.geometry swap_gdf = u_lp._swap_geom_cols(gdf_orig, "old_geom", "new_geom") - self.assertTrue(np.alltrue(swap_gdf.geometry.geom_equals(gdf_orig.new_geom))) + self.assertTrue(np.all(swap_gdf.geometry.geom_equals(gdf_orig.new_geom))) if __name__ == "__main__": diff --git a/doc/tutorial/1_main_climada.ipynb b/doc/tutorial/1_main_climada.ipynb index 36ce87bb2..7a9b45ab8 100644 --- a/doc/tutorial/1_main_climada.ipynb +++ b/doc/tutorial/1_main_climada.ipynb @@ -294,7 +294,6 @@ "\n", "min_lat, max_lat, min_lon, max_lon = 17.5, 19.0, -68.0, -65.0\n", "cent = Centroids.from_pnt_bounds((min_lon, min_lat, max_lon, max_lat), res=0.05)\n", - "cent.check()\n", "cent.plot();" ] }, @@ -302,8 +301,6 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Almost every class in CLIMADA has a `check()` method, as used above. This verifies that the necessary data for an object is correctly provided and logs the optional variables that are not present. It is always worth running it after filling an instance of an object.\n", - "\n", "### Hazard footprint\n", "\n", "Now we're ready to create our hazard object. This will be a `TropCyclone` class, which inherits from the `Hazard` class, and has the `from_tracks` constructor method to create a hazard from a `TCTracks` object at given centroids." @@ -339,7 +336,7 @@ "from climada.hazard import TropCyclone\n", "\n", "haz = TropCyclone.from_tracks(tracks, centroids=cent)\n", - "haz.check()" + "haz.check() # verifies that the necessary data for the Hazard object is correctly provided" ] }, { @@ -563,7 +560,6 @@ "exp_litpop = LitPop.from_countries(\n", " \"Puerto Rico\", res_arcsec=120\n", ") # We'll go lower resolution than default to keep it simple\n", - "exp_litpop.set_geometry_points() # Set geodataframe geometries from lat lon data\n", "\n", "exp_litpop.plot_hexbin(pop_name=True, linewidth=4, buffer=0.1);" ] diff --git a/doc/tutorial/climada_engine_Forecast.ipynb b/doc/tutorial/climada_engine_Forecast.ipynb index 29c9a5930..2ab5bb841 100644 --- a/doc/tutorial/climada_engine_Forecast.ipynb +++ b/doc/tutorial/climada_engine_Forecast.ipynb @@ -257,9 +257,7 @@ "### generate exposure\n", "# find out which hazard coord to consider\n", "CHE_borders = u_plot._get_borders(\n", - " np.stack(\n", - " [exposure.gdf[\"latitude\"].values, exposure.gdf[\"longitude\"].values], axis=1\n", - " )\n", + " np.stack([exposure.latitude, exposure.longitude], axis=1)\n", ")\n", "centroid_selection = np.logical_and(\n", " np.logical_and(\n", diff --git a/doc/tutorial/climada_engine_Impact.ipynb b/doc/tutorial/climada_engine_Impact.ipynb index b6ea21cd8..a342a43b3 100644 --- a/doc/tutorial/climada_engine_Impact.ipynb +++ b/doc/tutorial/climada_engine_Impact.ipynb @@ -68,7 +68,7 @@ "| event_id |list(int)| id (>0) of each hazard event (Hazard.event_id)|\n", "| event_name |(list(str))| name of each event (Hazard.event_name)|\n", "| date |np.array| date of events (Hazard.date)|\n", - "| coord_exp |np.array| exposures coordinates [lat, lon] (in degrees) (Exposure.gdf['latitudes'], Exposure.gdf['longitude'])|\n", + "| coord_exp |np.array| exposures coordinates [lat, lon] (in degrees) (Exposure.latidue, Exposure.longitude)|\n", "| frequency |np.array| frequency of events (Hazard.frequency)|\n", "| frequency_unit |str| unit of event frequency, by default '1/year', i.e., annual (Hazard.frequency_unit)|\n", "| unit |str| value unit used (Exposure.value_unit)|\n", @@ -1486,9 +1486,7 @@ "\n", "# Set Hazard in Exposures points\n", "# set centroids from exposures coordinates\n", - "centr_pnt = Centroids.from_lat_lon(\n", - " exp_pnt.gdf[\"latitude\"].values, exp_pnt.gdf[\"longitude\"].values, exp_pnt.crs\n", - ")\n", + "centr_pnt = Centroids.from_lat_lon(exp_pnt.latitude, exp_pnt.longitude, exp_pnt.crs)\n", "# compute Hazard in that centroids\n", "tr_pnt = TCTracks.from_ibtracs_netcdf(storm_id=\"2007314N10093\")\n", "tc_pnt = TropCyclone.from_tracks(tr_pnt, centroids=centr_pnt)\n", @@ -2007,9 +2005,7 @@ "\n", "# compute sequence of hazards using TropCyclone video_intensity method\n", "exp_sea = add_sea(exp_video, (100, 5))\n", - "centr_video = Centroids.from_lat_lon(\n", - " exp_sea.gdf[\"latitude\"].values, exp_sea.gdf[\"longitude\"].values\n", - ")\n", + "centr_video = Centroids.from_lat_lon(exp_sea.latitude, exp_sea.longitude)\n", "centr_video.check()\n", "\n", "track_name = \"2017242N16333\"\n", diff --git a/doc/tutorial/climada_entity_Exposures.ipynb b/doc/tutorial/climada_entity_Exposures.ipynb index d46903e8f..a57079ef2 100644 --- a/doc/tutorial/climada_entity_Exposures.ipynb +++ b/doc/tutorial/climada_entity_Exposures.ipynb @@ -28,8 +28,11 @@ "\n", "### What does an exposure look like in CLIMADA?\n", "\n", - "An exposure is represented in the class `Exposures`, which contains a [geopandas](https://geopandas.readthedocs.io/en/latest/gallery/cartopy_convert.html) [GeoDataFrame](https://geopandas.readthedocs.io/en/latest/docs/user_guide/data_structures.html#geodataframe) that is accessible through the `Exposures` `gdf` attribute.\n", - "Certain columns of `gdf` _have to_ be specified, while others are optional (this means that the package `climada.engine` also works without these variables set.) The full list looks like this:" + "An exposure is represented in the class `Exposures`, which contains a [geopandas](https://geopandas.readthedocs.io/en/latest/gallery/cartopy_convert.html) [GeoDataFrame](https://geopandas.readthedocs.io/en/latest/docs/user_guide/data_structures.html#geodataframe) that is accessible through the `Exposures.data` attribute.\n", + "A \"geometry\" column is initialized in the `GeoDataFrame` of the `Exposures` object, other columns are optional at first but some have to be present or make a difference when it comes to do calculations.\n", + "Apart from these special columns the data frame may contain additional columns, they will simply be ignored in the context of CLIMADA.\n", + "\n", + "The full list of meaningful columns is this:" ] }, { @@ -37,37 +40,47 @@ "metadata": {}, "source": [ "\n", - "
\n", + "| Column | Data Type | Description | Meaningful in | Optional |\n", + "| :-------------------- | :------------ | :------------------------------------------------------------------------------------- | - | :-: |\n", + "| `geometry` | Point | the geometry column of the `GeoDataFrame`, i.e., latitude (y) and longitude (x) | centroids assignment | - |\n", + "| `value` | float | a value for each exposure | impact calculation | ✔* |\n", + "| `impf_*` | int | impact functions ids for hazard types.
important attribute, since it relates the exposures to the hazard by specifying the impf_act functions.
Ideally it should be set to the specific hazard (e.g. `impf_TC`) so that different hazards can be set
in the same Exposures (e.g. `impf_TC` and `impf_FL`). | impact calculation | ✔* |\n", + "| `centr_*` | int | centroids index for hazard type.
There might be different hazards defined: centr_TC, centr_FL, ...
Computed in method `assign_centroids()` | impact calculation | ✔* |\n", + "| `deductible` | float | deductible value for each exposure.
Used for insurance | impact calculation | ✔ |\n", + "| `cover` | float | cover value for each exposure.
Used for insurance | impact calculation | ✔ |\n", + "| `region_id` | int | region id (e.g. country ISO code) for each exposure | aggregation | ✔ |\n", + "| `category_id` | int | category id (e.g. building code) for each exposure | aggregation | ✔ |\n", "\n", - "| Mandatory columns | Data Type | Description |\n", - "| :-------------------- | :------------ | :------------------------------------------------------------------------------------- |\n", - "| `latitude` | float | latitude |\n", - "| `longitude` | float | longitude |\n", - "| `value` | float | a value for each exposure                                                                                                                 |\n", + "*) an Exposures object is valid without such a column, but it's required for impact calculation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Apart from `data` the `Exposures` object has the following attributes and properties:\n", "\n", "
\n", - "
\n", "\n", - "| Optional columns | Data Type | Description |\n", + "| Attributes | Data Type | Description |\n", "| :-------------------- | :------------ | :------------------------------------------------------------------------------------- |\n", - "| `impf_*` | int | impact functions ids for hazard types.
important attribute, since it relates the exposures to the hazard by specifying the impf_act functions.
Ideally it should be set to the specific hazard (e.g. `impf_TC`) so that different hazards can be set
in the same Exposures (e.g. `impf_TC` and `impf_FL`).
If not provided, set to default `impf_` with ids 1 in check(). |\n", - "| `geometry` | Point | geometry of type Point
Main feature of geopandas DataFrame extension
Computed in method `set_geometry_points()` |\n", - "| `deductible` | float | deductible value for each exposure.
Used for insurance |\n", - "| `cover` | float | cover value for each exposure.
Used for insurance |\n", - "| `category_id` | int | category id (e.g. building code) for each exposure |\n", - "| `region_id` | int | region id (e.g. country ISO code) for each exposure |\n", - "| `impf_*` | int | centroids index for hazard type.
There might be different hazards defined: centr_TC, centr_FL, ...
Computed in method `assign_centroids()` |\n", + "| `description` | str | describing origin and content of the exposures data |\n", + "| `ref_year` | int | reference year |\n", + "| `value_unit` | str | unit of the exposures' values |\n", "\n", "
\n", "
\n", "\n", - "| Metadata variables | Data Type | Description |\n", + "| Properties | Data Type | Description |\n", "| :-------------------- | :------------ | :------------------------------------------------------------------------------------- |\n", - "| `crs` | str or int | coordinate reference system, see GeoDataFrame.crs |\n", - "| `description` | str | describing origin and content of the exposures data |\n", - "| `ref_year` | int | reference year |\n", - "| `value_unit` | str | unit of the exposures' values |\n", - "| `meta` | dict | dictionary containing corresponding raster properties (if any):
width, height, crs and transform must be present at least (transform needs to contain upper left corner!).
Exposures might not contain all the points of the corresponding raster. |\n" + "| `geometry` | numpy.array[Point] | array of geometry values |\n", + "| `crs` | pyproj.CRS | coordinate reference system, see
GeoDataFrame.crs |\n", + "| `latitude` | numpy.array[float] | array of latitude values |\n", + "| `longitude` | numpy.array[float] | array of longitude values |\n", + "| `region_id` | numpy.array[int] | array of regeion_id values |\n", + "| `category_id` | numpy.array[int] | array of category_id values |\n", + "| `cover` | numpy.array[float] | array of cover values |\n", + "| `deductible` | numpy.array[float] | array of cover values |" ] }, { @@ -85,25 +98,71 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Exposures from a pandas DataFrame\n", - "\n", - "In case you are unfamiliar with the data structure, check out the [pandas DataFrame documentation](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html)." + "### Exposures from plain data" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [ { - "name": "stderr", + "name": "stdout", "output_type": "stream", "text": [ - "/Users/lseverino/miniforge3/envs/climada_env/lib/python3.9/site-packages/dask/dataframe/_pyarrow_compat.py:17: FutureWarning: Minimal version of pyarrow will soon be increased to 14.0.1. You are using 12.0.1. Please consider upgrading.\n", - " warnings.warn(\n" + "description: random values in a square\n", + "ref_year: 2018\n", + "value_unit: CHF\n", + "crs: EPSG:7316\n", + "data: (9 entries)\n", + " region_id impf_ geometry value\n", + "0 1 0 POINT (4.000 1.000) 0.035321\n", + "1 1 1 POINT (4.000 2.000) 0.570256\n", + "2 1 2 POINT (4.000 3.000) 0.927632\n", + "3 1 3 POINT (5.000 1.000) 0.805402\n", + "4 1 4 POINT (5.000 2.000) 0.236179\n", + "5 1 5 POINT (5.000 3.000) 0.848296\n", + "6 1 6 POINT (6.000 1.000) 0.520281\n", + "7 1 7 POINT (6.000 2.000) 0.036442\n", + "8 1 8 POINT (6.000 3.000) 0.780934\n" ] } ], + "source": [ + "import numpy as np\n", + "from climada.entity import Exposures\n", + "\n", + "latitude = [1, 2, 3] * 3\n", + "longitude = [4] * 3 + [5] * 3 + [6] * 3\n", + "exp_arr = Exposures(\n", + " lat=latitude, # list or array\n", + " lon=longitude, # instead of lat and lon one can provide an array of Points through the geometry argument\n", + " value=np.random.random_sample(len(latitude)), # a list or an array of floats\n", + " value_unit=\"CHF\",\n", + " crs=\"EPSG:7316\", # different formats are possible\n", + " description=\"random values in a square\",\n", + " data={\n", + " \"region_id\": 1,\n", + " \"impf_\": range(len(latitude)),\n", + " }, # data can also be an array or a data frame\n", + ")\n", + "print(exp_arr)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exposures from a pandas DataFrame\n", + "\n", + "In case you are unfamiliar with the data structure, check out the [pandas DataFrame documentation](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html)." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], "source": [ "import numpy as np\n", "from pandas import DataFrame\n", @@ -114,7 +173,7 @@ "exp_df = DataFrame()\n", "n_exp = 100 * 100\n", "# provide value\n", - "exp_df[\"value\"] = np.arange(n_exp)\n", + "exp_df[\"value\"] = np.random.random_sample(n_exp)\n", "# provide latitude and longitude\n", "lat, lon = np.mgrid[\n", " 15 : 35 : complex(0, np.sqrt(n_exp)), 20 : 40 : complex(0, np.sqrt(n_exp))\n", @@ -125,47 +184,153 @@ }, { "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "# For each exposure entry, specify which impact function should be taken for which hazard type.\n", - "# In this case, we only specify the IDs for tropical cyclone (TC); here, each exposure entry will be treated with\n", - "# the same impact function: the one that has ID '1':\n", - "# Of course, this will only be relevant at later steps during impact calculations.\n", - "exp_df[\"impf_TC\"] = np.ones(n_exp, int)" - ] - }, - { - "cell_type": "code", - "execution_count": 5, + "execution_count": 23, "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "exp_df is a DataFrame: \n", - "exp_df looks like:\n", - " value latitude longitude impf_TC\n", - "0 0 15.0 20.000000 1\n", - "1 1 15.0 20.202020 1\n", - "2 2 15.0 20.404040 1\n", - "3 3 15.0 20.606061 1\n", - "4 4 15.0 20.808081 1\n" - ] + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
valuelatitudelongitudeimpf_TC
00.53376415.020.0000001
10.99599315.020.2020201
20.60352315.020.4040401
30.75425315.020.6060611
40.30506615.020.8080811
...............
99950.48241635.039.1919191
99960.06904435.039.3939391
99970.11656035.039.5959601
99980.23985635.039.7979801
99990.09956835.040.0000001
\n", + "

10000 rows × 4 columns

\n", + "
" + ], + "text/plain": [ + " value latitude longitude impf_TC\n", + "0 0.533764 15.0 20.000000 1\n", + "1 0.995993 15.0 20.202020 1\n", + "2 0.603523 15.0 20.404040 1\n", + "3 0.754253 15.0 20.606061 1\n", + "4 0.305066 15.0 20.808081 1\n", + "... ... ... ... ...\n", + "9995 0.482416 35.0 39.191919 1\n", + "9996 0.069044 35.0 39.393939 1\n", + "9997 0.116560 35.0 39.595960 1\n", + "9998 0.239856 35.0 39.797980 1\n", + "9999 0.099568 35.0 40.000000 1\n", + "\n", + "[10000 rows x 4 columns]" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "# Let's have a look at the pandas DataFrame\n", - "print(\"exp_df is a DataFrame:\", str(type(exp_df)))\n", - "print(\"exp_df looks like:\")\n", - "print(exp_df.head())" + "# For each exposure entry, specify which impact function should be taken for which hazard type.\n", + "# In this case, we only specify the IDs for tropical cyclone (TC); here, each exposure entry will be treated with\n", + "# the same impact function: the one that has ID '1':\n", + "# Of course, this will only be relevant at later steps during impact calculations.\n", + "exp_df[\"impf_TC\"] = np.ones(n_exp, int)\n", + "exp_df" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 24, "metadata": {}, "outputs": [ { @@ -174,22 +339,7 @@ "text": [ "exp has the type: \n", "and contains a GeoDataFrame exp.gdf: \n", - "2024-04-12 14:39:01,086 - climada.util.coordinates - INFO - Setting geometry points.\n", - "\n", - "check method logs:\n", - "2024-04-12 14:39:01,093 - climada.entity.exposures.base - INFO - category_id not set.\n", - "2024-04-12 14:39:01,093 - climada.entity.exposures.base - INFO - cover not set.\n", - "2024-04-12 14:39:01,093 - climada.entity.exposures.base - INFO - deductible not set.\n", - "2024-04-12 14:39:01,094 - climada.entity.exposures.base - INFO - region_id not set.\n", - "2024-04-12 14:39:01,094 - climada.entity.exposures.base - INFO - centr_ not set.\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/lseverino/Documents/PhD/workspace/climada_python/climada/util/coordinates.py:2755: FutureWarning: You are adding a column named 'geometry' to a GeoDataFrame constructed without an active geometry column. Currently, this automatically sets the active geometry column to 'geometry' but in the future that will no longer happen. Instead, either provide geometry to the GeoDataFrame constructor (GeoDataFrame(... geometry=GeoSeries()) or use `set_geometry('geometry')` to explicitly set the active geometry column.\n", - " df_val['geometry'] = gpd.GeoSeries(\n" + "\n" ] } ], @@ -197,55 +347,38 @@ "# Generate Exposures from the pandas DataFrame. This step converts the DataFrame into\n", "# a CLIMADA Exposures instance!\n", "exp = Exposures(exp_df)\n", - "print(\"exp has the type:\", str(type(exp)))\n", - "print(\"and contains a GeoDataFrame exp.gdf:\", str(type(exp.gdf)))\n", - "\n", - "# set geometry attribute (shapely Points) from GeoDataFrame from latitude and longitude\n", - "exp.set_geometry_points()\n", - "print(\"\\n\" + \"check method logs:\")\n", - "\n", - "# always apply the check() method in the end. It puts metadata that has not been assigned,\n", - "# and points out missing mandatory data\n", - "exp.check()" + "print(f\"exp has the type: {type(exp)}\")\n", + "print(f\"and contains a GeoDataFrame exp.gdf: {type(exp.gdf)}\\n\")" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\n", - "exp looks like:\n", "description: None\n", "ref_year: 2018\n", "value_unit: USD\n", - "meta: {'crs': 'EPSG:4326'}\n", "crs: EPSG:4326\n", - "data:\n", - " value latitude longitude impf_TC geometry\n", - "0 0 15.0 20.000000 1 POINT (20.00000 15.00000)\n", - "1 1 15.0 20.202020 1 POINT (20.20202 15.00000)\n", - "2 2 15.0 20.404040 1 POINT (20.40404 15.00000)\n", - "3 3 15.0 20.606061 1 POINT (20.60606 15.00000)\n", - "4 4 15.0 20.808081 1 POINT (20.80808 15.00000)\n", - "... ... ... ... ... ...\n", - "9995 9995 35.0 39.191919 1 POINT (39.19192 35.00000)\n", - "9996 9996 35.0 39.393939 1 POINT (39.39394 35.00000)\n", - "9997 9997 35.0 39.595960 1 POINT (39.59596 35.00000)\n", - "9998 9998 35.0 39.797980 1 POINT (39.79798 35.00000)\n", - "9999 9999 35.0 40.000000 1 POINT (40.00000 35.00000)\n", - "\n", - "[10000 rows x 5 columns]\n" + "data: (10000 entries)\n", + " value impf_TC geometry\n", + "0 0.533764 1 POINT (20.00000 15.00000)\n", + "1 0.995993 1 POINT (20.20202 15.00000)\n", + "2 0.603523 1 POINT (20.40404 15.00000)\n", + "3 0.754253 1 POINT (20.60606 15.00000)\n", + "9996 0.069044 1 POINT (39.39394 35.00000)\n", + "9997 0.116560 1 POINT (39.59596 35.00000)\n", + "9998 0.239856 1 POINT (39.79798 35.00000)\n", + "9999 0.099568 1 POINT (40.00000 35.00000)\n" ] } ], "source": [ "# let's have a look at the Exposures instance we created!\n", - "print(\"\\n\" + \"exp looks like:\")\n", "print(exp)" ] }, @@ -262,28 +395,14 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "World is a GeoDataFrame: \n", - "World looks like:\n", - " name geometry\n", - "0 Vatican City POINT (12.45339 41.90328)\n", - "1 San Marino POINT (12.44177 43.93610)\n", - "2 Vaduz POINT (9.51667 47.13372)\n", - "3 Lobamba POINT (31.20000 -26.46667)\n", - "4 Luxembourg POINT (6.13000 49.61166)\n" - ] - }, { "name": "stderr", "output_type": "stream", "text": [ - "/var/folders/y5/t1z41tgj7dv50sm2_29dn8740000gp/T/ipykernel_16894/4205155986.py:6: FutureWarning: The geopandas.dataset module is deprecated and will be removed in GeoPandas 1.0. You can get the original 'naturalearth_cities' data from https://www.naturalearthdata.com/downloads/110m-cultural-vectors/.\n", + "C:\\Users\\me\\AppData\\Local\\Temp\\ipykernel_31104\\2272990317.py:6: FutureWarning: The geopandas.dataset module is deprecated and will be removed in GeoPandas 1.0. You can get the original 'naturalearth_cities' data from https://www.naturalearthdata.com/downloads/110m-cultural-vectors/.\n", " world = gpd.read_file(gpd.datasets.get_path('naturalearth_cities'))\n" ] } @@ -294,54 +413,66 @@ "from climada.entity import Exposures\n", "\n", "# Read spatial info from an external file into GeoDataFrame\n", - "world = gpd.read_file(gpd.datasets.get_path(\"naturalearth_cities\"))\n", - "print(\"World is a GeoDataFrame:\", str(type(world)))\n", - "print(\"World looks like:\")\n", - "print(world.head())" + "world = gpd.read_file(gpd.datasets.get_path(\"naturalearth_cities\"))" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 27, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\n", - "exp_gpd is an Exposures: \n", - "2024-04-12 14:41:09,131 - climada.entity.exposures.base - INFO - Setting latitude and longitude attributes.\n" + "description: None\n", + "ref_year: 2018\n", + "value_unit: USD\n", + "crs: EPSG:4326\n", + "data: (243 entries)\n", + " name value geometry\n", + "0 Vatican City 0.876947 POINT (12.45339 41.90328)\n", + "1 San Marino 0.895454 POINT (12.44177 43.93610)\n", + "2 Vaduz 0.373366 POINT (9.51667 47.13372)\n", + "3 Lobamba 0.422729 POINT (31.20000 -26.46667)\n", + "239 São Paulo 0.913955 POINT (-46.62697 -23.55673)\n", + "240 Sydney 0.514479 POINT (151.21255 -33.87137)\n", + "241 Singapore 0.830635 POINT (103.85387 1.29498)\n", + "242 Hong Kong 0.764571 POINT (114.18306 22.30693)\n" ] } ], "source": [ "# Generate Exposures: value, latitude and longitude for each exposure entry.\n", + "world[\"value\"] = np.arange(n_exp)\n", "# Convert GeoDataFrame into Exposure instance\n", "exp_gpd = Exposures(world)\n", - "print(\"\\n\" + \"exp_gpd is an Exposures:\", str(type(exp_gpd)))\n", - "# add random values to entries\n", - "exp_gpd.gdf[\"value\"] = np.arange(world.shape[0])\n", - "# set latitude and longitude attributes from geometry\n", - "exp_gpd.set_lat_lon()" + "print(exp_gpd)" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\n", - "check method logs:\n", - "2024-04-12 14:41:24,338 - climada.entity.exposures.base - INFO - category_id not set.\n", - "2024-04-12 14:41:24,341 - climada.entity.exposures.base - INFO - cover not set.\n", - "2024-04-12 14:41:24,343 - climada.entity.exposures.base - INFO - deductible not set.\n", - "2024-04-12 14:41:24,344 - climada.entity.exposures.base - INFO - region_id not set.\n", - "2024-04-12 14:41:24,344 - climada.entity.exposures.base - INFO - centr_ not set.\n" + "description: None\n", + "ref_year: 2018\n", + "value_unit: USD\n", + "crs: EPSG:4326\n", + "data: (243 entries)\n", + " name value geometry impf_TC\n", + "0 Vatican City 0.876947 POINT (12.45339 41.90328) 1\n", + "1 San Marino 0.895454 POINT (12.44177 43.93610) 1\n", + "2 Vaduz 0.373366 POINT (9.51667 47.13372) 1\n", + "3 Lobamba 0.422729 POINT (31.20000 -26.46667) 1\n", + "239 São Paulo 0.913955 POINT (-46.62697 -23.55673) 1\n", + "240 Sydney 0.514479 POINT (151.21255 -33.87137) 1\n", + "241 Singapore 0.830635 POINT (103.85387 1.29498) 1\n", + "242 Hong Kong 0.764571 POINT (114.18306 22.30693) 1\n" ] } ], @@ -350,73 +481,7 @@ "# In this case, we only specify the IDs for tropical cyclone (TC); here, each exposure entry will be treated with\n", "# the same impact function: the one that has ID '1':\n", "# Of course, this will only be relevant at later steps during impact calculations.\n", - "exp_gpd.gdf[\"impf_TC\"] = np.ones(world.shape[0], int)\n", - "print(\"\\n\" + \"check method logs:\")\n", - "\n", - "# as always, run check method to assign meta-data and check for missing mandatory variables.\n", - "exp_gpd.check()" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\u001b[1;03;30;30mexp_gpd looks like:\u001b[0m\n", - "ref_year: 2018\n", - "value_unit: USD\n", - "meta: {'crs': \n", - "Name: WGS 84\n", - "Axis Info [ellipsoidal]:\n", - "- Lat[north]: Geodetic latitude (degree)\n", - "- Lon[east]: Geodetic longitude (degree)\n", - "Area of Use:\n", - "- name: World.\n", - "- bounds: (-180.0, -90.0, 180.0, 90.0)\n", - "Datum: World Geodetic System 1984\n", - "- Ellipsoid: WGS 84\n", - "- Prime Meridian: Greenwich\n", - "}\n", - "crs: epsg:4326\n", - "data:\n", - " name geometry value latitude longitude \\\n", - "0 Vatican City POINT (12.45339 41.90328) 0 41.903282 12.453387 \n", - "1 San Marino POINT (12.44177 43.93610) 1 43.936096 12.441770 \n", - "2 Vaduz POINT (9.51667 47.13372) 2 47.133724 9.516669 \n", - "3 Luxembourg POINT (6.13000 49.61166) 3 49.611660 6.130003 \n", - "4 Palikir POINT (158.14997 6.91664) 4 6.916644 158.149974 \n", - ".. ... ... ... ... ... \n", - "197 Cairo POINT (31.24802 30.05191) 197 30.051906 31.248022 \n", - "198 Tokyo POINT (139.74946 35.68696) 198 35.686963 139.749462 \n", - "199 Paris POINT (2.33139 48.86864) 199 48.868639 2.331389 \n", - "200 Santiago POINT (-70.66899 -33.44807) 200 -33.448068 -70.668987 \n", - "201 Singapore POINT (103.85387 1.29498) 201 1.294979 103.853875 \n", - "\n", - " impf_TC \n", - "0 1 \n", - "1 1 \n", - "2 1 \n", - "3 1 \n", - "4 1 \n", - ".. ... \n", - "197 1 \n", - "198 1 \n", - "199 1 \n", - "200 1 \n", - "201 1 \n", - "\n", - "[202 rows x 6 columns]\n" - ] - } - ], - "source": [ - "# let's have a look at the Exposures instance we created!\n", - "print(\"\\n\" + \"\\x1b[1;03;30;30m\" + \"exp_gpd looks like:\" + \"\\x1b[0m\")\n", + "exp_gpd.data[\"impf_TC\"] = np.ones(world.shape[0], int)\n", "print(exp_gpd)" ] }, @@ -429,7 +494,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 30, "metadata": {}, "outputs": [ { @@ -462,73 +527,197 @@ " \n", " \n", " name\n", - " geometry\n", " value\n", - " latitude\n", - " longitude\n", " impf_TC\n", + " geometry\n", " \n", " \n", " \n", " \n", " 11\n", " Tarawa\n", - " POINT (173.01757 1.33819)\n", - " 11\n", - " 1.338188\n", - " 173.017571\n", + " 0.107688\n", " 1\n", + " POINT (173.01757 1.33819)\n", " \n", " \n", " 15\n", " Kigali\n", - " POINT (30.05859 -1.95164)\n", - " 15\n", - " -1.951644\n", - " 30.058586\n", + " 0.218687\n", " 1\n", + " POINT (30.05859 -1.95164)\n", " \n", " \n", " 17\n", " Juba\n", - " POINT (31.58003 4.82998)\n", - " 17\n", - " 4.829975\n", - " 31.580026\n", + " 0.763743\n", " 1\n", + " POINT (31.58003 4.82998)\n", " \n", " \n", " 31\n", " Putrajaya\n", - " POINT (101.69504 2.93252)\n", - " 31\n", - " 2.932515\n", - " 101.695037\n", + " 0.533607\n", " 1\n", + " POINT (101.69504 2.93252)\n", " \n", " \n", " 37\n", " Bujumbura\n", + " 0.127881\n", + " 1\n", " POINT (29.36001 -3.37609)\n", - " 37\n", - " -3.376087\n", - " 29.360006\n", + " \n", + " \n", + " 58\n", + " Kampala\n", + " 0.079019\n", + " 1\n", + " POINT (32.58138 0.31860)\n", + " \n", + " \n", + " 75\n", + " Mogadishu\n", + " 0.696766\n", + " 1\n", + " POINT (45.36473 2.06863)\n", + " \n", + " \n", + " 88\n", + " Quito\n", + " 0.212070\n", + " 1\n", + " POINT (-78.50200 -0.21304)\n", + " \n", + " \n", + " 93\n", + " Malabo\n", + " 0.088459\n", + " 1\n", + " POINT (8.78328 3.75002)\n", + " \n", + " \n", + " 99\n", + " Libreville\n", + " 0.929139\n", + " 1\n", + " POINT (9.45796 0.38539)\n", + " \n", + " \n", + " 108\n", + " Brazzaville\n", + " 0.795766\n", + " 1\n", + " POINT (15.28274 -4.25724)\n", + " \n", + " \n", + " 113\n", + " Bandar Seri Begawan\n", + " 0.655856\n", + " 1\n", + " POINT (114.93328 4.88333)\n", + " \n", + " \n", + " 116\n", + " Bangui\n", + " 0.398002\n", + " 1\n", + " POINT (18.55829 4.36664)\n", + " \n", + " \n", + " 117\n", + " Yaoundé\n", + " 0.240599\n", + " 1\n", + " POINT (11.51470 3.86865)\n", + " \n", + " \n", + " 134\n", + " Victoria\n", + " 0.956208\n", + " 1\n", + " POINT (55.44999 -4.61663)\n", + " \n", + " \n", + " 135\n", + " São Tomé\n", + " 0.726704\n", + " 1\n", + " POINT (6.72965 0.33747)\n", + " \n", + " \n", + " 138\n", + " Malé\n", + " 0.996017\n", + " 1\n", + " POINT (73.50890 4.17204)\n", + " \n", + " \n", + " 158\n", + " Kuala Lumpur\n", + " 0.880473\n", + " 1\n", + " POINT (101.68870 3.13980)\n", + " \n", + " \n", + " 201\n", + " Kinshasa\n", + " 0.074387\n", + " 1\n", + " POINT (15.31303 -4.32778)\n", + " \n", + " \n", + " 228\n", + " Nairobi\n", + " 0.297170\n", + " 1\n", + " POINT (36.81471 -1.28140)\n", + " \n", + " \n", + " 230\n", + " Bogota\n", + " 0.420891\n", " 1\n", + " POINT (-74.08529 4.59837)\n", + " \n", + " \n", + " 241\n", + " Singapore\n", + " 0.830635\n", + " 1\n", + " POINT (103.85387 1.29498)\n", " \n", " \n", "\n", "" ], "text/plain": [ - " name geometry value latitude longitude impf_TC\n", - "11 Tarawa POINT (173.01757 1.33819) 11 1.338188 173.017571 1\n", - "15 Kigali POINT (30.05859 -1.95164) 15 -1.951644 30.058586 1\n", - "17 Juba POINT (31.58003 4.82998) 17 4.829975 31.580026 1\n", - "31 Putrajaya POINT (101.69504 2.93252) 31 2.932515 101.695037 1\n", - "37 Bujumbura POINT (29.36001 -3.37609) 37 -3.376087 29.360006 1" + " name value impf_TC geometry\n", + "11 Tarawa 0.107688 1 POINT (173.01757 1.33819)\n", + "15 Kigali 0.218687 1 POINT (30.05859 -1.95164)\n", + "17 Juba 0.763743 1 POINT (31.58003 4.82998)\n", + "31 Putrajaya 0.533607 1 POINT (101.69504 2.93252)\n", + "37 Bujumbura 0.127881 1 POINT (29.36001 -3.37609)\n", + "58 Kampala 0.079019 1 POINT (32.58138 0.31860)\n", + "75 Mogadishu 0.696766 1 POINT (45.36473 2.06863)\n", + "88 Quito 0.212070 1 POINT (-78.50200 -0.21304)\n", + "93 Malabo 0.088459 1 POINT (8.78328 3.75002)\n", + "99 Libreville 0.929139 1 POINT (9.45796 0.38539)\n", + "108 Brazzaville 0.795766 1 POINT (15.28274 -4.25724)\n", + "113 Bandar Seri Begawan 0.655856 1 POINT (114.93328 4.88333)\n", + "116 Bangui 0.398002 1 POINT (18.55829 4.36664)\n", + "117 Yaoundé 0.240599 1 POINT (11.51470 3.86865)\n", + "134 Victoria 0.956208 1 POINT (55.44999 -4.61663)\n", + "135 São Tomé 0.726704 1 POINT (6.72965 0.33747)\n", + "138 Malé 0.996017 1 POINT (73.50890 4.17204)\n", + "158 Kuala Lumpur 0.880473 1 POINT (101.68870 3.13980)\n", + "201 Kinshasa 0.074387 1 POINT (15.31303 -4.32778)\n", + "228 Nairobi 0.297170 1 POINT (36.81471 -1.28140)\n", + "230 Bogota 0.420891 1 POINT (-74.08529 4.59837)\n", + "241 Singapore 0.830635 1 POINT (103.85387 1.29498)" ] }, - "execution_count": 11, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -536,15 +725,15 @@ "source": [ "# Example 1: extract data in a region: latitudes between -5 and 5\n", "sel_exp = exp_gpd.copy() # to keep the original exp_gpd Exposures data\n", - "sel_exp.gdf = sel_exp.gdf.cx[:, -5:5]\n", + "sel_exp.data = sel_exp.data.cx[:, -5:5]\n", "\n", "print(\"\\n\" + \"sel_exp contains a subset of the original data\")\n", - "sel_exp.gdf.head()" + "sel_exp.data" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 29, "metadata": {}, "outputs": [ { @@ -577,83 +766,69 @@ " \n", " \n", " name\n", - " geometry\n", " value\n", - " latitude\n", - " longitude\n", " impf_TC\n", + " geometry\n", " \n", " \n", " \n", " \n", " 36\n", " Porto-Novo\n", - " POINT (2.61663 6.48331)\n", - " 36\n", - " 6.483311\n", - " 2.616626\n", + " 0.573619\n", " 1\n", + " POINT (2.61663 6.48331)\n", " \n", " \n", " 46\n", " Lomé\n", - " POINT (1.22081 6.13388)\n", - " 46\n", - " 6.133883\n", - " 1.220811\n", + " 0.176892\n", " 1\n", + " POINT (1.22081 6.13388)\n", " \n", " \n", " 93\n", " Malabo\n", - " POINT (8.78328 3.75002)\n", - " 93\n", - " 3.750015\n", - " 8.783278\n", + " 0.088459\n", " 1\n", + " POINT (8.78328 3.75002)\n", " \n", " \n", " 123\n", " Cotonou\n", - " POINT (2.40435 6.36298)\n", - " 123\n", - " 6.362980\n", - " 2.404355\n", + " 0.441703\n", " 1\n", + " POINT (2.40435 6.36298)\n", " \n", " \n", " 135\n", " São Tomé\n", - " POINT (6.72965 0.33747)\n", - " 135\n", - " 0.337466\n", - " 6.729650\n", + " 0.726704\n", " 1\n", + " POINT (6.72965 0.33747)\n", " \n", " \n", " 225\n", " Lagos\n", - " POINT (3.38959 6.44521)\n", - " 225\n", - " 6.445208\n", - " 3.389585\n", + " 0.990135\n", " 1\n", + " POINT (3.38959 6.44521)\n", " \n", " \n", "\n", "" ], "text/plain": [ - " name geometry value latitude longitude impf_TC\n", - "36 Porto-Novo POINT (2.61663 6.48331) 36 6.483311 2.616626 1\n", - "46 Lomé POINT (1.22081 6.13388) 46 6.133883 1.220811 1\n", - "93 Malabo POINT (8.78328 3.75002) 93 3.750015 8.783278 1\n", - "123 Cotonou POINT (2.40435 6.36298) 123 6.362980 2.404355 1\n", - "135 São Tomé POINT (6.72965 0.33747) 135 0.337466 6.729650 1\n", - "225 Lagos POINT (3.38959 6.44521) 225 6.445208 3.389585 1" + " name value impf_TC geometry\n", + "36 Porto-Novo 0.573619 1 POINT (2.61663 6.48331)\n", + "46 Lomé 0.176892 1 POINT (1.22081 6.13388)\n", + "93 Malabo 0.088459 1 POINT (8.78328 3.75002)\n", + "123 Cotonou 0.441703 1 POINT (2.40435 6.36298)\n", + "135 São Tomé 0.726704 1 POINT (6.72965 0.33747)\n", + "225 Lagos 0.990135 1 POINT (3.38959 6.44521)" ] }, - "execution_count": 12, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -665,23 +840,22 @@ "sel_polygon = exp_gpd.copy() # to keep the original exp_gpd Exposures data\n", "\n", "poly = Polygon([(0, -10), (0, 10), (10, 5)])\n", - "sel_polygon.gdf = sel_polygon.gdf[sel_polygon.gdf.intersects(poly)]\n", + "sel_polygon.data = sel_polygon.gdf[sel_polygon.gdf.intersects(poly)]\n", "\n", "# Let's have a look. Again, the sub-selection is a GeoDataFrame!\n", "print(\"\\n\" + \"sel_exp contains a subset of the original data\")\n", - "sel_polygon.gdf" + "sel_polygon.data" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 31, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "2024-04-12 14:42:09,423 - climada.entity.exposures.base - INFO - Setting latitude and longitude attributes.\n", "\n", "the crs has changed to EPSG:3395\n", "the values for latitude and longitude are now according to the new coordinate system: \n" @@ -709,91 +883,69 @@ " \n", " \n", " name\n", - " geometry\n", " value\n", - " latitude\n", - " longitude\n", " impf_TC\n", + " geometry\n", " \n", " \n", " \n", " \n", " 36\n", " Porto-Novo\n", - " POINT (291281.418 718442.692)\n", - " 36\n", - " 718442.691819\n", - " 291281.418257\n", + " 0.573619\n", " 1\n", + " POINT (291281.418 718442.692)\n", " \n", " \n", " 46\n", " Lomé\n", - " POINT (135900.092 679566.331)\n", - " 46\n", - " 679566.330586\n", - " 135900.092271\n", + " 0.176892\n", " 1\n", + " POINT (135900.092 679566.331)\n", " \n", " \n", " 93\n", " Malabo\n", - " POINT (977749.979 414955.553)\n", - " 93\n", - " 414955.553292\n", - " 977749.978796\n", + " 0.088459\n", " 1\n", + " POINT (977749.979 414955.553)\n", " \n", " \n", " 123\n", " Cotonou\n", - " POINT (267651.551 705052.049)\n", - " 123\n", - " 705052.049006\n", - " 267651.551008\n", + " 0.441703\n", " 1\n", + " POINT (267651.551 705052.049)\n", " \n", " \n", " 135\n", " São Tomé\n", - " POINT (749141.190 37315.322)\n", - " 135\n", - " 37315.322206\n", - " 749141.189651\n", + " 0.726704\n", " 1\n", + " POINT (749141.190 37315.322)\n", " \n", " \n", " 225\n", " Lagos\n", - " POINT (377326.898 714202.107)\n", - " 225\n", - " 714202.106826\n", - " 377326.898464\n", + " 0.990135\n", " 1\n", + " POINT (377326.898 714202.107)\n", " \n", " \n", "\n", "" ], "text/plain": [ - " name geometry value latitude \\\n", - "36 Porto-Novo POINT (291281.418 718442.692) 36 718442.691819 \n", - "46 Lomé POINT (135900.092 679566.331) 46 679566.330586 \n", - "93 Malabo POINT (977749.979 414955.553) 93 414955.553292 \n", - "123 Cotonou POINT (267651.551 705052.049) 123 705052.049006 \n", - "135 São Tomé POINT (749141.190 37315.322) 135 37315.322206 \n", - "225 Lagos POINT (377326.898 714202.107) 225 714202.106826 \n", - "\n", - " longitude impf_TC \n", - "36 291281.418257 1 \n", - "46 135900.092271 1 \n", - "93 977749.978796 1 \n", - "123 267651.551008 1 \n", - "135 749141.189651 1 \n", - "225 377326.898464 1 " + " name value impf_TC geometry\n", + "36 Porto-Novo 0.573619 1 POINT (291281.418 718442.692)\n", + "46 Lomé 0.176892 1 POINT (135900.092 679566.331)\n", + "93 Malabo 0.088459 1 POINT (977749.979 414955.553)\n", + "123 Cotonou 0.441703 1 POINT (267651.551 705052.049)\n", + "135 São Tomé 0.726704 1 POINT (749141.190 37315.322)\n", + "225 Lagos 0.990135 1 POINT (377326.898 714202.107)" ] }, - "execution_count": 13, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } @@ -806,20 +958,20 @@ "print(\n", " \"the values for latitude and longitude are now according to the new coordinate system: \"\n", ")\n", - "sel_polygon.gdf" + "sel_polygon.data" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "exp_all type and number of rows: 25\n", - "number of unique rows: 23\n" + "exp_all type and number of rows: 28\n", + "number of unique rows: 26\n" ] }, { @@ -842,336 +994,111 @@ "\n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
namegeometryvaluelatitudelongitudeimpf_TC
0LomePOINT (135900.088 679566.334)36679566.3339521.359001e+051
1MalaboPOINT (977749.984 414955.551)84414955.5508579.777500e+051
2CotonouPOINT (280307.458 709388.810)113709388.8101602.803075e+051
3Sao TomePOINT (749550.327 36865.909)12536865.9086827.495503e+051
4TarawaPOINT (19260227.883 147982.749)9147982.7489781.926023e+071
\n", - "" - ], - "text/plain": [ - " name geometry value latitude \\\n", - "0 Lome POINT (135900.088 679566.334) 36 679566.333952 \n", - "1 Malabo POINT (977749.984 414955.551) 84 414955.550857 \n", - "2 Cotonou POINT (280307.458 709388.810) 113 709388.810160 \n", - "3 Sao Tome POINT (749550.327 36865.909) 125 36865.908682 \n", - "4 Tarawa POINT (19260227.883 147982.749) 9 147982.748978 \n", - "\n", - " longitude impf_TC \n", - "0 1.359001e+05 1 \n", - "1 9.777500e+05 1 \n", - "2 2.803075e+05 1 \n", - "3 7.495503e+05 1 \n", - "4 1.926023e+07 1 " - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Example 4: concatenate exposures\n", - "exp_all = Exposures.concat([sel_polygon, sel_exp.to_crs(epsg=3395)])\n", - "\n", - "# the output is of type Exposures\n", - "print(\"exp_all type and number of rows:\", type(exp_all), exp_all.gdf.shape[0])\n", - "print(\"number of unique rows:\", exp_all.gdf.drop_duplicates().shape[0])\n", - "\n", - "# NaNs will appear in the missing values\n", - "exp_all.gdf.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "### Exposures of any file type supported by Geopandas and Pandas\n", - "\n", - "Geopandas can read almost any vector-based spatial data format including ESRI shapefile, GeoJSON files and more, see [readers geopandas](http://geopandas.org/io.html). Pandas supports formats such as csv, html or sql; see [readers pandas](https://pandas.pydata.org/pandas-docs/stable/io.html). Using the corresponding readers, `DataFrame` and `GeoDataFrame` can be filled and provided to `Exposures` following the previous examples." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "### Exposures from an excel file\n", - "\n", - "If you manually collect exposure data, Excel may be your preferred option. \n", - "In this case, it is easiest if you format your data according to the structure provided in the template `climada_python/climada/data/system/entity_template.xlsx`, in the sheet `assets`." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "exp_templ is a DataFrame: \n", - "exp_templ looks like:\n" - ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", "
latitudelongitudevaluedeductiblecoverregion_idcategory_idimpf_TCcentr_TCimpf_FLcentr_FL
026.933899-80.1287991.392750e+1001.392750e+10111111
126.957203-80.0982841.259606e+1001.259606e+10111212namevalueimpf_TCgeometry
226.783846-80.7489471.259606e+1001.259606e+10111323Kuala Lumpur0.88047313POINT (11319934.225 347356.996)
326.645524-80.5507041.259606e+1001.259606e+1024Kinshasa0.074387111414POINT (1704638.257 -479002.730)
426.897796-80.5969291.259606e+1001.259606e+10125Nairobi0.2971701POINT (4098194.882 -141701.948)
26Bogota0.42089115POINT (-8247136.736 509015.405)
27Singapore0.83063515POINT (11560960.460 143203.754)
\n", "
" ], "text/plain": [ - " latitude longitude value deductible cover region_id \\\n", - "0 26.933899 -80.128799 1.392750e+10 0 1.392750e+10 1 \n", - "1 26.957203 -80.098284 1.259606e+10 0 1.259606e+10 1 \n", - "2 26.783846 -80.748947 1.259606e+10 0 1.259606e+10 1 \n", - "3 26.645524 -80.550704 1.259606e+10 0 1.259606e+10 1 \n", - "4 26.897796 -80.596929 1.259606e+10 0 1.259606e+10 1 \n", - "\n", - " category_id impf_TC centr_TC impf_FL centr_FL \n", - "0 1 1 1 1 1 \n", - "1 1 1 2 1 2 \n", - "2 1 1 3 1 3 \n", - "3 1 1 4 1 4 \n", - "4 1 1 5 1 5 " + " name value impf_TC geometry\n", + "23 Kuala Lumpur 0.880473 1 POINT (11319934.225 347356.996)\n", + "24 Kinshasa 0.074387 1 POINT (1704638.257 -479002.730)\n", + "25 Nairobi 0.297170 1 POINT (4098194.882 -141701.948)\n", + "26 Bogota 0.420891 1 POINT (-8247136.736 509015.405)\n", + "27 Singapore 0.830635 1 POINT (11560960.460 143203.754)" ] }, - "execution_count": 15, + "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "import pandas as pd\n", - "from climada.util.constants import ENT_TEMPLATE_XLS\n", - "from climada.entity import Exposures\n", + "# Example 4: concatenate exposures\n", + "exp_all = Exposures.concat([sel_polygon, sel_exp.to_crs(epsg=3395)])\n", "\n", - "# Read your Excel file into a pandas DataFrame (we will use the template example for this demonstration):\n", - "file_name = ENT_TEMPLATE_XLS\n", - "exp_templ = pd.read_excel(file_name)\n", + "# the output is of type Exposures\n", + "print(\"exp_all type and number of rows:\", type(exp_all), exp_all.gdf.shape[0])\n", + "print(\"number of unique rows:\", exp_all.gdf.drop_duplicates().shape[0])\n", "\n", - "# Let's have a look at the data:\n", - "print(\"exp_templ is a DataFrame:\", str(type(exp_templ)))\n", - "print(\"exp_templ looks like:\")\n", - "exp_templ.head()" + "# NaNs will appear in the missing values\n", + "exp_all.data.tail()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "As we can see, the general structure is the same as always: the exposure has `latitude`, `longitude` and `value` columns. Further, this example specified several impact function ids: some for Tropical Cyclones (`impf_TC`), and some for Floods (`impf_FL`). It also provides some meta-info (`region_id`, `category_id`) and insurance info relevant to the impact calculation in later steps (`cover`, `deductible`)." + "\n", + "### Exposures of any file type supported by Geopandas and Pandas\n", + "\n", + "Geopandas can read almost any vector-based spatial data format including ESRI shapefile, GeoJSON files and more, see [readers geopandas](http://geopandas.org/io.html). Pandas supports formats such as csv, html or sql; see [readers pandas](https://pandas.pydata.org/pandas-docs/stable/io.html). Using the corresponding readers, `DataFrame` and `GeoDataFrame` can be filled and provided to `Exposures` following the previous examples." ] }, { - "cell_type": "code", - "execution_count": 17, + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "exp_templ is now an Exposures: \n", - "\n", - "set_geometry logs:\n", - "2024-04-12 14:44:29,822 - climada.util.coordinates - INFO - Setting geometry points.\n", - "\n", - "check exp_templ:\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/lseverino/Documents/PhD/workspace/climada_python/climada/util/coordinates.py:2755: FutureWarning: You are adding a column named 'geometry' to a GeoDataFrame constructed without an active geometry column. Currently, this automatically sets the active geometry column to 'geometry' but in the future that will no longer happen. Instead, either provide geometry to the GeoDataFrame constructor (GeoDataFrame(... geometry=GeoSeries()) or use `set_geometry('geometry')` to explicitly set the active geometry column.\n", - " df_val['geometry'] = gpd.GeoSeries(\n" - ] - } - ], "source": [ - "# Generate an Exposures instance from the dataframe.\n", - "exp_templ = Exposures(exp_templ)\n", - "print(\"\\n\" + \"exp_templ is now an Exposures:\", str(type(exp_templ)))\n", "\n", - "# set geometry attribute (shapely Points) from GeoDataFrame from latitude and longitude\n", - "print(\"\\n\" + \"set_geometry logs:\")\n", - "exp_templ.set_geometry_points()\n", - "# as always, run check method to include metadata and check for missing mandatory parameters\n", + "### Exposures from an excel file\n", "\n", - "print(\"\\n\" + \"check exp_templ:\")\n", - "exp_templ.check()" + "If you manually collect exposure data, Excel may be your preferred option. \n", + "In this case, it is easiest if you format your data according to the structure provided in the template `climada_python/climada/data/system/entity_template.xlsx`, in the sheet `assets`." ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 42, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\n", - "exp_templ.gdf looks like:\n" + "exp_templ is a DataFrame: \n", + "exp_templ looks like:\n" ] }, { @@ -1206,7 +1133,6 @@ " centr_TC\n", " impf_FL\n", " centr_FL\n", - " geometry\n", " \n", " \n", " \n", @@ -1223,7 +1149,6 @@ " 1\n", " 1\n", " 1\n", - " POINT (-80.12880 26.93390)\n", " \n", " \n", " 1\n", @@ -1238,7 +1163,6 @@ " 2\n", " 1\n", " 2\n", - " POINT (-80.09828 26.95720)\n", " \n", " \n", " 2\n", @@ -1253,7 +1177,6 @@ " 3\n", " 1\n", " 3\n", - " POINT (-80.74895 26.78385)\n", " \n", " \n", " 3\n", @@ -1268,7 +1191,6 @@ " 4\n", " 1\n", " 4\n", - " POINT (-80.55070 26.64552)\n", " \n", " \n", " 4\n", @@ -1283,7 +1205,6 @@ " 5\n", " 1\n", " 5\n", - " POINT (-80.59693 26.89780)\n", " \n", " \n", "\n", @@ -1297,191 +1218,157 @@ "3 26.645524 -80.550704 1.259606e+10 0 1.259606e+10 1 \n", "4 26.897796 -80.596929 1.259606e+10 0 1.259606e+10 1 \n", "\n", - " category_id impf_TC centr_TC impf_FL centr_FL \\\n", - "0 1 1 1 1 1 \n", - "1 1 1 2 1 2 \n", - "2 1 1 3 1 3 \n", - "3 1 1 4 1 4 \n", - "4 1 1 5 1 5 \n", - "\n", - " geometry \n", - "0 POINT (-80.12880 26.93390) \n", - "1 POINT (-80.09828 26.95720) \n", - "2 POINT (-80.74895 26.78385) \n", - "3 POINT (-80.55070 26.64552) \n", - "4 POINT (-80.59693 26.89780) " + " category_id impf_TC centr_TC impf_FL centr_FL \n", + "0 1 1 1 1 1 \n", + "1 1 1 2 1 2 \n", + "2 1 1 3 1 3 \n", + "3 1 1 4 1 4 \n", + "4 1 1 5 1 5 " ] }, - "execution_count": 18, + "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "# Let's have a look at our Exposures instance!\n", - "print(\"\\n\" + \"exp_templ.gdf looks like:\")\n", - "exp_templ.gdf.head()" + "import pandas as pd\n", + "from climada.util.constants import ENT_TEMPLATE_XLS\n", + "from climada.entity import Exposures\n", + "\n", + "# Read your Excel file into a pandas DataFrame (we will use the template example for this demonstration):\n", + "file_name = ENT_TEMPLATE_XLS\n", + "exp_templ = pd.read_excel(file_name)\n", + "\n", + "# Let's have a look at the data:\n", + "print(\"exp_templ is a DataFrame:\", str(type(exp_templ)))\n", + "print(\"exp_templ looks like:\")\n", + "exp_templ.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Exposures from a raster file\n", - "\n", - "Last but not least, you may have your exposure data stored in a raster file. Raster data may be read in from any file-type supported by [rasterio](https://rasterio.readthedocs.io/en/stable/). " + "As we can see, the general structure is the same as always: the exposure has `latitude`, `longitude` and `value` columns. Further, this example specified several impact function ids: some for Tropical Cyclones (`impf_TC`), and some for Floods (`impf_FL`). It also provides some meta-info (`region_id`, `category_id`) and insurance info relevant to the impact calculation in later steps (`cover`, `deductible`)." ] }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 43, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "2024-04-12 14:44:52,508 - climada.util.coordinates - INFO - Reading /Users/lseverino/climada/demo/data/SC22000_VE__M1.grd.gz\n" + "\n", + "exp_templ is now an Exposures: description: None\n", + "ref_year: 2018\n", + "value_unit: USD\n", + "crs: EPSG:4326\n", + "data: (24 entries)\n", + " value deductible cover region_id category_id impf_TC \\\n", + "0 1.392750e+10 0 1.392750e+10 1 1 1 \n", + "1 1.259606e+10 0 1.259606e+10 1 1 1 \n", + "2 1.259606e+10 0 1.259606e+10 1 1 1 \n", + "3 1.259606e+10 0 1.259606e+10 1 1 1 \n", + "20 1.259760e+10 0 1.259760e+10 1 1 1 \n", + "21 1.281454e+10 0 1.281454e+10 1 1 1 \n", + "22 1.262176e+10 0 1.262176e+10 1 1 1 \n", + "23 1.259754e+10 0 1.259754e+10 1 1 1 \n", + "\n", + " centr_TC impf_FL centr_FL geometry \n", + "0 1 1 1 POINT (-80.12880 26.93390) \n", + "1 2 1 2 POINT (-80.09828 26.95720) \n", + "2 3 1 3 POINT (-80.74895 26.78385) \n", + "3 4 1 4 POINT (-80.55070 26.64552) \n", + "20 21 1 21 POINT (-80.06858 26.71255) \n", + "21 22 1 22 POINT (-80.09070 26.66490) \n", + "22 23 1 23 POINT (-80.12540 26.66470) \n", + "23 24 1 24 POINT (-80.15140 26.66315) \n" ] } ], "source": [ - "from rasterio.windows import Window\n", - "from climada.util.constants import HAZ_DEMO_FL\n", - "from climada.entity import Exposures\n", + "# Generate an Exposures instance from the dataframe.\n", + "exp_templ = Exposures(exp_templ)\n", + "print(\"\\n\" + \"exp_templ is now an Exposures:\", exp_templ)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exposures from a raster file\n", "\n", - "# We take an example with a dummy raster file (HAZ_DEMO_FL), running the method set_from_raster directly loads the\n", - "# necessary info from the file into an Exposures instance.\n", - "exp_raster = Exposures.from_raster(HAZ_DEMO_FL, window=Window(10, 20, 50, 60))\n", - "# There are several keyword argument options that come with the set_from_raster method (such as\n", - "# specifying a window, if not the entire file should be read, or a bounding box. Check them out." + "Last but not least, you may have your exposure data stored in a raster file. Raster data may be read in from any file-type supported by [rasterio](https://rasterio.readthedocs.io/en/stable/). " ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "2024-04-12 14:44:53,848 - climada.entity.exposures.base - INFO - Setting impf_ to default impact functions ids 1.\n", - "2024-04-12 14:44:53,849 - climada.entity.exposures.base - INFO - category_id not set.\n", - "2024-04-12 14:44:53,850 - climada.entity.exposures.base - INFO - cover not set.\n", - "2024-04-12 14:44:53,850 - climada.entity.exposures.base - INFO - deductible not set.\n", - "2024-04-12 14:44:53,851 - climada.entity.exposures.base - INFO - geometry not set.\n", - "2024-04-12 14:44:53,851 - climada.entity.exposures.base - INFO - region_id not set.\n", - "2024-04-12 14:44:53,852 - climada.entity.exposures.base - INFO - centr_ not set.\n", - "Meta: {'driver': 'GSBG', 'dtype': 'float32', 'nodata': 1.701410009187828e+38, 'width': 50, 'height': 60, 'count': 1, 'crs': CRS.from_epsg(4326), 'transform': Affine(0.009000000000000341, 0.0, -69.2471495969998,\n", - " 0.0, -0.009000000000000341, 10.248220966978932)}\n" + "2024-10-04 17:19:03,632 - climada.util.coordinates - INFO - Reading C:\\Users\\me\\climada\\demo\\data\\SC22000_VE__M1.grd.gz\n" ] } ], "source": [ - "# As always, run the check method, such that metadata can be assigned and checked for missing mandatory parameters.\n", - "exp_raster.check()\n", - "print(\"Meta:\", exp_raster.meta)" + "from rasterio.windows import Window\n", + "from climada.util.constants import HAZ_DEMO_FL\n", + "from climada.entity import Exposures\n", + "\n", + "# We take an example with a dummy raster file (HAZ_DEMO_FL), running the method set_from_raster directly loads the\n", + "# necessary info from the file into an Exposures instance.\n", + "exp_raster = Exposures.from_raster(HAZ_DEMO_FL, window=Window(10, 20, 50, 60))\n", + "# There are several keyword argument options that come with the set_from_raster method (such as\n", + "# specifying a window, if not the entire file should be read, or a bounding box. Check them out." ] }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\n", - "exp_raster looks like:\n" + "2024-10-04 17:19:03,725 - climada.util.coordinates - INFO - Raster from resolution 0.009000000000000341 to 0.009000000000000341.\n" ] }, { "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
longitudelatitudevalueimpf_
0-69.2426510.2437210.01
1-69.2336510.2437210.01
2-69.2246510.2437210.01
3-69.2156510.2437210.01
4-69.2066510.2437210.01
\n", - "
" - ], "text/plain": [ - " longitude latitude value impf_\n", - "0 -69.24265 10.243721 0.0 1\n", - "1 -69.23365 10.243721 0.0 1\n", - "2 -69.22465 10.243721 0.0 1\n", - "3 -69.21565 10.243721 0.0 1\n", - "4 -69.20665 10.243721 0.0 1" + "{'crs': \n", + " Name: WGS 84\n", + " Axis Info [ellipsoidal]:\n", + " - Lat[north]: Geodetic latitude (degree)\n", + " - Lon[east]: Geodetic longitude (degree)\n", + " Area of Use:\n", + " - name: World.\n", + " - bounds: (-180.0, -90.0, 180.0, 90.0)\n", + " Datum: World Geodetic System 1984 ensemble\n", + " - Ellipsoid: WGS 84\n", + " - Prime Meridian: Greenwich,\n", + " 'height': 60,\n", + " 'width': 50,\n", + " 'transform': Affine(0.009000000000000341, 0.0, -69.2471495969998,\n", + " 0.0, -0.009000000000000341, 10.248220966978932)}" ] }, - "execution_count": 21, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "# Let's have a look at the Exposures instance!\n", - "print(\"\\n\" + \"exp_raster looks like:\")\n", - "exp_raster.gdf.head()" + "exp_raster.derive_raster()" ] }, { @@ -1499,14 +1386,46 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "\n" + "2024-10-04 17:19:04,888 - climada.entity.exposures.base - INFO - Reading C:\\Users\\me\\climada\\demo\\data\\exp_demo_today.h5\n", + "description: None\n", + "ref_year: 2016\n", + "value_unit: USD\n", + "crs: EPSG:4326\n", + "data: (50 entries)\n", + " value impf_TC deductible cover category_id region_id \\\n", + "0 1.392750e+10 1 0.0 1.392750e+10 1 1.0 \n", + "1 1.259606e+10 1 0.0 1.259606e+10 1 1.0 \n", + "2 1.259606e+10 1 0.0 1.259606e+10 1 1.0 \n", + "3 1.259606e+10 1 0.0 1.259606e+10 1 1.0 \n", + "46 1.264524e+10 1 0.0 1.264524e+10 1 1.0 \n", + "47 1.281438e+10 1 0.0 1.281438e+10 1 1.0 \n", + "48 1.260291e+10 1 0.0 1.260291e+10 1 1.0 \n", + "49 1.262482e+10 1 0.0 1.262482e+10 1 1.0 \n", + "\n", + " geometry \n", + "0 POINT (-80.12880 26.93390) \n", + "1 POINT (-80.09828 26.95720) \n", + "2 POINT (-80.74895 26.78385) \n", + "3 POINT (-80.55070 26.64552) \n", + "46 POINT (-80.11640 26.34907) \n", + "47 POINT (-80.08385 26.34635) \n", + "48 POINT (-80.24130 26.34802) \n", + "49 POINT (-80.15886 26.34796) \n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "c:\\Users\\me\\miniconda3\\envs\\climada_env\\Lib\\pickle.py:1718: UserWarning: Unpickling a shapely <2.0 geometry object. Please save the pickle again; shapely 2.1 will not have this compatibility.\n", + " setstate(state)\n" ] } ], @@ -1516,17 +1435,7 @@ "from climada.util.constants import EXP_DEMO_H5\n", "\n", "exp_hdf5 = Exposures.from_hdf5(EXP_DEMO_H5)\n", - "exp_hdf5.check()\n", - "print(type(exp_hdf5))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Before you leave ...\n", - "\n", - "After defining an `Exposures` instance use always the `check()` method to see which attributes are missing. This method will raise an ERROR if `value`, `longitude` or `latitude` ar missing and an INFO messages for the the optional variables not set." + "print(exp_hdf5)" ] }, { @@ -1783,7 +1692,8 @@ "id": "5d078d09", "metadata": {}, "source": [ - "Finally, as with any Python object, use climada's save option to save it in pickle format. Note however, that pickle has a transient format and should be avoided when possible." + "Optionally use climada's save option to save it in pickle format. This allows fast to quickly restore the object in its current state and take up your work right were you left it the next time.\n", + "Note however, that pickle has a transient format and is not suitable for storing data persistently." ] }, { @@ -1798,51 +1708,6 @@ "# this generates a results folder in the current path and stores the output there\n", "save(\"exp_templ.pkl.p\", exp_templ) # creates results folder and stores there" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "## Dask - improving performance for big exposure\n", - "\n", - "Dask is used in some methods of CLIMADA and can be activated easily by proving the scheduler." - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " value latitude longitude impf_TC\n", - "0 0 15.0 20.000000 1\n", - "1 1 15.0 20.202020 1\n", - "2 2 15.0 20.404040 1\n", - "3 3 15.0 20.606061 1\n", - "4 4 15.0 20.808081 1\n", - "CPU times: user 243 ms, sys: 116 ms, total: 359 ms\n", - "Wall time: 2.52 s\n", - " value latitude longitude impf_TC geometry\n", - "0 0 15.0 20.000000 1 POINT (20.00000 15.00000)\n", - "1 1 15.0 20.202020 1 POINT (20.20202 15.00000)\n", - "2 2 15.0 20.404040 1 POINT (20.40404 15.00000)\n", - "3 3 15.0 20.606061 1 POINT (20.60606 15.00000)\n", - "4 4 15.0 20.808081 1 POINT (20.80808 15.00000)\n" - ] - } - ], - "source": [ - "# set_geometry_points is expensive for big exposures\n", - "# for small amount of data, the execution time might be even greater when using dask\n", - "exp.gdf.drop(columns=[\"geometry\"], inplace=True)\n", - "print(exp.gdf.head())\n", - "%time exp.set_geometry_points(scheduler='processes')\n", - "print(exp.gdf.head())" - ] } ], "metadata": { @@ -1862,7 +1727,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.18" + "version": "3.11.9" }, "latex_envs": { "LaTeX_envs_menu_present": true, diff --git a/doc/tutorial/climada_entity_LitPop.ipynb b/doc/tutorial/climada_entity_LitPop.ipynb index 56c2d065a..b41728bf2 100644 --- a/doc/tutorial/climada_entity_LitPop.ipynb +++ b/doc/tutorial/climada_entity_LitPop.ipynb @@ -754,14 +754,13 @@ "ent_adm0 = LitPop.from_countries(\n", " \"CHE\", res_arcsec=120, fin_mode=\"gdp\", admin1_calc=False\n", ")\n", - "ent_adm0.set_geometry_points()\n", + "ent_adm0.check()\n", "\n", "ent_adm1 = LitPop.from_countries(\n", " \"CHE\", res_arcsec=120, fin_mode=\"gdp\", admin1_calc=True\n", ")\n", - "\n", - "ent_adm0.check()\n", "ent_adm1.check()\n", + "\n", "print(\"Done.\")" ] }, diff --git a/doc/tutorial/climada_hazard_TropCyclone.ipynb b/doc/tutorial/climada_hazard_TropCyclone.ipynb index 480d5c0b4..47df87fb7 100644 --- a/doc/tutorial/climada_hazard_TropCyclone.ipynb +++ b/doc/tutorial/climada_hazard_TropCyclone.ipynb @@ -1895,7 +1895,6 @@ "# construct centroids\n", "min_lat, max_lat, min_lon, max_lon = 16.99375, 21.95625, -72.48125, -61.66875\n", "cent = Centroids.from_pnt_bounds((min_lon, min_lat, max_lon, max_lat), res=0.12)\n", - "cent.check()\n", "cent.plot()\n", "\n", "# construct tropical cyclones\n", diff --git a/script/applications/eca_san_salvador/San_Salvador_Risk.ipynb b/script/applications/eca_san_salvador/San_Salvador_Risk.ipynb index b73180b38..84c9d37a3 100644 --- a/script/applications/eca_san_salvador/San_Salvador_Risk.ipynb +++ b/script/applications/eca_san_salvador/San_Salvador_Risk.ipynb @@ -754,8 +754,8 @@ ], "source": [ "point_idx = 1064\n", - "point_lat = exp_acel.gdf.latitude.values[point_idx]\n", - "point_lon = exp_acel.gdf.longitude.values[point_idx]\n", + "point_lat = exp_acel.latitude[point_idx]\n", + "point_lon = exp_acel.longitude[point_idx]\n", "point_eai = imp_acel.eai_exp[point_idx]\n", "print(\n", " \"Annual expected impact in {:.4f}° N {:.4f}° W is {:.0f} USD.\".format(\n", diff --git a/script/applications/eca_san_salvador/functions_ss.py b/script/applications/eca_san_salvador/functions_ss.py index 3d0478558..536fbc55d 100755 --- a/script/applications/eca_san_salvador/functions_ss.py +++ b/script/applications/eca_san_salvador/functions_ss.py @@ -62,7 +62,6 @@ def plot_salvador_ma(): def load_entity(): ent_file = "FL_entity_Acelhuate_houses.xlsx" ent = Entity.from_excel(ent_file) - ent.exposures.set_geometry_points() ent.check() return ent @@ -277,7 +276,6 @@ def load_accounting(): def generate_plots_risk(): fig_ma = plot_salvador_ma() ent = load_entity() - ent.exposures.set_geometry_points() ent.exposures.to_crs(epsg=3857, inplace=True) fig_point = plot_exposure_ss(ent.exposures, 1064) fig_houses = plot_exposure_ss(ent.exposures) diff --git a/script/jenkins/petals_regression_test/Jenkinsfile b/script/jenkins/petals_regression_test/Jenkinsfile index 433771e3a..a78d32d36 100644 --- a/script/jenkins/petals_regression_test/Jenkinsfile +++ b/script/jenkins/petals_regression_test/Jenkinsfile @@ -4,7 +4,7 @@ pipeline { stages { stage('integ_test') { steps { - sh 'bash script/jenkins/petals_regression_test/run_integ_test.sh' + sh "bash script/jenkins/petals_regression_test/run_integ_test.sh ${env.GIT_BRANCH}" } } } diff --git a/script/jenkins/petals_regression_test/run_integ_test.sh b/script/jenkins/petals_regression_test/run_integ_test.sh index 1c9399879..c532a1709 100644 --- a/script/jenkins/petals_regression_test/run_integ_test.sh +++ b/script/jenkins/petals_regression_test/run_integ_test.sh @@ -5,7 +5,8 @@ mamba env update -n climada_env -f ~/jobs/petals_install_env/workspace/requireme source activate climada_env REGTESTENV=~/jobs/petals_compatibility/petals_env -BRANCH=`git branch -r | grep PR | cut -f 2 -d /` +BRANCH=$1 +echo ::: $REGTESTENV/$BRANCH PETALS_DIR=`test -e $REGTESTENV/$BRANCH && cat $REGTESTENV/$BRANCH || echo ~/jobs/petals_branches/branches/develop/workspace` python -m venv --system-site-packages tvenv