Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CIF-260 layer riparian areas #112

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions city_metrix/layers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@
from .cams import Cams
from .vegetation_water_map import VegetationWaterMap
from .height_above_nearest_drainage import HeightAboveNearestDrainage
from .riparian_areas import RiparianAreas
6 changes: 4 additions & 2 deletions city_metrix/layers/height_above_nearest_drainage.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ class HeightAboveNearestDrainage(Layer):
default is 30, other options - 90
river_head: number of river head threshold cells
default is 1000, other options - 100, 5000
thresh: flow accumuation threshold, default is 1
"""

def __init__(self, spatial_resolution=30, river_head=1000, **kwargs):
def __init__(self, spatial_resolution=30, river_head=1000, thresh=1, **kwargs):
super().__init__(**kwargs)
self.spatial_resolution = spatial_resolution
self.river_head = river_head
self.thresh = thresh

def get_data(self, bbox):
if self.spatial_resolution == 30 and self.river_head == 100:
Expand All @@ -37,7 +39,7 @@ def get_data(self, bbox):
swbd = ee.Image('MODIS/MOD44W/MOD44W_005_2000_02_24').select('water_mask')
swbdMask = swbd.unmask().Not().focal_median(1)

thresh = 1
thresh = self.thresh
HANDthresh = hand.lte(thresh).focal_max(1).focal_mode(2, 'circle', 'pixels', 5).mask(swbdMask)
HANDthresh = HANDthresh.mask(HANDthresh)

Expand Down
61 changes: 61 additions & 0 deletions city_metrix/layers/riparian_areas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import xarray as xr
import numpy as np
import ee
from scipy.ndimage import distance_transform_edt

from .layer import Layer, get_image_collection
from .height_above_nearest_drainage import HeightAboveNearestDrainage


class RiparianAreas(Layer):
"""
Attributes:
spatial_resolution: raster resolution in meters (see https://github.com/stac-extensions/raster)
default is 30, other options - 90
river_head: number of river head threshold cells
default is 1000, other options - 100, 5000
thresh: flow accumuation threshold, default is 0
"""

def __init__(self, spatial_resolution=30, river_head=1000, thresh=0, **kwargs):
super().__init__(**kwargs)
self.spatial_resolution = spatial_resolution
self.river_head = river_head
self.thresh = thresh

def get_data(self, bbox):
# read HAND data to generate drainage paths
hand = HeightAboveNearestDrainage(spatial_resolution=self.spatial_resolution, river_head=self.river_head, thresh=self.thresh).get_data(bbox)

# Read surface water occurance
water = ee.Image('JRC/GSW1_3/GlobalSurfaceWater').select(['occurrence']).gte(50)
water_da = get_image_collection(
ee.ImageCollection(water),
bbox,
self.spatial_resolution,
"water"
).occurrence

combWater = np.maximum(hand.fillna(0), water_da.fillna(0)) > 0
combWater = combWater.fillna(False)

# Buffer waterways by riparian zone definitions
distance_arr = xr.apply_ufunc(
distance_transform_edt,
~combWater, # True where not-water
input_core_dims=[('y', 'x')],
output_core_dims=[('y', 'x')],
dask='parallelized',
kwargs={'sampling': self.spatial_resolution}
)

halfpixel = self.spatial_resolution * 0.5
# https://doi.org/10.1016/j.jenvman.2019.109391
distance_200 = distance_arr.where(distance_arr <= 200)
nutrientBuffer = (distance_200 <= (3.0 - halfpixel))
floraBuffer = (distance_200 <= (24.0 - halfpixel))
birdBuffer = (distance_200 <= (144.0 - halfpixel))

riparianMask = birdBuffer

return riparianMask
2 changes: 2 additions & 0 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,7 @@ dependencies:
- scikit-image=0.24.0
- exactextract=0.2.0
- cfgrib=0.9.15.0
- scipy=1.13.1
- numpy=1.23.0
- pip:
- overturemaps==0.6.0
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"timezonefinder",
"scikit-image>=0.24.0",
"exactextract>=0.2.0",
"cfgrib"
"cfgrib",
"scipy",
"numpy"
],
)
5 changes: 5 additions & 0 deletions tests/test_layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
OpenStreetMap,
OpenStreetMapClass,
OvertureBuildings,
RiparianAreas,
Sentinel2Level2,
SmartSurfaceLULC,
TreeCanopyHeight,
Expand Down Expand Up @@ -133,6 +134,10 @@ def test_overture_buildings():
data = OvertureBuildings().get_data(BBOX)
assert np.size(data) > 0

def test_riparian_areas():
data = RiparianAreas().get_data(BBOX)
assert np.size(data) > 0

@pytest.mark.skip(reason="layer is deprecated")
def test_sentinel_2_level2():
sentinel_2_bands = ["green"]
Expand Down
Loading