Skip to content

Commit 15d3c93

Browse files
Antsalaciawehrad
andauthored
Add raster compression option (#106)
* adding compression option * Adding modification and verification in method * Add argument in method * rename and add docstring * Add `raster_compression` type in docstring * fix None bug and rename * change argument * CodeQL issue * pre-commit issues * Address CodeQL alert * test with pre-commit * change in metadata * add pre-commit * pre-commit change * modify `get_raster_compression` docstring * modify line length on docstring * Problem with self and variables env * Adding conftest and test * rename filepath for authfile * fix authfile fixture * try and fix authfile writing in Github action * modify compression methods * fix authfile * fix authfile * add assert for authentification * remove assert * change None check and add test for comp * Add comments * comply with github_adv_sec * fix typo * forgot pre-commit * add Antsalacia in author list --------- Co-authored-by: Adrien Wehrlé <adrien.wehrle@hotmail.fr>
1 parent f8e5528 commit 15d3c93

File tree

3 files changed

+515
-383
lines changed

3 files changed

+515
-383
lines changed

earthspy/earthspy.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""
22
3-
@author: Adrien Wehrlé, EO-IO, University of Zurich, Switzerland
3+
@authors: Adrien Wehrlé (EO-IO), Antsalacia
44
55
"""
66

@@ -84,6 +84,7 @@ def set_query_parameters(
8484
download_mode: str = "SM",
8585
remove_splitboxes: bool = True,
8686
verbose: bool = True,
87+
raster_compression: str = None,
8788
) -> None:
8889
"""Define a set of parameters used for the API request.
8990
@@ -144,6 +145,11 @@ def set_query_parameters(
144145
:param verbose: Whether to print processing status or not, defaults
145146
to True.
146147
:type verbose: bool, optional
148+
149+
150+
:param raster_compression: Raster compression to apply following methods
151+
available in rasterio, defaults to None.
152+
:type raster_compression: Union[None, str], optional
147153
"""
148154

149155
# set processing attributes
@@ -171,6 +177,9 @@ def set_query_parameters(
171177
# set and correct resolution
172178
self.set_correct_resolution()
173179

180+
# set compression method
181+
self.get_raster_compression(raster_compression)
182+
174183
# set post-processing attributes
175184
self.get_evaluation_script(evaluation_script)
176185
self.get_store_folder(store_folder)
@@ -187,6 +196,28 @@ def set_query_parameters(
187196

188197
return None
189198

199+
def get_raster_compression(self, raster_compression: Union[None, str]) -> str:
200+
"""Get raster compression based on rasterio's available methods
201+
202+
:return: Raster compression method
203+
:rtype: Union[None, str]
204+
"""
205+
206+
# list rasterio compression algorithm and exclude dunders
207+
rasterio_compression_algorithms = [
208+
m for m in dir(rasterio.enums.Compression) if not m.startswith("__")
209+
]
210+
211+
# use rasterio compression method as is
212+
if raster_compression is None:
213+
self.raster_compression = None
214+
elif raster_compression.lower() in rasterio_compression_algorithms:
215+
self.raster_compression = raster_compression
216+
else:
217+
raise KeyError("Compression algorithm not found")
218+
219+
return self.raster_compression
220+
190221
def get_data_collection(self) -> shb.DataCollection:
191222
"""Get Sentinel Hub DataCollection object from data collection name.
192223
@@ -294,7 +325,6 @@ def get_raw_data_collection_resolution(self) -> int:
294325
295326
:return: Data collection resolution.
296327
:rtype: int
297-
298328
"""
299329

300330
# set default satellite resolution
@@ -1072,11 +1102,19 @@ def merge_rasters(self) -> None:
10721102
"transform": output_transform,
10731103
}
10741104
)
1105+
1106+
# update dictionary if compression set
1107+
if self.raster_compression is not None:
1108+
output_meta.update({"compress": self.raster_compression})
1109+
1110+
# extract scene id
10751111
id_dict = {k: self.metadata[date][0][k] for k in ["id"]}
1112+
10761113
# write mosaic
10771114
with rasterio.open(date_output_filename, "w", **output_meta) as dst:
10781115
dst.write(mosaic)
10791116
dst.update_tags(**id_dict)
1117+
10801118
# save file name of merged raster
10811119
self.output_filenames_renamed.append(date_output_filename)
10821120

tests/conftest.py

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
#!/usr/bin/env python3
2+
"""
3+
4+
@authors: Adrien Wehrlé (EO-IO), Antsalacia
5+
6+
"""
7+
8+
import os
9+
10+
import pytest
11+
12+
import earthspy.earthspy as es
13+
14+
15+
def pytest_addoption(parser):
16+
"""Add option to pass local path to authentication file"""
17+
parser.addoption(
18+
"--authfile",
19+
action="store",
20+
default="./auth.txt",
21+
help="Full path to Sentinel Hub credential file containing ID and password",
22+
)
23+
24+
25+
# if running in Github action
26+
if os.getenv("CI") is not None:
27+
28+
@pytest.fixture(scope="session")
29+
def SH_CLIENT_ID() -> None:
30+
"""Create local client id from environment variable"""
31+
# check if variable in environment variables
32+
SH_CLIENT_ID = os.environ["SH_CLIENT_ID"]
33+
return SH_CLIENT_ID
34+
35+
@pytest.fixture(scope="session")
36+
def SH_CLIENT_SECRET() -> None:
37+
"""Create local client secret from environment variable"""
38+
# check if variable in environment variables
39+
SH_CLIENT_SECRET = os.environ["SH_CLIENT_SECRET"]
40+
return SH_CLIENT_SECRET
41+
42+
# path to credential file to be created
43+
@pytest.fixture(scope="session")
44+
def authfile(SH_CLIENT_ID, SH_CLIENT_SECRET):
45+
"""Set credential file name and create credential file
46+
for testing"""
47+
authfile = "auth.txt"
48+
with open(authfile, "w") as out:
49+
out.write(f"{SH_CLIENT_ID}\n{SH_CLIENT_SECRET}")
50+
return authfile
51+
52+
53+
# if running locally
54+
else:
55+
56+
@pytest.fixture(scope="session")
57+
def authfile(pytestconfig):
58+
"""Get option from command line call"""
59+
return pytestconfig.getoption("authfile")
60+
61+
@pytest.fixture(scope="session")
62+
def credentials(authfile):
63+
"""Read credentials stored in text file"""
64+
65+
with open(authfile) as file:
66+
credentials = file.read().splitlines()
67+
return credentials
68+
69+
@pytest.fixture(scope="session")
70+
def SH_CLIENT_ID(credentials) -> None:
71+
"""Extract client id from line"""
72+
SH_CLIENT_ID = credentials[0]
73+
return SH_CLIENT_ID
74+
75+
@pytest.fixture(scope="session")
76+
def SH_CLIENT_SECRET(credentials) -> None:
77+
"""Extract client secret from line"""
78+
SH_CLIENT_SECRET = credentials[1]
79+
return SH_CLIENT_SECRET
80+
81+
82+
@pytest.fixture(scope="session")
83+
def test_evalscript():
84+
"""Set a test evalscript for Sentinel-2"""
85+
test_evalscript = """
86+
//VERSION=3
87+
function setup(){
88+
return{
89+
input: ["B02", "B03", "B04", "dataMask"],
90+
output: {bands: 4}
91+
}
92+
}
93+
function evaluatePixel(sample){
94+
// Set gain for visualisation
95+
let gain = 2.5;
96+
// Return RGB
97+
return [sample.B04 * gain, sample.B03 * gain, sample.B02 * gain,
98+
sample.dataMask];
99+
}
100+
"""
101+
return test_evalscript
102+
103+
104+
@pytest.fixture(scope="session")
105+
def test_url():
106+
"""Set a test evalscript pointing to Sentinel-2 True Color"""
107+
test_url = (
108+
"https://custom-scripts.sentinel-hub.com/custom-scripts/"
109+
+ "sentinel-2/true_color/script.js"
110+
)
111+
return test_url
112+
113+
114+
@pytest.fixture(scope="session")
115+
def test_collection():
116+
"""Set a test data collection"""
117+
test_collection = "SENTINEL2_L2A"
118+
return test_collection
119+
120+
121+
@pytest.fixture(scope="session")
122+
def test_bounding_box():
123+
"""Set a test footprint area (bounding box)"""
124+
test_bounding_box = [-51.13, 69.204, -51.06, 69.225]
125+
return test_bounding_box
126+
127+
128+
@pytest.fixture(scope="session")
129+
def test_area_name():
130+
"""Set a test area available as geojson file"""
131+
test_area_name = "Ilulissat"
132+
return test_area_name
133+
134+
135+
@pytest.fixture(scope="session")
136+
def t1(authfile, test_evalscript, test_collection, test_bounding_box):
137+
"""Set a test query with default parameters"""
138+
t1 = es.EarthSpy(authfile)
139+
t1.set_query_parameters(
140+
bounding_box=test_bounding_box,
141+
time_interval=["2019-08-23"],
142+
evaluation_script=test_evalscript,
143+
data_collection=test_collection,
144+
download_mode="SM",
145+
)
146+
return t1
147+
148+
149+
@pytest.fixture(scope="session")
150+
def t2(authfile, test_evalscript, test_collection, test_area_name):
151+
"""Set a test query with area name"""
152+
t2 = es.EarthSpy(authfile)
153+
t2.set_query_parameters(
154+
bounding_box=test_area_name,
155+
time_interval=["2019-08-23"],
156+
evaluation_script=test_evalscript,
157+
data_collection=test_collection,
158+
download_mode="SM",
159+
)
160+
return t2
161+
162+
163+
@pytest.fixture(scope="session")
164+
def t3(authfile, test_evalscript, test_collection, test_bounding_box):
165+
"""Set a test query with direct download mode"""
166+
t3 = es.EarthSpy(authfile)
167+
t3.set_query_parameters(
168+
bounding_box=test_bounding_box,
169+
time_interval=["2019-08-23"],
170+
evaluation_script=test_evalscript,
171+
data_collection=test_collection,
172+
download_mode="D",
173+
)
174+
return t3
175+
176+
177+
@pytest.fixture(scope="session")
178+
def t4(authfile, test_evalscript, test_collection, test_bounding_box):
179+
"""Set a test query with LZW raster compression"""
180+
t4 = es.EarthSpy(authfile)
181+
t4.set_query_parameters(
182+
bounding_box=test_bounding_box,
183+
time_interval=["2019-08-23"],
184+
evaluation_script=test_evalscript,
185+
data_collection=test_collection,
186+
download_mode="SM",
187+
raster_compression="LZW",
188+
)
189+
return t4

0 commit comments

Comments
 (0)