fimeval 0.1.56__py3-none-any.whl → 0.1.58__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.
- fimeval/BenchFIMQuery/__init__.py +5 -0
- fimeval/BenchFIMQuery/access_benchfim.py +824 -0
- fimeval/BenchFIMQuery/utilis.py +334 -0
- fimeval/BuildingFootprint/__init__.py +2 -1
- fimeval/BuildingFootprint/arcgis_API.py +195 -0
- fimeval/BuildingFootprint/evaluationwithBF.py +21 -63
- fimeval/ContingencyMap/__init__.py +2 -2
- fimeval/ContingencyMap/evaluationFIM.py +123 -62
- fimeval/ContingencyMap/printcontingency.py +3 -1
- fimeval/ContingencyMap/water_bodies.py +175 -0
- fimeval/__init__.py +10 -1
- fimeval/setup_benchFIM.py +41 -0
- fimeval/utilis.py +47 -0
- {fimeval-0.1.56.dist-info → fimeval-0.1.58.dist-info}/METADATA +47 -23
- fimeval-0.1.58.dist-info/RECORD +21 -0
- {fimeval-0.1.56.dist-info → fimeval-0.1.58.dist-info}/WHEEL +1 -1
- fimeval/BuildingFootprint/microsoftBF.py +0 -132
- fimeval/ContingencyMap/PWBs3.py +0 -42
- fimeval-0.1.56.dist-info/RECORD +0 -17
- {fimeval-0.1.56.dist-info → fimeval-0.1.58.dist-info}/licenses/LICENSE.txt +0 -0
- {fimeval-0.1.56.dist-info → fimeval-0.1.58.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Supath Dhital
|
|
3
|
+
Date Updated: January 2026
|
|
4
|
+
"""
|
|
1
5
|
import os
|
|
6
|
+
import re
|
|
2
7
|
import numpy as np
|
|
3
8
|
from pathlib import Path
|
|
4
9
|
import geopandas as gpd
|
|
@@ -10,16 +15,20 @@ import pandas as pd
|
|
|
10
15
|
from rasterio.warp import reproject, Resampling
|
|
11
16
|
from rasterio.io import MemoryFile
|
|
12
17
|
from rasterio import features
|
|
18
|
+
from shapely.geometry import shape
|
|
13
19
|
from rasterio.mask import mask
|
|
14
20
|
|
|
21
|
+
os.environ["CHECK_DISK_FREE_SPACE"] = "NO"
|
|
22
|
+
|
|
15
23
|
import warnings
|
|
16
24
|
|
|
17
25
|
warnings.filterwarnings("ignore", category=rasterio.errors.ShapeSkipWarning)
|
|
18
26
|
|
|
19
|
-
from .methods import AOI,
|
|
27
|
+
from .methods import AOI, convex_hull, smallest_extent, get_smallest_raster_path
|
|
20
28
|
from .metrics import evaluationmetrics
|
|
21
|
-
from .
|
|
22
|
-
from ..utilis import MakeFIMsUniform
|
|
29
|
+
from .water_bodies import ExtractPWB
|
|
30
|
+
from ..utilis import MakeFIMsUniform, benchmark_name, find_best_boundary
|
|
31
|
+
from ..setup_benchFIM import ensure_benchmark
|
|
23
32
|
|
|
24
33
|
|
|
25
34
|
# giving the permission to the folder
|
|
@@ -64,7 +73,7 @@ def fix_permissions(path):
|
|
|
64
73
|
|
|
65
74
|
# Function for the evalution of the model
|
|
66
75
|
def evaluateFIM(
|
|
67
|
-
benchmark_path, candidate_paths,
|
|
76
|
+
benchmark_path, candidate_paths, PWB_Dir, folder, method, output_dir, shapefile=None
|
|
68
77
|
):
|
|
69
78
|
# Lists to store evaluation metrics
|
|
70
79
|
csi_values = []
|
|
@@ -98,20 +107,18 @@ def evaluateFIM(
|
|
|
98
107
|
|
|
99
108
|
# If method is AOI, and direct shapefile directory is not provided, then it will search for the shapefile in the folder
|
|
100
109
|
if method.__name__ == "AOI":
|
|
101
|
-
#
|
|
110
|
+
# Ubest-matching boundary file, prefer .gpkg from benchFIM downloads
|
|
102
111
|
if shapefile is None:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if file.lower().endswith(ext):
|
|
106
|
-
shapefile = os.path.join(folder, file)
|
|
107
|
-
print(f"Auto-detected shapefile: {shapefile}")
|
|
108
|
-
break
|
|
109
|
-
if shapefile:
|
|
110
|
-
break
|
|
111
|
-
if shapefile is None:
|
|
112
|
+
shapefile_path = find_best_boundary(Path(folder), Path(benchmark_path))
|
|
113
|
+
if shapefile_path is None:
|
|
112
114
|
raise FileNotFoundError(
|
|
113
|
-
"No
|
|
115
|
+
f"No boundary file (.gpkg, .shp, .geojson, .kml) found in {folder}. "
|
|
116
|
+
"Either provide a shapefile path or place a boundary file in the folder."
|
|
114
117
|
)
|
|
118
|
+
shapefile = str(shapefile_path)
|
|
119
|
+
else:
|
|
120
|
+
shapefile = str(shapefile)
|
|
121
|
+
|
|
115
122
|
# Run AOI with the found or provided shapefile
|
|
116
123
|
bounding_geom = AOI(benchmark_path, shapefile, save_dir)
|
|
117
124
|
|
|
@@ -127,8 +134,23 @@ def evaluateFIM(
|
|
|
127
134
|
benchmark_nodata = src1.nodata
|
|
128
135
|
benchmark_crs = src1.crs
|
|
129
136
|
b_profile = src1.profile
|
|
137
|
+
|
|
138
|
+
#Getting the correct geometry shape and crs to extract PWB
|
|
139
|
+
boundary_shape = shape(bounding_geom[0])
|
|
140
|
+
boundary_gdf = gpd.GeoDataFrame(geometry=[boundary_shape], crs=benchmark_crs)
|
|
141
|
+
|
|
142
|
+
#Proceed the masking
|
|
130
143
|
out_image1[out_image1 == benchmark_nodata] = 0
|
|
131
144
|
out_image1 = np.where(out_image1 > 0, 2, 0).astype(np.float32)
|
|
145
|
+
|
|
146
|
+
#If PWB_Dir is provided, use the local PWB shapefile, else download from ArcGIS API
|
|
147
|
+
if PWB_Dir is not None:
|
|
148
|
+
gdf = gpd.read_file(PWB_Dir)
|
|
149
|
+
else:
|
|
150
|
+
#Get the permanent water bodies from ArcGIS REST API
|
|
151
|
+
pwb_obj = ExtractPWB(boundary = boundary_gdf, save = False)
|
|
152
|
+
gdf = pwb_obj.gdf
|
|
153
|
+
|
|
132
154
|
gdf = gdf.to_crs(benchmark_crs)
|
|
133
155
|
shapes1 = [
|
|
134
156
|
geom for geom in gdf.geometry if geom is not None and not geom.is_empty
|
|
@@ -277,8 +299,8 @@ def evaluateFIM(
|
|
|
277
299
|
out_transform1,
|
|
278
300
|
)
|
|
279
301
|
merged = out_image1 + out_image2_resized
|
|
280
|
-
merged[merged==7] = 5
|
|
281
|
-
|
|
302
|
+
merged[merged == 7] = 5
|
|
303
|
+
|
|
282
304
|
# Get Evaluation Metrics
|
|
283
305
|
(
|
|
284
306
|
unique_values,
|
|
@@ -392,19 +414,18 @@ def safe_delete_folder(folder_path):
|
|
|
392
414
|
|
|
393
415
|
def EvaluateFIM(
|
|
394
416
|
main_dir,
|
|
395
|
-
method_name,
|
|
396
|
-
output_dir,
|
|
417
|
+
method_name=None,
|
|
418
|
+
output_dir=None,
|
|
397
419
|
PWB_dir=None,
|
|
398
420
|
shapefile_dir=None,
|
|
399
421
|
target_crs=None,
|
|
400
422
|
target_resolution=None,
|
|
423
|
+
benchmark_dict=None,
|
|
401
424
|
):
|
|
425
|
+
if output_dir is None:
|
|
426
|
+
output_dir = os.path.join(os.getcwd(), "Evaluation_Results")
|
|
427
|
+
|
|
402
428
|
main_dir = Path(main_dir)
|
|
403
|
-
# Read the permanent water bodies
|
|
404
|
-
if PWB_dir is None:
|
|
405
|
-
gdf = get_PWB()
|
|
406
|
-
else:
|
|
407
|
-
gdf = gpd.read_file(PWB_dir)
|
|
408
429
|
|
|
409
430
|
# Grant the permission to the main directory
|
|
410
431
|
fix_permissions(main_dir)
|
|
@@ -414,32 +435,55 @@ def EvaluateFIM(
|
|
|
414
435
|
benchmark_path = None
|
|
415
436
|
candidate_path = []
|
|
416
437
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
438
|
+
for tif_file in tif_files:
|
|
439
|
+
if benchmark_name(tif_file):
|
|
440
|
+
benchmark_path = tif_file
|
|
441
|
+
else:
|
|
442
|
+
candidate_path.append(tif_file)
|
|
443
|
+
|
|
444
|
+
if benchmark_path and candidate_path:
|
|
445
|
+
if method_name is None:
|
|
446
|
+
local_method = "AOI"
|
|
423
447
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
benchmark_path = tif_file
|
|
448
|
+
# For single case, if user have explicitly send boundary, use that, else use the boundary from the benchmark FIM evaluation
|
|
449
|
+
if shapefile_dir is not None:
|
|
450
|
+
local_shapefile = shapefile_dir
|
|
428
451
|
else:
|
|
429
|
-
|
|
452
|
+
boundary = find_best_boundary(folder_dir, benchmark_path)
|
|
453
|
+
if boundary is None:
|
|
454
|
+
print(
|
|
455
|
+
f"Skipping {folder_dir.name}: no boundary file found "
|
|
456
|
+
f"and method_name is None (auto-AOI)."
|
|
457
|
+
)
|
|
458
|
+
return
|
|
459
|
+
local_shapefile = str(boundary)
|
|
460
|
+
else:
|
|
461
|
+
local_method = method_name
|
|
462
|
+
local_shapefile = shapefile_dir
|
|
430
463
|
|
|
431
|
-
if benchmark_path and candidate_path:
|
|
432
464
|
print(f"**Flood Inundation Evaluation of {folder_dir.name}**")
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
465
|
+
try:
|
|
466
|
+
Metrics = evaluateFIM(
|
|
467
|
+
benchmark_path,
|
|
468
|
+
candidate_path,
|
|
469
|
+
PWB_dir,
|
|
470
|
+
folder_dir,
|
|
471
|
+
local_method,
|
|
472
|
+
output_dir,
|
|
473
|
+
shapefile=local_shapefile,
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
# Print results in structured table format with 3 decimal points
|
|
477
|
+
candidate_names = [os.path.splitext(os.path.basename(path))[0] for path in candidate_path]
|
|
478
|
+
df_display = pd.DataFrame.from_dict(Metrics, orient='index')
|
|
479
|
+
df_display.columns = candidate_names
|
|
480
|
+
df_display.reset_index(inplace=True)
|
|
481
|
+
df_display.rename(columns={'index': 'Metrics'}, inplace=True)
|
|
482
|
+
print("\n")
|
|
483
|
+
print(df_display.to_string(index=False, float_format='%.3f'))
|
|
484
|
+
print("\n")
|
|
485
|
+
except Exception as e:
|
|
486
|
+
print(f"Error evaluating {folder_dir.name}: {e}")
|
|
443
487
|
else:
|
|
444
488
|
print(
|
|
445
489
|
f"Skipping {folder_dir.name} as it doesn't have a valid benchmark and candidate configuration."
|
|
@@ -448,34 +492,51 @@ def EvaluateFIM(
|
|
|
448
492
|
# Check if main_dir directly contains tif files
|
|
449
493
|
TIFFfiles_main_dir = list(main_dir.glob("*.tif"))
|
|
450
494
|
if TIFFfiles_main_dir:
|
|
451
|
-
|
|
452
|
-
|
|
495
|
+
|
|
496
|
+
# Ensure benchmark is present if needed
|
|
497
|
+
TIFFfiles_main_dir = ensure_benchmark(
|
|
498
|
+
main_dir, TIFFfiles_main_dir, benchmark_dict
|
|
453
499
|
)
|
|
454
500
|
|
|
455
|
-
# processing folder
|
|
456
501
|
processing_folder = main_dir / "processing"
|
|
457
|
-
|
|
502
|
+
try:
|
|
503
|
+
MakeFIMsUniform(
|
|
504
|
+
main_dir, target_crs=target_crs, target_resolution=target_resolution
|
|
505
|
+
)
|
|
458
506
|
|
|
459
|
-
|
|
460
|
-
|
|
507
|
+
# processing folder
|
|
508
|
+
TIFFfiles = list(processing_folder.glob("*.tif"))
|
|
509
|
+
|
|
510
|
+
process_TIFF(TIFFfiles, main_dir)
|
|
511
|
+
except Exception as e:
|
|
512
|
+
print(f"Error processing {main_dir}: {e}")
|
|
513
|
+
finally:
|
|
514
|
+
safe_delete_folder(processing_folder)
|
|
461
515
|
else:
|
|
462
516
|
for folder in main_dir.iterdir():
|
|
463
517
|
if folder.is_dir():
|
|
464
518
|
tif_files = list(folder.glob("*.tif"))
|
|
465
519
|
|
|
466
520
|
if tif_files:
|
|
467
|
-
MakeFIMsUniform(
|
|
468
|
-
folder,
|
|
469
|
-
target_crs=target_crs,
|
|
470
|
-
target_resolution=target_resolution,
|
|
471
|
-
)
|
|
472
|
-
|
|
473
521
|
processing_folder = folder / "processing"
|
|
474
|
-
|
|
522
|
+
try:
|
|
523
|
+
# Ensure benchmark is present if needed
|
|
524
|
+
tif_files = ensure_benchmark(folder, tif_files, benchmark_dict)
|
|
525
|
+
|
|
526
|
+
MakeFIMsUniform(
|
|
527
|
+
folder,
|
|
528
|
+
target_crs=target_crs,
|
|
529
|
+
target_resolution=target_resolution,
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
TIFFfiles = list(processing_folder.glob("*.tif"))
|
|
475
533
|
|
|
476
|
-
|
|
477
|
-
|
|
534
|
+
process_TIFF(TIFFfiles, folder)
|
|
535
|
+
except Exception as e:
|
|
536
|
+
print(f"Error processing folder {folder.name}: {e}")
|
|
537
|
+
finally:
|
|
538
|
+
safe_delete_folder(processing_folder)
|
|
478
539
|
else:
|
|
479
540
|
print(
|
|
480
541
|
f"Skipping {folder.name} as it doesn't contain any tif files."
|
|
481
|
-
)
|
|
542
|
+
)
|
|
@@ -101,7 +101,9 @@ def getContingencyMap(raster_path, method_path):
|
|
|
101
101
|
base_name = os.path.basename(raster_path).split(".")[0]
|
|
102
102
|
output_path = os.path.join(plot_dir, f"{base_name}.png")
|
|
103
103
|
plt.savefig(output_path, dpi=500, bbox_inches="tight")
|
|
104
|
-
plt.show()
|
|
104
|
+
plt.show(block=False)
|
|
105
|
+
plt.pause(5.0)
|
|
106
|
+
plt.close()
|
|
105
107
|
|
|
106
108
|
|
|
107
109
|
def PrintContingencyMap(main_dir, method_name, out_dir):
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Supath Dhital
|
|
3
|
+
Date Created: January 2026
|
|
4
|
+
|
|
5
|
+
Description: This module extracts permanent water bodies
|
|
6
|
+
using the ArcGIS REST API and AWS S3 for a given boundary file. The mechanism is using AWS, it retrieve all the US permanent water bodies shapefile from S3 bucket and then clip on the boundary.
|
|
7
|
+
|
|
8
|
+
This FIMeval module now uses the ArcGIS REST API to extract water bodies within a specified boundary. As it query data for only specified boundary, it is more efficient and faster than downloading the entire US water bodies dataset.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# import Libraries
|
|
12
|
+
import geopandas as gpd
|
|
13
|
+
import boto3
|
|
14
|
+
import botocore
|
|
15
|
+
import os
|
|
16
|
+
import tempfile
|
|
17
|
+
import requests
|
|
18
|
+
import pandas as pd
|
|
19
|
+
import numpy as np
|
|
20
|
+
import json
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Union, Optional
|
|
23
|
+
from shapely.geometry import box
|
|
24
|
+
|
|
25
|
+
#USING ANONYMOUS S3 CLIENT TO ACCESS PUBLIC DATA
|
|
26
|
+
# Initialize an anonymous S3 client
|
|
27
|
+
s3 = boto3.client(
|
|
28
|
+
"s3", config=botocore.config.Config(signature_version=botocore.UNSIGNED)
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
bucket_name = "sdmlab"
|
|
32
|
+
pwb_folder = "PWB/"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def PWB_inS3(s3_client, bucket, prefix):
|
|
36
|
+
"""Download all components of a shapefile from S3 into a temporary directory."""
|
|
37
|
+
tmp_dir = tempfile.mkdtemp()
|
|
38
|
+
response = s3_client.list_objects_v2(Bucket=bucket, Prefix=prefix)
|
|
39
|
+
if "Contents" not in response:
|
|
40
|
+
raise ValueError("No files found in the specified S3 folder.")
|
|
41
|
+
|
|
42
|
+
for obj in response["Contents"]:
|
|
43
|
+
file_key = obj["Key"]
|
|
44
|
+
file_name = os.path.basename(file_key)
|
|
45
|
+
if file_name.endswith((".shp", ".shx", ".dbf", ".prj", ".cpg")):
|
|
46
|
+
local_path = os.path.join(tmp_dir, file_name)
|
|
47
|
+
s3_client.download_file(bucket, file_key, local_path)
|
|
48
|
+
|
|
49
|
+
shp_files = [f for f in os.listdir(tmp_dir) if f.endswith(".shp")]
|
|
50
|
+
if not shp_files:
|
|
51
|
+
raise ValueError("No .shp file found after download.")
|
|
52
|
+
|
|
53
|
+
shp_path = os.path.join(tmp_dir, shp_files[0])
|
|
54
|
+
return shp_path
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_PWB():
|
|
58
|
+
shp_path = PWB_inS3(s3, bucket_name, pwb_folder)
|
|
59
|
+
pwb = gpd.read_file(shp_path)
|
|
60
|
+
return pwb
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
#USING ARCGIS REST TO ACCESS PUBLIC DATA- More fast
|
|
64
|
+
class ExtractPWB:
|
|
65
|
+
SERVICE_URL = "https://services.arcgis.com/P3ePLMYs2RVChkJx/arcgis/rest/services/USA_Detailed_Water_Bodies/FeatureServer/0"
|
|
66
|
+
|
|
67
|
+
def __init__(
|
|
68
|
+
self,
|
|
69
|
+
boundary: Union[str, Path, gpd.GeoDataFrame],
|
|
70
|
+
layer: Optional[str] = None,
|
|
71
|
+
output_dir: Optional[Union[str, Path]] = None,
|
|
72
|
+
save: bool = True,
|
|
73
|
+
output_filename: str = "permanent_water.gpkg"
|
|
74
|
+
):
|
|
75
|
+
self.boundary_gdf = self._load_boundary(boundary, layer)
|
|
76
|
+
self.output_dir = Path(output_dir) if output_dir else Path.cwd() / "PWBOutputs"
|
|
77
|
+
|
|
78
|
+
# We store the final result in self.gdf so it can be accessed after init
|
|
79
|
+
self.gdf = self.extract(save=save, output_filename=output_filename)
|
|
80
|
+
|
|
81
|
+
def _load_boundary(self, boundary, layer):
|
|
82
|
+
if isinstance(boundary, gpd.GeoDataFrame):
|
|
83
|
+
gdf = boundary.copy()
|
|
84
|
+
else:
|
|
85
|
+
kwargs = {"layer": layer} if layer else {}
|
|
86
|
+
gdf = gpd.read_file(boundary, **kwargs)
|
|
87
|
+
return gdf.to_crs("EPSG:4326") if gdf.crs != "EPSG:4326" else gdf
|
|
88
|
+
|
|
89
|
+
def _get_query_envelopes(self, threshold=1.0):
|
|
90
|
+
xmin, ymin, xmax, ymax = self.boundary_gdf.total_bounds
|
|
91
|
+
cols = list(np.arange(xmin, xmax, threshold)) + [xmax]
|
|
92
|
+
rows = list(np.arange(ymin, ymax, threshold)) + [ymax]
|
|
93
|
+
|
|
94
|
+
grid = []
|
|
95
|
+
for i in range(len(cols)-1):
|
|
96
|
+
for j in range(len(rows)-1):
|
|
97
|
+
grid.append({
|
|
98
|
+
"xmin": cols[i], "ymin": rows[j],
|
|
99
|
+
"xmax": cols[i+1], "ymax": rows[j+1],
|
|
100
|
+
"spatialReference": {"wkid": 4326}
|
|
101
|
+
})
|
|
102
|
+
return grid
|
|
103
|
+
|
|
104
|
+
def extract(self, save: bool = True, output_filename: str = "permanent_water.gpkg", verbose: bool = True) -> gpd.GeoDataFrame:
|
|
105
|
+
all_features = []
|
|
106
|
+
query_url = f"{self.SERVICE_URL}/query"
|
|
107
|
+
envelopes = self._get_query_envelopes()
|
|
108
|
+
|
|
109
|
+
permanent_filter = "FTYPE IN ('Lake/Pond', 'Stream/River', 'Reservoir', 'Canal/Ditch')"
|
|
110
|
+
|
|
111
|
+
for env_idx, env in enumerate(envelopes):
|
|
112
|
+
offset = 0
|
|
113
|
+
limit = 1000
|
|
114
|
+
|
|
115
|
+
while True:
|
|
116
|
+
payload = {
|
|
117
|
+
"f": "geojson",
|
|
118
|
+
"where": permanent_filter,
|
|
119
|
+
"geometry": json.dumps(env),
|
|
120
|
+
"geometryType": "esriGeometryEnvelope",
|
|
121
|
+
"inSR": "4326",
|
|
122
|
+
"spatialRel": "esriSpatialRelIntersects",
|
|
123
|
+
"outFields": "NAME,FTYPE,FCODE,SQKM",
|
|
124
|
+
"returnGeometry": "true",
|
|
125
|
+
"outSR": "4326",
|
|
126
|
+
"resultOffset": offset,
|
|
127
|
+
"resultRecordCount": limit
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
response = requests.post(query_url, data=payload, timeout=60)
|
|
132
|
+
response.raise_for_status()
|
|
133
|
+
data = response.json()
|
|
134
|
+
|
|
135
|
+
features = data.get("features", [])
|
|
136
|
+
if not features:
|
|
137
|
+
break
|
|
138
|
+
|
|
139
|
+
batch_gdf = gpd.GeoDataFrame.from_features(features, crs="EPSG:4326")
|
|
140
|
+
all_features.append(batch_gdf)
|
|
141
|
+
|
|
142
|
+
if verbose and offset > 0:
|
|
143
|
+
print(f" Grid {env_idx}: Paginated to offset {offset}...")
|
|
144
|
+
|
|
145
|
+
if len(features) < limit:
|
|
146
|
+
break
|
|
147
|
+
|
|
148
|
+
offset += limit
|
|
149
|
+
|
|
150
|
+
except Exception as e:
|
|
151
|
+
print(f"Error at grid {env_idx}, offset {offset}: {e}")
|
|
152
|
+
break
|
|
153
|
+
|
|
154
|
+
if not all_features:
|
|
155
|
+
print("No water bodies found.")
|
|
156
|
+
return gpd.GeoDataFrame()
|
|
157
|
+
|
|
158
|
+
# Combine and Deduplicate
|
|
159
|
+
full_gdf = pd.concat(all_features, ignore_index=True)
|
|
160
|
+
full_gdf = gpd.GeoDataFrame(full_gdf, crs="EPSG:4326")
|
|
161
|
+
full_gdf = full_gdf.loc[full_gdf.geometry.to_wkt().drop_duplicates().index]
|
|
162
|
+
|
|
163
|
+
# Clip to exact AOI
|
|
164
|
+
final_gdf = gpd.clip(full_gdf, self.boundary_gdf)
|
|
165
|
+
|
|
166
|
+
# Conditional Saving
|
|
167
|
+
if save:
|
|
168
|
+
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
169
|
+
output_path = self.output_dir / output_filename
|
|
170
|
+
final_gdf.to_file(output_path, driver="GPKG")
|
|
171
|
+
if verbose: print(f"Saved {len(final_gdf)} features to {output_path}")
|
|
172
|
+
else:
|
|
173
|
+
if verbose: print(f"PWB Extraction complete.")
|
|
174
|
+
|
|
175
|
+
return final_gdf
|
fimeval/__init__.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
from .ContingencyMap.evaluationFIM import EvaluateFIM
|
|
3
3
|
from .ContingencyMap.printcontingency import PrintContingencyMap
|
|
4
4
|
from .ContingencyMap.plotevaluationmetrics import PlotEvaluationMetrics
|
|
5
|
-
from .ContingencyMap.
|
|
5
|
+
from .ContingencyMap.water_bodies import get_PWB, ExtractPWB
|
|
6
6
|
|
|
7
7
|
# Utility modules
|
|
8
8
|
from .utilis import compress_tif_lzw
|
|
@@ -10,6 +10,12 @@ from .utilis import compress_tif_lzw
|
|
|
10
10
|
# Evaluation with Building foorprint module
|
|
11
11
|
from .BuildingFootprint.evaluationwithBF import EvaluationWithBuildingFootprint
|
|
12
12
|
|
|
13
|
+
# Access benchmark FIM module
|
|
14
|
+
from .BenchFIMQuery.access_benchfim import benchFIMquery
|
|
15
|
+
|
|
16
|
+
# Building Footprint module
|
|
17
|
+
from .BuildingFootprint.arcgis_API import getBuildingFootprint
|
|
18
|
+
|
|
13
19
|
__all__ = [
|
|
14
20
|
"EvaluateFIM",
|
|
15
21
|
"PrintContingencyMap",
|
|
@@ -17,4 +23,7 @@ __all__ = [
|
|
|
17
23
|
"get_PWB",
|
|
18
24
|
"EvaluationWithBuildingFootprint",
|
|
19
25
|
"compress_tif_lzw",
|
|
26
|
+
"benchFIMquery",
|
|
27
|
+
"getBuildingFootprint",
|
|
28
|
+
"ExtractPWB",
|
|
20
29
|
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This code setup all the case folders whether it has valid benchmark FIM/ which benchmark need to access from catalog and so on.
|
|
3
|
+
Basically It will do everything before going into the actual evaluation process.
|
|
4
|
+
Author: Supath Dhital
|
|
5
|
+
Date updated: 25 Nov, 2025
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from .BenchFIMQuery.access_benchfim import benchFIMquery
|
|
11
|
+
from .utilis import benchmark_name
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def ensure_benchmark(folder_dir, tif_files, benchmark_map):
|
|
15
|
+
"""
|
|
16
|
+
If no local benchmark is found in `tif_files`, and `folder_dir.name`
|
|
17
|
+
exists in `benchmark_map`, download it into this folder using benchFIMquery.
|
|
18
|
+
Returns an updated list of tif files.
|
|
19
|
+
"""
|
|
20
|
+
folder_dir = Path(folder_dir)
|
|
21
|
+
|
|
22
|
+
# If a benchmark/BM tif is already present, just use existing files
|
|
23
|
+
has_benchmark = any(benchmark_name(f) for f in tif_files)
|
|
24
|
+
if has_benchmark or not benchmark_map:
|
|
25
|
+
return tif_files
|
|
26
|
+
|
|
27
|
+
# If folder not in mapping, do nothing
|
|
28
|
+
folder_key = folder_dir.name
|
|
29
|
+
file_name = benchmark_map.get(folder_key)
|
|
30
|
+
if not file_name:
|
|
31
|
+
return tif_files
|
|
32
|
+
|
|
33
|
+
# Download benchmark FIM by filename into this folder
|
|
34
|
+
benchFIMquery(
|
|
35
|
+
file_name=file_name,
|
|
36
|
+
download=True,
|
|
37
|
+
out_dir=str(folder_dir),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Return refreshed tif list
|
|
41
|
+
return list(folder_dir.glob("*.tif"))
|
fimeval/utilis.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import re
|
|
2
3
|
import shutil
|
|
3
4
|
import pyproj
|
|
4
5
|
import rasterio
|
|
@@ -182,3 +183,49 @@ def MakeFIMsUniform(fim_dir, target_crs=None, target_resolution=None):
|
|
|
182
183
|
resample_to_resolution(str(src_path), coarsest_x, coarsest_y)
|
|
183
184
|
else:
|
|
184
185
|
print("All rasters already have the same resolution. No resampling needed.")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# Function to find the best boundary file in the folder if multiple boundary files are present
|
|
189
|
+
def find_best_boundary(folder: Path, benchmark_path: Path):
|
|
190
|
+
"""
|
|
191
|
+
Choose the best boundary file in `folder`:
|
|
192
|
+
- prefer .gpkg (from benchFIM downloads),
|
|
193
|
+
- otherwise, pick the file with the most name tokens in common with the benchmark.
|
|
194
|
+
"""
|
|
195
|
+
exts = [".gpkg", ".shp", ".geojson", ".kml"]
|
|
196
|
+
candidates = []
|
|
197
|
+
for ext in exts:
|
|
198
|
+
candidates.extend(folder.glob(f"*{ext}"))
|
|
199
|
+
|
|
200
|
+
if not candidates:
|
|
201
|
+
return None
|
|
202
|
+
if len(candidates) == 1:
|
|
203
|
+
print(f"Auto-detected boundary: {candidates[0]}")
|
|
204
|
+
return candidates[0]
|
|
205
|
+
|
|
206
|
+
bench_tokens = set(
|
|
207
|
+
t for t in re.split(r"[_\-\.\s]+", benchmark_path.stem.lower()) if t
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
def score(path: Path):
|
|
211
|
+
name_tokens = set(t for t in re.split(r"[_\-\.\s]+", path.stem.lower()) if t)
|
|
212
|
+
common = len(bench_tokens & name_tokens)
|
|
213
|
+
bonus = 1 if path.suffix.lower() == ".gpkg" else 0
|
|
214
|
+
return (common, bonus)
|
|
215
|
+
|
|
216
|
+
best = max(candidates, key=score)
|
|
217
|
+
print(f"Auto-detected boundary (best match to benchmark): {best}")
|
|
218
|
+
return best
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# To test whether the tif is benchmark or not
|
|
222
|
+
def benchmark_name(f: Path) -> bool:
|
|
223
|
+
name = f.stem.lower()
|
|
224
|
+
|
|
225
|
+
# Explicit word
|
|
226
|
+
if "benchmark" in name:
|
|
227
|
+
return True
|
|
228
|
+
|
|
229
|
+
# Treating underscores/dashes/dots as separators and look for a 'bm' token
|
|
230
|
+
tokens = re.split(r"[_\-\.\s]+", name)
|
|
231
|
+
return "bm" in tokens
|