diff --git a/city_metrix/layers/layer.py b/city_metrix/layers/layer.py index 635ec25..079107e 100644 --- a/city_metrix/layers/layer.py +++ b/city_metrix/layers/layer.py @@ -135,6 +135,9 @@ def mean(self): def count(self): return self._zonal_stats("count") + + def sum(self): + return self._zonal_stats("sum") def _zonal_stats(self, stats_func): if box(*self.zones.total_bounds).area <= MAX_TILE_SIZE_DEGREES**2: @@ -315,6 +318,8 @@ def _aggregate_stats(df, stats_func): elif stats_func == "mean": # mean must weight by number of pixels used for each tile return (df["mean"] * df["count"]).sum() / df["count"].sum() + elif stats_func == "sum": + return df["sum"].sum() def get_stats_funcs(stats_func): diff --git a/city_metrix/layers/open_street_map.py b/city_metrix/layers/open_street_map.py index a4e5489..1b50c55 100644 --- a/city_metrix/layers/open_street_map.py +++ b/city_metrix/layers/open_street_map.py @@ -35,9 +35,16 @@ class OpenStreetMapClass(Enum): class OpenStreetMap(Layer): - def __init__(self, osm_class=None, **kwargs): + """ + Attributes: + osm_class: Enum value from OpenStreetMapClass + buffer_distance: meters distance for buffer around osm features + """ + + def __init__(self, osm_class=None, buffer_distance=None, **kwargs): super().__init__(**kwargs) self.osm_class = osm_class + self.buffer_distance = buffer_distance # meters def get_data(self, bbox): north, south, east, west = bbox[3], bbox[1], bbox[0], bbox[2] @@ -74,4 +81,7 @@ def get_data(self, bbox): crs = get_utm_zone_epsg(bbox) osm_feature = osm_feature.to_crs(crs) + if self.buffer_distance: + osm_feature['geometry'] = osm_feature.geometry.buffer(self.buffer_distance) + return osm_feature diff --git a/city_metrix/metrics/__init__.py b/city_metrix/metrics/__init__.py index d95cfa5..4bc404a 100644 --- a/city_metrix/metrics/__init__.py +++ b/city_metrix/metrics/__init__.py @@ -1,7 +1,8 @@ from .built_land_without_tree_cover import built_land_without_tree_cover from .built_land_with_low_surface_reflectivity import built_land_with_low_surface_reflectivity from .built_land_with_high_land_surface_temperature import built_land_with_high_land_surface_temperature +from .era_5_met_preprocessing import era_5_met_preprocessing from .mean_tree_cover import mean_tree_cover -from .urban_open_space import urban_open_space from .natural_areas import natural_areas -from .era_5_met_preprocessing import era_5_met_preprocessing +from .pop_open_space import pop_open_space +from .urban_open_space import urban_open_space diff --git a/city_metrix/metrics/pop_open_space.py b/city_metrix/metrics/pop_open_space.py new file mode 100644 index 0000000..1676c02 --- /dev/null +++ b/city_metrix/metrics/pop_open_space.py @@ -0,0 +1,13 @@ +from geopandas import GeoDataFrame, GeoSeries +from city_metrix.layers import OpenStreetMap, OpenStreetMapClass, WorldPop + + +def pop_open_space(zones: GeoDataFrame, buffer_distance=400) -> GeoSeries: +# (Later add agesex_classes) + pop = WorldPop() + open_space = OpenStreetMap(osm_class=OpenStreetMapClass.OPEN_SPACE, buffer_distance=buffer_distance) + + pop_open_space_sum = pop.mask(open_space).groupby(zones).sum() + pop_sum = pop.groupby(zones).sum() + + return pop_open_space_sum / pop_sum diff --git a/tests/test_metrics.py b/tests/test_metrics.py index 8fd42cc..9f1cfd2 100644 --- a/tests/test_metrics.py +++ b/tests/test_metrics.py @@ -44,6 +44,13 @@ def test_natural_areas(): assert expected_zone_size == actual_indicator_size +def test_pop_open_space(): + indicator = pop_open_space(ZONES) + expected_zone_size = ZONES.geometry.size + actual_indicator_size = indicator.size + assert expected_zone_size == actual_indicator_size + + def test_urban_open_space(): indicator = urban_open_space(ZONES) expected_zone_size = ZONES.geometry.size