openforis-whisp 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openforis_whisp/__init__.py +68 -0
- openforis_whisp/data_conversion.py +348 -0
- openforis_whisp/datasets.py +695 -0
- openforis_whisp/logger.py +39 -0
- openforis_whisp/parameters/__init__.py +15 -0
- openforis_whisp/parameters/config_runtime.py +47 -0
- openforis_whisp/parameters/lookup_context_and_metadata.csv +13 -0
- openforis_whisp/parameters/lookup_gee_datasets.csv +155 -0
- openforis_whisp/pd_schemas.py +77 -0
- openforis_whisp/reformat.py +346 -0
- openforis_whisp/risk.py +329 -0
- openforis_whisp/stats.py +752 -0
- openforis_whisp/utils.py +154 -0
- openforis_whisp-0.0.1.dist-info/LICENSE +21 -0
- openforis_whisp-0.0.1.dist-info/METADATA +296 -0
- openforis_whisp-0.0.1.dist-info/RECORD +17 -0
- openforis_whisp-0.0.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import ee
|
|
2
|
+
from google.oauth2 import service_account
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def initialize_ee(credentials_path=None):
|
|
6
|
+
"""Initializes Google Earth Engine using the provided path or defaults to normal if no path is given."""
|
|
7
|
+
try:
|
|
8
|
+
if not ee.data._initialized:
|
|
9
|
+
print(credentials_path)
|
|
10
|
+
if credentials_path:
|
|
11
|
+
credentials = service_account.Credentials.from_service_account_file(
|
|
12
|
+
credentials_path,
|
|
13
|
+
scopes=["https://www.googleapis.com/auth/earthengine"],
|
|
14
|
+
)
|
|
15
|
+
ee.Initialize(credentials)
|
|
16
|
+
print("EE initialized with credentials from:", credentials_path)
|
|
17
|
+
else:
|
|
18
|
+
ee.Initialize()
|
|
19
|
+
print("EE initialized with default credentials.")
|
|
20
|
+
except Exception as e:
|
|
21
|
+
print("Error initializing EE:", e)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Default to normal initialize if nobody calls whisp.initialize_ee.
|
|
25
|
+
try:
|
|
26
|
+
if not ee.data._initialized:
|
|
27
|
+
ee.Initialize()
|
|
28
|
+
print("EE auto-initialized with default credentials.")
|
|
29
|
+
except Exception as e:
|
|
30
|
+
print("Error in default EE initialization:", e)
|
|
31
|
+
|
|
32
|
+
from openforis_whisp.stats import (
|
|
33
|
+
whisp_stats_ee_to_ee,
|
|
34
|
+
whisp_stats_ee_to_df,
|
|
35
|
+
whisp_stats_geojson_to_df,
|
|
36
|
+
whisp_stats_geojson_to_ee,
|
|
37
|
+
whisp_stats_geojson_to_geojson,
|
|
38
|
+
whisp_stats_ee_to_drive,
|
|
39
|
+
whisp_stats_geojson_to_drive,
|
|
40
|
+
whisp_formatted_stats_ee_to_df,
|
|
41
|
+
whisp_formatted_stats_ee_to_geojson,
|
|
42
|
+
whisp_formatted_stats_geojson_to_df,
|
|
43
|
+
whisp_formatted_stats_geojson_to_geojson,
|
|
44
|
+
convert_iso3_to_iso2,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# temporary parameters to be removed once isio3 to iso2 conversion server side is implemented
|
|
48
|
+
from openforis_whisp.parameters.config_runtime import iso3_country_column, iso2_country_column
|
|
49
|
+
|
|
50
|
+
from openforis_whisp.reformat import (
|
|
51
|
+
validate_dataframe_using_lookups,
|
|
52
|
+
validate_dataframe,
|
|
53
|
+
create_schema_from_dataframe,
|
|
54
|
+
load_schema_if_any_file_changed,
|
|
55
|
+
# log_missing_columns,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
from openforis_whisp.data_conversion import (
|
|
59
|
+
convert_ee_to_df,
|
|
60
|
+
convert_geojson_to_ee,
|
|
61
|
+
convert_df_to_geojson,
|
|
62
|
+
convert_csv_to_geojson,
|
|
63
|
+
convert_ee_to_geojson,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
from openforis_whisp.risk import whisp_risk
|
|
67
|
+
|
|
68
|
+
from openforis_whisp.utils import get_example_data_path
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import geojson
|
|
3
|
+
from shapely.geometry import shape
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
# Existing imports
|
|
7
|
+
from typing import List, Any
|
|
8
|
+
from geojson import Feature, FeatureCollection, Polygon, Point
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import geopandas as gpd
|
|
12
|
+
import ee
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def convert_ee_to_geojson(ee_object, filename=None, indent=2, **kwargs):
|
|
16
|
+
"""Converts Earth Engine object to geojson.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
ee_object (object): An Earth Engine object.
|
|
20
|
+
filename (str, optional): The file path to save the geojson. Defaults to None.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
object: GeoJSON object.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
if (
|
|
28
|
+
isinstance(ee_object, ee.Geometry)
|
|
29
|
+
or isinstance(ee_object, ee.Feature)
|
|
30
|
+
or isinstance(ee_object, ee.FeatureCollection)
|
|
31
|
+
):
|
|
32
|
+
json_object = ee_object.getInfo()
|
|
33
|
+
if filename is not None:
|
|
34
|
+
filename = os.path.abspath(filename)
|
|
35
|
+
if not os.path.exists(os.path.dirname(filename)):
|
|
36
|
+
os.makedirs(os.path.dirname(filename))
|
|
37
|
+
with open(filename, "w") as f:
|
|
38
|
+
f.write(json.dumps(json_object, indent=indent, **kwargs) + "\n")
|
|
39
|
+
else:
|
|
40
|
+
return json_object
|
|
41
|
+
else:
|
|
42
|
+
print("Could not convert the Earth Engine object to geojson")
|
|
43
|
+
except Exception as e:
|
|
44
|
+
raise Exception(e)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def convert_geojson_to_ee(geojson_filepath: Any) -> ee.FeatureCollection:
|
|
48
|
+
"""
|
|
49
|
+
Reads a GeoJSON file from the given path and converts it to an Earth Engine FeatureCollection.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
geojson_filepath (Any): The filepath to the GeoJSON file.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
ee.FeatureCollection: Earth Engine FeatureCollection created from the GeoJSON.
|
|
56
|
+
"""
|
|
57
|
+
if isinstance(geojson_filepath, (str, Path)):
|
|
58
|
+
file_path = os.path.abspath(geojson_filepath)
|
|
59
|
+
print(f"Reading GeoJSON file from: {file_path}")
|
|
60
|
+
|
|
61
|
+
with open(file_path, "r") as f:
|
|
62
|
+
geojson_data = json.load(f)
|
|
63
|
+
else:
|
|
64
|
+
raise ValueError("Input must be a file path (str or Path)")
|
|
65
|
+
|
|
66
|
+
validation_errors = validate_geojson(geojson_filepath)
|
|
67
|
+
if validation_errors:
|
|
68
|
+
raise ValueError(f"GeoJSON validation errors: {validation_errors}")
|
|
69
|
+
|
|
70
|
+
feature_collection = ee.FeatureCollection(create_feature_collection(geojson_data))
|
|
71
|
+
|
|
72
|
+
return feature_collection
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def convert_geojson_to_shapefile(geojson_path, shapefile_output_path):
|
|
76
|
+
"""
|
|
77
|
+
Convert a GeoJSON file to a Shapefile and save it to a file.
|
|
78
|
+
|
|
79
|
+
Parameters:
|
|
80
|
+
- geojson_path: Path to the GeoJSON file.
|
|
81
|
+
- shapefile_output_path: Path where the Shapefile will be saved.
|
|
82
|
+
"""
|
|
83
|
+
with open(geojson_path, "r") as geojson_file:
|
|
84
|
+
geojson = json.load(geojson_file)
|
|
85
|
+
|
|
86
|
+
gdf = gpd.GeoDataFrame.from_features(geojson["features"])
|
|
87
|
+
|
|
88
|
+
gdf.to_file(shapefile_output_path, driver="ESRI Shapefile")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def convert_shapefile_to_geojson(shapefile_path, geojson_output_path):
|
|
92
|
+
"""
|
|
93
|
+
Convert a Shapefile to GeoJSON and save it to a file.
|
|
94
|
+
|
|
95
|
+
Parameters:
|
|
96
|
+
- shapefile_path: Path to the Shapefile.
|
|
97
|
+
- geojson_output_path: Path where the GeoJSON file will be saved.
|
|
98
|
+
"""
|
|
99
|
+
gdf = gpd.read_file(shapefile_path)
|
|
100
|
+
|
|
101
|
+
geojson_str = gdf.to_json()
|
|
102
|
+
|
|
103
|
+
with open(geojson_output_path, "w") as geojson_file:
|
|
104
|
+
geojson_file.write(geojson_str)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def convert_shapefile_to_ee(shapefile_path):
|
|
108
|
+
"""
|
|
109
|
+
Convert a zipped shapefile to an Earth Engine FeatureCollection.
|
|
110
|
+
NB Making this as existing Geemap function shp_to_ee wouldnt work.
|
|
111
|
+
Args:
|
|
112
|
+
- shapefile_path (str): Path to the shapefile (.zip) to be converted.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
- ee.FeatureCollection: Earth Engine FeatureCollection created from the shapefile.
|
|
116
|
+
"""
|
|
117
|
+
gdf = gpd.read_file(shapefile_path)
|
|
118
|
+
|
|
119
|
+
geo_json = gdf.to_json()
|
|
120
|
+
|
|
121
|
+
roi = ee.FeatureCollection(json.loads(geo_json))
|
|
122
|
+
|
|
123
|
+
return roi
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def convert_ee_to_df(
|
|
127
|
+
ee_object,
|
|
128
|
+
columns=None,
|
|
129
|
+
remove_geom=False,
|
|
130
|
+
sort_columns=False,
|
|
131
|
+
**kwargs,
|
|
132
|
+
):
|
|
133
|
+
"""Converts an ee.FeatureCollection to pandas dataframe.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
ee_object (ee.FeatureCollection): ee.FeatureCollection.
|
|
137
|
+
columns (list): List of column names. Defaults to None.
|
|
138
|
+
remove_geom (bool): Whether to remove the geometry column. Defaults to True.
|
|
139
|
+
sort_columns (bool): Whether to sort the column names. Defaults to False.
|
|
140
|
+
kwargs: Additional arguments passed to ee.data.computeFeature.
|
|
141
|
+
|
|
142
|
+
Raises:
|
|
143
|
+
TypeError: ee_object must be an ee.FeatureCollection
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
pd.DataFrame: pandas DataFrame
|
|
147
|
+
"""
|
|
148
|
+
if isinstance(ee_object, ee.Feature):
|
|
149
|
+
ee_object = ee.FeatureCollection([ee_object])
|
|
150
|
+
|
|
151
|
+
if not isinstance(ee_object, ee.FeatureCollection):
|
|
152
|
+
raise TypeError("ee_object must be an ee.FeatureCollection")
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
if remove_geom:
|
|
156
|
+
data = ee_object.map(
|
|
157
|
+
lambda f: ee.Feature(None, f.toDictionary(f.propertyNames().sort()))
|
|
158
|
+
)
|
|
159
|
+
else:
|
|
160
|
+
data = ee_object
|
|
161
|
+
|
|
162
|
+
kwargs["expression"] = data
|
|
163
|
+
kwargs["fileFormat"] = "PANDAS_DATAFRAME"
|
|
164
|
+
|
|
165
|
+
df = ee.data.computeFeatures(kwargs)
|
|
166
|
+
|
|
167
|
+
if isinstance(columns, list):
|
|
168
|
+
df = df[columns]
|
|
169
|
+
|
|
170
|
+
if remove_geom and ("geometry" in df.columns):
|
|
171
|
+
df = df.drop(columns=["geometry"], axis=1)
|
|
172
|
+
|
|
173
|
+
if sort_columns:
|
|
174
|
+
df = df.reindex(sorted(df.columns), axis=1)
|
|
175
|
+
|
|
176
|
+
return df
|
|
177
|
+
except Exception as e:
|
|
178
|
+
raise Exception(e)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def convert_ee_to_shapefile(feature_collection, shapefile_path):
|
|
182
|
+
"""
|
|
183
|
+
Export an Earth Engine FeatureCollection to a shapefile.
|
|
184
|
+
|
|
185
|
+
Parameters:
|
|
186
|
+
- feature_collection: Earth Engine FeatureCollection to be exported.
|
|
187
|
+
- shapefile_path: Path to save the shapefile.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
- Path to the saved shapefile.
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
geojson = convert_ee_to_geojson(feature_collection)
|
|
194
|
+
|
|
195
|
+
features = geojson["features"]
|
|
196
|
+
geoms = [shape(feature["geometry"]) for feature in features]
|
|
197
|
+
properties = [feature["properties"] for feature in features]
|
|
198
|
+
gdf = gpd.GeoDataFrame(properties, geometry=geoms)
|
|
199
|
+
|
|
200
|
+
gdf.to_file(shapefile_path, driver="ESRI Shapefile")
|
|
201
|
+
|
|
202
|
+
print(f"Shapefile saved to {shapefile_path}")
|
|
203
|
+
|
|
204
|
+
return shapefile_path
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def validate_geojson(input_data: Any) -> List[str]:
|
|
208
|
+
"""
|
|
209
|
+
Validates GeoJSON data and filters out certain non-critical errors.
|
|
210
|
+
|
|
211
|
+
:param input_data: GeoJSON data as a string or a file path
|
|
212
|
+
:return: List of validation errors
|
|
213
|
+
"""
|
|
214
|
+
errors = []
|
|
215
|
+
|
|
216
|
+
if isinstance(input_data, (str, Path)):
|
|
217
|
+
try:
|
|
218
|
+
with open(input_data, "r") as f:
|
|
219
|
+
geojson_data = f.read()
|
|
220
|
+
except Exception as e:
|
|
221
|
+
errors.append(f"Error reading file: {e}")
|
|
222
|
+
return errors
|
|
223
|
+
else:
|
|
224
|
+
geojson_data = input_data
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
geojson_obj = json.loads(geojson_data)
|
|
228
|
+
if "type" not in geojson_obj:
|
|
229
|
+
errors.append("Missing 'type' field in GeoJSON.")
|
|
230
|
+
except ValueError as e:
|
|
231
|
+
errors.append(f"Invalid GeoJSON: {e}")
|
|
232
|
+
|
|
233
|
+
return errors
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def extract_features(geometry: Any, features: List[Feature]) -> None:
|
|
237
|
+
"""
|
|
238
|
+
Recursively extracts features from a geometry and adds them to the feature list.
|
|
239
|
+
|
|
240
|
+
:param geometry: GeoJSON geometry
|
|
241
|
+
:param features: List of extracted features
|
|
242
|
+
"""
|
|
243
|
+
if geometry["type"] == "Polygon":
|
|
244
|
+
features.append(Feature(geometry=Polygon(geometry["coordinates"])))
|
|
245
|
+
elif geometry["type"] == "Point":
|
|
246
|
+
features.append(Feature(geometry=Point(geometry["coordinates"])))
|
|
247
|
+
elif geometry["type"] == "MultiPolygon":
|
|
248
|
+
for polygon in geometry["coordinates"]:
|
|
249
|
+
features.append(Feature(geometry=Polygon(polygon)))
|
|
250
|
+
elif geometry["type"] == "GeometryCollection":
|
|
251
|
+
for geom in geometry["geometries"]:
|
|
252
|
+
extract_features(geom, features)
|
|
253
|
+
elif geometry["type"] == "Feature":
|
|
254
|
+
extract_features(geometry["geometry"], features)
|
|
255
|
+
elif geometry["type"] == "FeatureCollection":
|
|
256
|
+
for feature in geometry["features"]:
|
|
257
|
+
extract_features(feature, features)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def create_feature_collection(geojson_obj: Any) -> FeatureCollection:
|
|
261
|
+
"""
|
|
262
|
+
Creates a FeatureCollection from a GeoJSON object.
|
|
263
|
+
|
|
264
|
+
:param geojson_obj: GeoJSON object
|
|
265
|
+
:return: GeoJSON FeatureCollection
|
|
266
|
+
"""
|
|
267
|
+
features = []
|
|
268
|
+
extract_features(geojson_obj, features)
|
|
269
|
+
return FeatureCollection(features)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def convert_csv_to_geojson(csv_filepath: str, geojson_filepath: str, geo_column: str = "geo"):
|
|
273
|
+
"""
|
|
274
|
+
Convert a CSV file with a geo column into a GeoJSON file.
|
|
275
|
+
|
|
276
|
+
Parameters
|
|
277
|
+
----------
|
|
278
|
+
csv_filepath : str
|
|
279
|
+
The filepath to the input CSV file.
|
|
280
|
+
geojson_filepath : str
|
|
281
|
+
The filepath to save the output GeoJSON file.
|
|
282
|
+
geo_column : str, optional
|
|
283
|
+
The name of the column containing GeoJSON geometries, by default "geo".
|
|
284
|
+
|
|
285
|
+
Returns
|
|
286
|
+
-------
|
|
287
|
+
None
|
|
288
|
+
"""
|
|
289
|
+
try:
|
|
290
|
+
df = pd.read_csv(csv_filepath)
|
|
291
|
+
|
|
292
|
+
df_to_geojson(df, geojson_filepath, geo_column)
|
|
293
|
+
|
|
294
|
+
except Exception as e:
|
|
295
|
+
print(f"An error occurred while converting CSV to GeoJSON: {e}")
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def convert_df_to_geojson(df: pd.DataFrame, geojson_filepath: str, geo_column: str = "geo"):
|
|
299
|
+
"""
|
|
300
|
+
Convert a DataFrame with a geo column into a GeoJSON file.
|
|
301
|
+
|
|
302
|
+
Parameters
|
|
303
|
+
----------
|
|
304
|
+
df : pd.DataFrame
|
|
305
|
+
The DataFrame containing the data.
|
|
306
|
+
geojson_filepath : str
|
|
307
|
+
The filepath to save the output GeoJSON file.
|
|
308
|
+
geo_column : str, optional
|
|
309
|
+
The name of the column containing GeoJSON geometries, by default "geo".
|
|
310
|
+
|
|
311
|
+
Returns
|
|
312
|
+
-------
|
|
313
|
+
None
|
|
314
|
+
"""
|
|
315
|
+
try:
|
|
316
|
+
if geo_column not in df.columns:
|
|
317
|
+
raise ValueError(f"Geo column '{geo_column}' not found in the DataFrame.")
|
|
318
|
+
|
|
319
|
+
features = []
|
|
320
|
+
|
|
321
|
+
for index, row in df.iterrows():
|
|
322
|
+
try:
|
|
323
|
+
geojson_str = row[geo_column]
|
|
324
|
+
|
|
325
|
+
geojson_str = geojson_str.replace("'", '"')
|
|
326
|
+
|
|
327
|
+
geometry = geojson.loads(geojson_str)
|
|
328
|
+
|
|
329
|
+
properties = row.drop(geo_column).to_dict()
|
|
330
|
+
properties = {
|
|
331
|
+
k: (v if pd.notna(v) else None) for k, v in properties.items()
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
feature = geojson.Feature(geometry=geometry, properties=properties)
|
|
335
|
+
|
|
336
|
+
features.append(feature)
|
|
337
|
+
except Exception as e:
|
|
338
|
+
continue
|
|
339
|
+
|
|
340
|
+
feature_collection = geojson.FeatureCollection(features)
|
|
341
|
+
|
|
342
|
+
with open(geojson_filepath, "w") as f:
|
|
343
|
+
geojson.dump(feature_collection, f, indent=2)
|
|
344
|
+
|
|
345
|
+
print(f"GeoJSON saved to {geojson_filepath}")
|
|
346
|
+
|
|
347
|
+
except Exception as e:
|
|
348
|
+
print(f"An error occurred while converting DataFrame to GeoJSON: {e}")
|