Skip to content

Commit 5a5c128

Browse files
Merge branch 'develop' into feature/basins-bounds
2 parents cadf062 + a2d2297 commit 5a5c128

File tree

7 files changed

+190
-149
lines changed

7 files changed

+190
-149
lines changed

.github/CODEOWNERS

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# This is a comment.
2+
# Each line is a file pattern followed by one or more owners.
3+
4+
# These owners will be the default owners for everything in
5+
# the repo. Unless a later match takes precedence, they will
6+
# be requested for review when someone opens a pull request.
7+
* @emanuel-schmid @chahank @peanutfun

.github/workflows/pull-request.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
ref: ${{ github.event.pull_request.head.sha }}
1717
-
1818
name: Checkout target commit
19-
run: git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin ${{ github.event.pull_request.base.ref }}
19+
run: git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=50 origin ${{ github.event.pull_request.base.ref }}
2020
-
2121
name: Set up Python 3.11
2222
uses: actions/setup-python@v5

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Code freeze date: YYYY-MM-DD
1616
### Changed
1717
- `Hazard.local_exceedance_intensity`, `Hazard.local_return_period` and `Impact.local_exceedance_impact`, `Impact.local_return_period`, using the `climada.util.interpolation` module: New default (no binning), binning on decimals, and faster implementation [#1012](https://github.com/CLIMADA-project/climada_python/pull/1012)
1818
### Fixed
19+
- NaN plotting issues in `geo_im_from_array`[#1038](https://github.com/CLIMADA-project/climada_python/pull/1038)
1920

2021
### Deprecated
2122

@@ -190,6 +191,7 @@ CLIMADA tutorials. [#872](https://github.com/CLIMADA-project/climada_python/pull
190191
- `Impact.from_hdf5` now calls `str` on `event_name` data that is not strings, and issue a warning then [#894](https://github.com/CLIMADA-project/climada_python/pull/894)
191192
- `Impact.write_hdf5` now throws an error if `event_name` is does not contain strings exclusively [#894](https://github.com/CLIMADA-project/climada_python/pull/894)
192193
- Split `climada.hazard.trop_cyclone` module into smaller submodules without affecting module usage [#911](https://github.com/CLIMADA-project/climada_python/pull/911)
194+
- `yearly_steps` parameter of `TropCyclone.apply_climate_scenario_knu` has been made explicit [#991](https://github.com/CLIMADA-project/climada_python/pull/991)
193195

194196
### Fixed
195197

climada/hazard/trop_cyclone/trop_cyclone.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ def apply_climate_scenario_knu(
391391
percentile: str = "50",
392392
scenario: str = "4.5",
393393
target_year: int = 2050,
394-
**kwargs,
394+
yearly_steps: int = 5,
395395
):
396396
"""
397397
From current TC hazard instance, return new hazard set with future events
@@ -437,6 +437,9 @@ def apply_climate_scenario_knu(
437437
438438
target_year : int
439439
future year to be simulated, between 2000 and 2100. Default: 2050.
440+
yearly_steps : int
441+
yearly resolution at which projections are provided. Default is 5 years.
442+
440443
Returns
441444
-------
442445
haz_cc : climada.hazard.TropCyclone
@@ -465,11 +468,11 @@ def apply_climate_scenario_knu(
465468
for basin in np.unique(tc_cc.basin):
466469
scale_year_rcp_05, scale_year_rcp_45 = [
467470
get_knutson_scaling_factor(
468-
percentile=percentile,
469471
variable=variable,
472+
percentile=percentile,
470473
basin=basin,
471474
baseline=(np.min(years), np.max(years)),
472-
**kwargs,
475+
yearly_steps=yearly_steps,
473476
).loc[target_year, scenario]
474477
for variable in ["cat05", "cat45"]
475478
]

climada/util/earth_engine.py

Lines changed: 153 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -26,148 +26,159 @@
2626
# That's why `earthengine-api` is not in the CLIMADA requirements.
2727
# See tutorial: climada_util_earth_engine.ipynb
2828
# pylint: disable=import-error
29-
import ee
30-
3129
LOGGER = logging.getLogger(__name__)
32-
ee.Initialize()
33-
34-
35-
def obtain_image_landsat_composite(landsat_collection, time_range, area):
36-
"""Selection of Landsat cloud-free composites in the Earth Engine library
37-
See also: https://developers.google.com/earth-engine/landsat
38-
39-
Parameters
40-
----------
41-
collection :
42-
name of the collection
43-
time_range : ['YYYY-MT-DY','YYYY-MT-DY']
44-
must be inside the available data
45-
area : ee.geometry.Geometry
46-
area of interest
47-
48-
Returns
49-
-------
50-
image_composite : ee.image.Image
51-
"""
52-
collection = ee.ImageCollection(landsat_collection)
53-
54-
# Filter by time range and location
55-
collection_time = collection.filterDate(time_range[0], time_range[1])
56-
image_area = collection_time.filterBounds(area)
57-
image_composite = ee.Algorithms.Landsat.simpleComposite(image_area, 75, 3)
58-
return image_composite
59-
60-
61-
def obtain_image_median(collection, time_range, area):
62-
"""Selection of median from a collection of images in the Earth Engine library
63-
See also: https://developers.google.com/earth-engine/reducers_image_collection
64-
65-
Parameters
66-
----------
67-
collection :
68-
name of the collection
69-
time_range : ['YYYY-MT-DY','YYYY-MT-DY']
70-
must be inside the available data
71-
area : ee.geometry.Geometry
72-
area of interest
73-
74-
Returns
75-
-------
76-
image_median : ee.image.Image
77-
"""
78-
collection = ee.ImageCollection(collection)
79-
80-
# Filter by time range and location
81-
collection_time = collection.filterDate(time_range[0], time_range[1])
82-
image_area = collection_time.filterBounds(area)
83-
image_median = image_area.median()
84-
return image_median
85-
86-
87-
def obtain_image_sentinel(sentinel_collection, time_range, area):
88-
"""Selection of median, cloud-free image from a collection of images in the Sentinel 2 dataset
89-
See also: https://developers.google.com/earth-engine/datasets/catalog/COPERNICUS_S2
90-
91-
Parameters
92-
----------
93-
collection :
94-
name of the collection
95-
time_range : ['YYYY-MT-DY','YYYY-MT-DY']
96-
must be inside the available data
97-
area : ee.geometry.Geometry
98-
area of interest
99-
100-
Returns
101-
-------
102-
sentinel_median : ee.image.Image
103-
"""
104-
105-
# First, method to remove cloud from the image
106-
def maskclouds(image):
107-
band_qa = image.select("QA60")
108-
cloud_mask = ee.Number(2).pow(10).int()
109-
cirrus_mask = ee.Number(2).pow(11).int()
110-
mask = band_qa.bitwiseAnd(cloud_mask).eq(0) and (
111-
band_qa.bitwiseAnd(cirrus_mask).eq(0)
112-
)
113-
return image.updateMask(mask).divide(10000)
114-
115-
sentinel_filtered = (
116-
ee.ImageCollection(sentinel_collection)
117-
.filterBounds(area)
118-
.filterDate(time_range[0], time_range[1])
119-
.filter(ee.Filter.lt("CLOUDY_PIXEL_PERCENTAGE", 20))
120-
.map(maskclouds)
30+
31+
try:
32+
import ee
33+
34+
LOGGER.info("Google Earth Engine API successfully imported.")
35+
EE_AVAILABLE = True
36+
except ImportError:
37+
LOGGER.error(
38+
"Google Earth Engine API not found. Please install it using 'pip install earthengine-api'."
39+
)
40+
EE_AVAILABLE = False
41+
42+
if not EE_AVAILABLE:
43+
LOGGER.error(
44+
"Google Earth Engine API not found. Skipping the init of `earth_engine.py`."
12145
)
46+
else:
47+
ee.Initialize()
48+
49+
def obtain_image_landsat_composite(landsat_collection, time_range, area):
50+
"""Selection of Landsat cloud-free composites in the Earth Engine library
51+
See also: https://developers.google.com/earth-engine/landsat
52+
53+
Parameters
54+
----------
55+
collection :
56+
name of the collection
57+
time_range : ['YYYY-MT-DY','YYYY-MT-DY']
58+
must be inside the available data
59+
area : ee.geometry.Geometry
60+
area of interest
61+
62+
Returns
63+
-------
64+
image_composite : ee.image.Image
65+
"""
66+
collection = ee.ImageCollection(landsat_collection)
67+
68+
# Filter by time range and location
69+
collection_time = collection.filterDate(time_range[0], time_range[1])
70+
image_area = collection_time.filterBounds(area)
71+
image_composite = ee.Algorithms.Landsat.simpleComposite(image_area, 75, 3)
72+
return image_composite
73+
74+
def obtain_image_median(collection, time_range, area):
75+
"""Selection of median from a collection of images in the Earth Engine library
76+
See also: https://developers.google.com/earth-engine/reducers_image_collection
77+
78+
Parameters
79+
----------
80+
collection :
81+
name of the collection
82+
time_range : ['YYYY-MT-DY','YYYY-MT-DY']
83+
must be inside the available data
84+
area : ee.geometry.Geometry
85+
area of interest
86+
87+
Returns
88+
-------
89+
image_median : ee.image.Image
90+
"""
91+
collection = ee.ImageCollection(collection)
92+
93+
# Filter by time range and location
94+
collection_time = collection.filterDate(time_range[0], time_range[1])
95+
image_area = collection_time.filterBounds(area)
96+
image_median = image_area.median()
97+
return image_median
98+
99+
def obtain_image_sentinel(sentinel_collection, time_range, area):
100+
"""Selection of median, cloud-free image from a collection of images in the Sentinel 2
101+
dataset.
102+
See also: https://developers.google.com/earth-engine/datasets/catalog/COPERNICUS_S2
103+
104+
Parameters
105+
----------
106+
collection :
107+
name of the collection
108+
time_range : ['YYYY-MT-DY','YYYY-MT-DY']
109+
must be inside the available data
110+
area : ee.geometry.Geometry
111+
area of interest
112+
113+
Returns
114+
-------
115+
sentinel_median : ee.image.Image
116+
"""
117+
118+
# First, method to remove cloud from the image
119+
def maskclouds(image):
120+
band_qa = image.select("QA60")
121+
cloud_mask = ee.Number(2).pow(10).int()
122+
cirrus_mask = ee.Number(2).pow(11).int()
123+
mask = band_qa.bitwiseAnd(cloud_mask).eq(0) and (
124+
band_qa.bitwiseAnd(cirrus_mask).eq(0)
125+
)
126+
return image.updateMask(mask).divide(10000)
127+
128+
sentinel_filtered = (
129+
ee.ImageCollection(sentinel_collection)
130+
.filterBounds(area)
131+
.filterDate(time_range[0], time_range[1])
132+
.filter(ee.Filter.lt("CLOUDY_PIXEL_PERCENTAGE", 20))
133+
.map(maskclouds)
134+
)
135+
136+
sentinel_median = sentinel_filtered.median()
137+
return sentinel_median
138+
139+
def get_region(geom):
140+
"""Get the region of a given geometry, needed for exporting tasks.
141+
142+
Parameters
143+
----------
144+
geom : ee.Geometry, ee.Feature, ee.Image
145+
region of interest
146+
147+
Returns
148+
-------
149+
region : list
150+
"""
151+
if isinstance(geom, ee.Geometry):
152+
return geom.getInfo()["coordinates"]
153+
if isinstance(geom, (ee.Feature, ee.Image)):
154+
return geom.geometry().getInfo()["coordinates"]
155+
raise ValueError(
156+
"parameter must be one of `ee.Geometry`, `ee.Feature`, `ee.Image`"
157+
)
158+
159+
def get_url(name, image, scale, region):
160+
"""It will open and download automatically a zip folder containing Geotiff data of 'image'.
161+
If additional parameters are needed, see also:
162+
https://github.com/google/earthengine-api/blob/master/python/ee/image.py
163+
164+
Parameters
165+
----------
166+
name : str
167+
name of the created folder
168+
image : ee.image.Image
169+
image to export
170+
scale : int
171+
resolution of export in meters (e.g: 30 for Landsat)
172+
region : list
173+
region of interest
174+
175+
Returns
176+
-------
177+
path : str
178+
"""
179+
path = image.getDownloadURL(
180+
{"name": (name), "scale": scale, "region": (region)}
181+
)
122182

123-
sentinel_median = sentinel_filtered.median()
124-
return sentinel_median
125-
126-
127-
def get_region(geom):
128-
"""Get the region of a given geometry, needed for exporting tasks.
129-
130-
Parameters
131-
----------
132-
geom : ee.Geometry, ee.Feature, ee.Image
133-
region of interest
134-
135-
Returns
136-
-------
137-
region : list
138-
"""
139-
if isinstance(geom, ee.Geometry):
140-
region = geom.getInfo()["coordinates"]
141-
elif isinstance(geom, ee.Feature, ee.Image):
142-
region = geom.geometry().getInfo()["coordinates"]
143-
elif isinstance(geom, list):
144-
condition = all([isinstance(item) == list for item in geom])
145-
if condition:
146-
region = geom
147-
return region
148-
149-
150-
def get_url(name, image, scale, region):
151-
"""It will open and download automatically a zip folder containing Geotiff data of 'image'.
152-
If additional parameters are needed, see also:
153-
https://github.com/google/earthengine-api/blob/master/python/ee/image.py
154-
155-
Parameters
156-
----------
157-
name : str
158-
name of the created folder
159-
image : ee.image.Image
160-
image to export
161-
scale : int
162-
resolution of export in meters (e.g: 30 for Landsat)
163-
region : list
164-
region of interest
165-
166-
Returns
167-
-------
168-
path : str
169-
"""
170-
path = image.getDownloadURL({"name": (name), "scale": scale, "region": (region)})
171-
172-
webbrowser.open_new_tab(path)
173-
return path
183+
webbrowser.open_new_tab(path)
184+
return path

0 commit comments

Comments
 (0)