smashbox.tools.geo_toolbox

   1import numpy as np
   2from scipy.ndimage import zoom
   3import rasterio
   4
   5
   6# Convert coordinates to mesh matrix row-col (from smash)
   7def xy_to_rowcol(x, y, xmin, ymax, xres, yres):
   8    row = int((ymax - y) / yres)
   9    col = int((x - xmin) / xres)
  10    return row, col
  11
  12
  13# Convert mesh matrix row-col to geographic coordinates (from smash)
  14def rowcol_to_xy(row, col, xmin, ymax, xres, yres):
  15    x = int(col * xres + xmin)
  16    y = int(ymax - row * yres)
  17    return x, y
  18
  19
  20def get_bbox_from_smash_mesh(mesh):
  21    """
  22    Description
  23    -----------
  24    Compute the bbox from a Smash mesh dictionary
  25    Parameters
  26    ----------
  27    mesh: dict
  28        dict of smash mesh
  29    return
  30    ------
  31    dict()
  32        the bounding box of the smash mesh
  33    """
  34
  35    if "xres" in mesh and "yres" in mesh:
  36        dx = mesh["xres"]
  37        dy = mesh["yres"]
  38    else:
  39        dx = np.mean(mesh["dx"])
  40        dy = np.mean(mesh["dy"])
  41
  42    if "ncol" in mesh:
  43        ncol = mesh["ncol"]
  44        nrow = mesh["nrow"]
  45    else:
  46        nrow = mesh["active_cell"].shape[0]
  47        ncol = mesh["active_cell"].shape[1]
  48
  49    left = mesh["xmin"]
  50    right = mesh["xmin"] + ncol * dx
  51    bottom = mesh["ymax"] - nrow * dy
  52    top = mesh["ymax"]
  53    bbox = {"left": left, "bottom": bottom, "right": right, "top": top}
  54
  55    return bbox
  56
  57
  58def get_bbox_from_ascii_data(ascii_data):
  59    """
  60
  61    Description
  62    -----------
  63
  64    Compute the boundingbox from ascii-data stored in a dictionnary (must contain the header)
  65
  66    Parameters
  67    ----------
  68
  69    ascii_data: dict()
  70        Un dictionnaire contenant un format ascii: {ascii_data,xllcorner,yllcorner,ncol,nrows,cellsize}, la clé ascii_data contient un sous dictionnaire contenant des clés associés a des sources de données {clé:numpy_array}
  71
  72    return
  73    ------
  74
  75    dict()
  76        Un dictionnaire contenant la boundingbox (coordonnées): {left, bottom, right, top}
  77
  78    """
  79    ascii_data_l = {}
  80    for key, value in ascii_data.items():
  81        ascii_data_l.update({key.lower(): value})
  82
  83    bbox = {
  84        "left": ascii_data_l["xllcorner"],
  85        "bottom": ascii_data_l["yllcorner"],
  86        "right": ascii_data_l["xllcorner"]
  87        + ascii_data_l["ncols"] * ascii_data_l["cellsize"],
  88        "top": ascii_data_l["yllcorner"]
  89        + ascii_data_l["nrows"] * ascii_data_l["cellsize"],
  90    }
  91
  92    return bbox
  93
  94
  95def check_bbox_consistency(bbox_model_active_cell, bbox_param):
  96
  97    if bbox_model_active_cell["left"] < bbox_param["left"]:
  98        print(
  99            f"Warning: Model domain is larger than the domain of the parameter. {bbox_model_active_cell['left']}<{bbox_param['left']} (bbox model left < bbox param left). Expect lacuna (-99.) in model parameters"
 100        )
 101
 102    if bbox_model_active_cell["right"] > bbox_param["right"]:
 103        print(
 104            f"Warning: Model domain is larger than the domain of the parameter. {bbox_model_active_cell['right']}>{bbox_param['right']} (bbox model left < bbox param left). Expect lacuna (-99.) in model parameters"
 105        )
 106
 107    if bbox_model_active_cell["bottom"] < bbox_param["bottom"]:
 108        print(
 109            f"Warning: Model domain is larger than the domain of the parameter. {bbox_model_active_cell['bottom']}<{bbox_param['bottom']} (bbox model left < bbox param left). Expect lacuna (-99.) in model parameters"
 110        )
 111
 112    if bbox_model_active_cell["top"] < bbox_param["top"]:
 113        print(
 114            f"Warning: Model domain is larger than the domain of the parameter. {bbox_model_active_cell['top']}>{bbox_param['top']} (bbox model left < bbox param left). Expect lacuna (-99.) in model parameters"
 115        )
 116
 117
 118def intersection_bbox(bbox1, bbox2):
 119    """
 120
 121    Description
 122    -----------
 123
 124    Function which compute the bounding boxes intersection of 2 input bbox. It return the working bbox
 125
 126    Parameters
 127    ----------
 128
 129    bbox1: dict()
 130        containing the first bbox informations
 131    bbox2 : dict()
 132        containing the second bbox informations
 133
 134    returns
 135    ----------
 136
 137    dict()
 138        containing the bbox union
 139
 140    Examples
 141    ----------
 142
 143    dataset=gdal_raster_open(filename)
 144    possible_bbox=intersection_bbox(bbox,bbox_dataset)
 145
 146    """
 147    left = max(bbox1["left"], bbox2["left"])
 148    bottom = max(bbox1["bottom"], bbox2["bottom"])
 149    right = min(bbox1["right"], bbox2["right"])
 150    top = min(bbox1["top"], bbox2["top"])
 151    if (left < right) and (bottom < top):
 152        bbox_intersection = {
 153            "left": left,
 154            "bottom": bottom,
 155            "right": right,
 156            "top": top,
 157        }
 158        return bbox_intersection
 159    else:
 160        print("Impossible bounding boxes intersection")
 161        return {"left": 0, "bottom": 0, "right": 0, "top": 0}
 162
 163
 164def get_window_from_bbox(mesh, bbox):
 165    """
 166    Function to get the mesh window from a defined bbox
 167
 168    Parameters
 169    ----------
 170    dataset: gdal object
 171    bbox : dict containing the bbox
 172    ----------
 173    returns
 174    dic containing the computed windows
 175
 176    Examples
 177    ----------
 178    dataset=gdal_raster_open(filename)
 179    bbox_dataset=get_bbox(dataset)
 180    window=get_window_from_bbox(dataset,bbox_dataset)
 181
 182    """
 183
 184    if "xres" in mesh and "yres" in mesh:
 185        dx = mesh["xres"]
 186        dy = mesh["yres"]
 187    else:
 188        dx = np.mean(mesh["dx"])
 189        dy = np.mean(mesh["dy"])
 190
 191    col_off = (bbox["left"] - mesh["xmin"]) / dx
 192    row_off = (mesh["ymax"] - bbox["top"]) / dy
 193    ncols = (bbox["right"] - bbox["left"]) / dx
 194    nrows = (bbox["top"] - bbox["bottom"]) / dy
 195
 196    if (col_off < 0) or (row_off < 0):
 197        raise Exception(
 198            "The requested bounding box exceeds the limits of the raster domain."
 199        )
 200
 201    window = {
 202        "row_off": int(row_off),
 203        "col_off": int(col_off),
 204        "nrows": int(nrows),
 205        "ncols": int(ncols),
 206    }
 207
 208    return window
 209
 210
 211def get_cropped_window_from_bbox_intersection(
 212    bbox_intersection, bbox_origin, dx, dy
 213):
 214    """
 215
 216    Description
 217    -----------
 218
 219    Function to compute the domain to crop between a bbox intersection (included into bbox_origin) and the origin bbox. This function return a window such as the domain with bbox_intersection can be cropped using the retruned window according the bbox_origin
 220
 221    Parameters
 222    ----------
 223
 224    bbox_intersection: dict
 225        A bbox that intersect bbox_origin
 226
 227    bbox_origin: dict
 228        a bbox from which we want to extract data
 229
 230    dx: float
 231        size of the grid in the x direction
 232
 233    dy: float
 234        size of the grid in the y direction
 235
 236    Return
 237    ------
 238
 239    dict()
 240        a window dictionnary containing information to crop a matrix: {row_off, col_off, nrows, ncols}
 241
 242
 243    """
 244    if (
 245        (bbox_intersection["left"] < bbox_origin["left"])
 246        or (bbox_intersection["bottom"] < bbox_origin["bottom"])
 247        or (bbox_intersection["right"] > bbox_origin["right"])
 248        or (bbox_intersection["top"] > bbox_origin["top"])
 249    ):
 250        print(
 251            "The domain of bbox_intersection is not included in the domain of bbox_out"
 252        )
 253        window = {"row_off": 0, "col_off": 0, "nrows": 0, "ncols": 0}
 254        return window
 255
 256    col_off = (bbox_intersection["left"] - bbox_origin["left"]) / dx
 257    row_off = (bbox_origin["top"] - bbox_intersection["top"]) / dy
 258
 259    ncols = (bbox_intersection["right"] - bbox_intersection["left"]) / dx
 260    nrows = (bbox_intersection["top"] - bbox_intersection["bottom"]) / dy
 261
 262    window = {
 263        "row_off": int(row_off),
 264        "col_off": int(col_off),
 265        "nrows": int(nrows),
 266        "ncols": int(ncols),
 267    }
 268
 269    return window
 270
 271
 272def write_array_to_geotiff(filename, array, xmin, ymax, xres, yres):
 273
 274    metadata = {
 275        "driver": "GTiff",
 276        "dtype": "float64",
 277        "nodata": None,
 278        "width": array.shape[1],
 279        "height": array.shape[0],
 280        "count": 1,
 281        "crs": None,
 282        "transform": rasterio.Affine(xres, 0.0, xmin, 0.0, -yres, ymax),
 283    }
 284
 285    rasterio_write_tiff(filename=filename, matrix=array, metadata=metadata)
 286
 287
 288def rasterio_write_tiff(filename="test.tif", matrix=np.zeros(10), metadata={}):
 289
 290    with rasterio.Env():
 291        with rasterio.open(filename, "w", compress="lzw", **metadata) as dst:
 292            dst.write(matrix, 1)
 293
 294
 295def crop_array(
 296    array,
 297    bbox_in,
 298    res_in,
 299    bbox_out,
 300    res_out,
 301    order=0,
 302    cval=-99.0,
 303    grid_mode=True,
 304):
 305    """
 306
 307    Description
 308    --------------
 309
 310    Crop a part of a numpy array with an input bbox and resolution to a new bbox with a new resolution
 311
 312    Parameters
 313    ----------
 314
 315    array : numpy.array()
 316        Input gridded numpy array shape=(n,m)
 317
 318    bbox_in : dict()
 319        bounding box of the input array. Dictionnary {"left":,"top":,"right":,"bottom":}
 320
 321    res_in : dict()
 322        resolution of the input array in x and y direction. Disctionnary {"dx":, "dy":}
 323
 324    bbox_out : dict()
 325        bounding box of the output array. Dictionnary {"left":,"top":,"right":,"bottom":}
 326
 327    res_out : dict()
 328        resolution of the output array in x and y direction. Disctionnary {"dx":, "dy":}
 329
 330    order : int()
 331        order of the resampling cubic interpolation
 332
 333    cval : float() | np.nan
 334        fill value for the extended boundaries
 335
 336    grid_mode : bool()
 337        True | False. if True coordinate start from the edge of the cell. If False coordinate starts from the center of the cell.
 338
 339    Return
 340    ------
 341
 342    numpy.array()
 343        Cropped and resampled array according bbox_out and res_out
 344
 345    """
 346
 347    # intersection bbox
 348    bbox_intersection = intersection_bbox(bbox_in, bbox_out)
 349
 350    # ---------------------- fait une découpe grossière du domaine -------
 351
 352    # 1- crop array on bbox_intersection+-res_in at res in: shrink the domain, speed up futur resampling on large domain ?
 353    if res_in["dx"] >= res_out["dx"] and res_in["dy"] >= res_out["dy"]:
 354        res_shrinked = {"dx": res_in["dx"], "dy": res_in["dy"]}
 355    elif res_in["dx"] < res_out["dx"] and res_in["dy"] < res_out["dy"]:
 356        res_shrinked = {"dx": res_out["dy"], "dy": res_out["dy"]}
 357    elif res_in["dx"] < res_out["dx"] and res_in["dy"] >= res_out["dy"]:
 358        res_shrinked = {"dx": res_out["dx"], "dy": res_in["dy"]}
 359    elif res_in["dx"] >= res_out["dx"] and res_in["dy"] < res_out["dy"]:
 360        res_shrinked = {"dx": res_in["dx"], "dy": res_out["dy"]}
 361
 362    bbox_in_shrinked = {
 363        "left": bbox_in["left"]
 364        + int(
 365            max(bbox_intersection["left"] - bbox_in["left"], 0)
 366            / res_shrinked["dx"]
 367        )
 368        * res_shrinked["dx"],
 369        "right": bbox_in["right"]
 370        - int(
 371            max(bbox_in["right"] - bbox_intersection["right"], 0)
 372            / res_shrinked["dx"]
 373        )
 374        * res_shrinked["dx"],
 375        "bottom": bbox_in["bottom"]
 376        + int(
 377            max(bbox_intersection["bottom"] - bbox_in["bottom"], 0)
 378            / res_shrinked["dy"]
 379        )
 380        * res_shrinked["dy"],
 381        "top": bbox_in["top"]
 382        - int(
 383            max(bbox_in["top"] - bbox_intersection["top"], 0)
 384            / res_shrinked["dy"]
 385        )
 386        * res_shrinked["dy"],
 387    }
 388    bbox_intersection_shrinked = intersection_bbox(bbox_in, bbox_in_shrinked)
 389
 390    windows_wrap = get_window_from_bbox(
 391        mesh={
 392            "xmin": bbox_in["left"],
 393            "ymax": bbox_in["top"],
 394            "xres": res_shrinked["dx"],
 395            "yres": res_shrinked["dy"],
 396        },
 397        bbox=bbox_intersection_shrinked,
 398    )
 399
 400    # Erase input array and bbox_in
 401    array = array[
 402        windows_wrap["row_off"] : windows_wrap["row_off"]
 403        + windows_wrap["nrows"],
 404        windows_wrap["col_off"] : windows_wrap["col_off"]
 405        + windows_wrap["ncols"],
 406    ]
 407
 408    bbox_in = bbox_intersection_shrinked
 409
 410    # ---------------------------------------------------------------------------------------
 411
 412    # 2- resample the array to res_out
 413    resampled_array = resample_array(
 414        array,
 415        res_in=res_in,
 416        res_out=res_out,
 417        order=order,
 418        cval=cval,
 419        grid_mode=grid_mode,
 420    )
 421
 422    # 3- crop the array on the intersection of bbox_in and bbox_out
 423    # window of bbox_intersection in the domain of bbox_in (bbox_prcp, matrix to read)
 424    window_intersection = get_cropped_window_from_bbox_intersection(
 425        bbox_intersection, bbox_in, res_out["dx"], res_out["dy"]
 426    )
 427
 428    # reading the part of the matrix (array_in)
 429    cropped_array = resampled_array[
 430        window_intersection["row_off"] : window_intersection["row_off"]
 431        + window_intersection["nrows"],
 432        window_intersection["col_off"] : window_intersection["col_off"]
 433        + window_intersection["ncols"],
 434    ]
 435
 436    # allocate out array: shape of bbox_out
 437    array_out = (
 438        np.zeros(
 439            shape=(
 440                int((bbox_out["top"] - bbox_out["bottom"]) / res_out["dx"]),
 441                int((bbox_out["right"] - bbox_out["left"]) / res_out["dy"]),
 442            )
 443        )
 444        - 99.0
 445    )
 446
 447    # window of bbox_intersection in the domain of bbox_smash
 448    window_intersection_out = get_cropped_window_from_bbox_intersection(
 449        bbox_intersection, bbox_out, res_out["dx"], res_out["dy"]
 450    )
 451
 452    # prcp domain smaller than smash domain:
 453    if window_intersection_out["ncols"] > cropped_array.shape[1]:
 454        window_intersection_out["ncols"] = cropped_array.shape[1]
 455    if window_intersection_out["nrows"] > cropped_array.shape[0]:
 456        window_intersection_out["nrows"] = cropped_array.shape[0]
 457
 458    # copy crop_array (input matrix cropped) in array_out
 459    array_out[
 460        window_intersection_out["row_off"] : window_intersection_out["row_off"]
 461        + window_intersection_out["nrows"],
 462        window_intersection_out["col_off"] : window_intersection_out["col_off"]
 463        + window_intersection_out["ncols"],
 464    ] = cropped_array
 465
 466    return array_out
 467
 468
 469def resample_array(
 470    array,
 471    res_in={"dx": 1, "dy": 1},
 472    res_out={"dx": 1, "dy": 1},
 473    order=0,
 474    cval=-99.0,
 475    grid_mode=True,
 476):
 477    """
 478
 479    Parameters
 480    ----------
 481
 482    array: numpy.array()
 483        Input gridded numpy array shape=(n,m)
 484
 485    res_in: dict()
 486        resolution of the input array in x and y direction. Disctionnary {"dx":, "dy":}
 487
 488    res_out: dict()
 489        resolution of the output array in x and y direction. Disctionnary {"dx":, "dy":}
 490
 491    order: int()
 492        order of the resampling cubic interpolation
 493
 494    cval: float() | np.nan
 495        fill value for the extended boundaries
 496
 497    grid_mode: bool()
 498        True | False. if True coordinate start from the edge of the cell. If False coordinate starts from the center of the cell.
 499
 500    Return
 501    ------
 502
 503    numpy.array()
 504        Cropped and resampled array according bbox_out and res_out
 505
 506    """
 507
 508    ratio_x = res_in["dx"] / res_out["dx"]
 509    ratio_y = res_in["dy"] / res_out["dy"]
 510    resampled_array = zoom(
 511        array,
 512        (ratio_y, ratio_x),
 513        order=order,
 514        mode="grid-constant",
 515        cval=-99.0,
 516        grid_mode=True,
 517    )
 518
 519    return resampled_array
 520
 521
 522# def read_geotiff_to_ascii(path=""):
 523
 524#     if not os.path.exists(path):
 525#         raise ValueError(f"{path} does not exist.")
 526
 527#     with rasterio.open(path) as ds:
 528#         transform = ds.get_transform()
 529#         xmin = transform[0]
 530#         ymax = transform[3]
 531#         xres = transform[1]
 532#         yres = -transform[5]
 533
 534#         ascii_data = {
 535#             "ncols": ds.width,
 536#             "nrows": ds.height,
 537#             "cellsize": xres,
 538#             "xllcorner": xmin,
 539#             "yllcorner": ymax - ds.height * yres,
 540#             "nodata_value": ds.nodata,
 541#             "data": ds.read(indexes=1),
 542#         }
 543
 544#     return ascii_data
 545
 546
 547# def write_smashparam_to_geotiff(model, path):
 548
 549#     if not os.path.exists(path):
 550#         os.mkdir(path)
 551
 552#     list_param = list(model.rr_parameters.keys)
 553
 554#     for param in list_param:
 555
 556#         array = model.rr_parameters.values[:, :, list_param.index(param)]
 557
 558#         write_array_to_geotiff(
 559#             os.path.join(path, param + ".tif"),
 560#             array,
 561#             model.mesh.xmin,
 562#             model.mesh.ymax,
 563#             model.mesh.xres,
 564#             model.mesh.yres,
 565#         )
 566
 567
 568# # format standart smashop ?
 569# # faire une fonction qui extrait des params depuis smash vers un dict_ascii_format ?
 570# def load_param_from_tiffformat(
 571#     model=None, path_to_parameters="", bounding_condition=False
 572# ):
 573
 574#     # check metadata first
 575
 576#     list_param = model.rr_parameters.keys
 577
 578#     mesh_out = object_handler.read_object_as_dict(model.mesh)
 579
 580#     bbox_out = get_bbox_from_smash_mesh(mesh_out)
 581#     res_out = {"dx": np.mean(mesh_out["dx"]), "dy": np.mean(mesh_out["dy"])}
 582
 583#     for param in list_param:
 584
 585#         if os.path.exists(os.path.join(path_to_parameters, param + ".tif")):
 586
 587#             ascii_data = read_geotiff_to_ascii(
 588#                 os.path.join(path_to_parameters, param + ".tif")
 589#             )
 590
 591#             bbox_in = get_bbox_from_ascii_data(ascii_data)
 592
 593#             check_bbox_consistency(bbox_out, bbox_in)
 594
 595#             res_in = {"dx": ascii_data["cellsize"], "dy": ascii_data["cellsize"]}
 596
 597#             print(f"</> In read_param_from_asciiformat, reading param {param}")
 598
 599#             cropped_param = crop_array(
 600#                 ascii_data["data"],
 601#                 bbox_in,
 602#                 res_in,
 603#                 bbox_out,
 604#                 res_out,
 605#                 resampling_method="scipy-zoom",
 606#                 order=0,
 607#                 cval=-99.0,
 608#                 grid_mode=True,
 609#             )
 610
 611#             pos = np.where(list_param == param)[0][0]
 612#             model.rr_parameters.values[:, :, pos] = cropped_param
 613
 614#             print("</> Removing values lower than 0")
 615#             model.rr_parameters.values[:, :, pos] = np.where(
 616#                 model.rr_parameters.values[:, :, pos] < 0,
 617#                 0,
 618#                 model.rr_parameters.values[:, :, pos],
 619#             )
 620
 621#         else:
 622
 623#             print(
 624#                 f"</> Error: in load_param_from_tiffformat, missing parameter {param} in {path_to_parameters}"
 625#             )
 626
 627
 628# # format standart smashop ?
 629# # faire une fonction qui extrait des params depuis smash vers un dict_ascii_format ?
 630# def load_param_from_asciiformat(model, dict_ascii_parameters, bounding_condition=False):
 631
 632#     list_param = model.rr_parameters.keys
 633
 634#     mesh_out = object_handler.read_object_as_dict(model.mesh)
 635
 636#     bbox_in = get_bbox_from_ascii_data(dict_ascii_parameters)
 637#     res_in = {
 638#         "dx": dict_ascii_parameters["cellsize"],
 639#         "dy": dict_ascii_parameters["cellsize"],
 640#     }
 641
 642#     bbox_out = get_bbox_from_smash_mesh(mesh_out)
 643#     res_out = {"dx": np.mean(mesh_out["dx"]), "dy": np.mean(mesh_out["dy"])}
 644
 645#     check_bbox_consistency(bbox_out, bbox_in)
 646
 647#     for param in list_param:
 648
 649#         if param in dict_ascii_parameters["ascii_data"].keys():
 650
 651#             print(f"</> In read_param_from_asciiformat, reading param {param}")
 652
 653#             cropped_param = crop_array(
 654#                 param,
 655#                 bbox_in,
 656#                 res_in,
 657#                 bbox_out,
 658#                 res_out,
 659#                 resampling_method="scipy-zoom",
 660#                 order=0,
 661#                 cval=-99.0,
 662#                 grid_mode=True,
 663#             )
 664
 665#             pos = np.where(list_param == param)[0][0]
 666#             model.rr_parameters.values[:, :, pos] = cropped_param
 667#         else:
 668#             print(
 669#                 f"</> In read_param_from_asciiformat, skipping missing parameter {param}"
 670#             )
 671
 672
 673# def transfert_params_to_model(
 674#     from_mesh=dict(), with_param=(dict | object), to_model=None
 675# ):
 676
 677#     if isinstance(with_param, dict):
 678#         pass
 679#     else:
 680#         with_param = object_handler.read_object_as_dict(with_param)
 681
 682#     structure = to_model.setup.structure
 683#     structure_parameters = smash._constant.STRUCTURE_RR_PARAMETERS.copy()
 684#     # copy() obligatoire car la liste contenu dans la copie du dictionnaire n'est pas une copy mais un pointeur ...
 685#     parameters_list = structure_parameters[structure].copy()
 686
 687#     mesh_out = object_handler.read_object_as_dict(to_model.mesh)
 688
 689#     bbox_in = get_bbox_from_smash_mesh(from_mesh)
 690
 691#     if "xres" in from_mesh and "yres" in from_mesh:
 692#         dx = from_mesh["xres"]
 693#         dy = from_mesh["yres"]
 694#     else:
 695#         dx = np.mean(from_mesh["dx"])
 696#         dy = np.mean(from_mesh["dy"])
 697
 698#     res_in = {
 699#         "dx": dx,
 700#         "dy": dy,
 701#     }
 702
 703#     bbox_out = get_bbox_from_smash_mesh(mesh_out)
 704
 705#     if "xres" in mesh_out and "yres" in mesh_out:
 706#         dx = mesh_out["xres"]
 707#         dy = mesh_out["yres"]
 708#     else:
 709#         dx = np.mean(mesh_out["dx"])
 710#         dy = np.mean(mesh_out["dy"])
 711
 712#     res_out = {
 713#         "dx": dx,
 714#         "dy": dy,
 715#     }
 716
 717#     for i in range(len(parameters_list)):
 718
 719#         if "values" in with_param:
 720#             param = with_param["values"][:, :, i]
 721#             pos = i
 722#         else:
 723#             param = with_param[parameters_list[i]]
 724#             pos = np.where(to_model.rr_parameters.keys == parameters_list[i])[0][0]
 725
 726#         cropped_param = crop_array(
 727#             param,
 728#             bbox_in,
 729#             res_in,
 730#             bbox_out,
 731#             res_out,
 732#             resampling_method="scipy-zoom",
 733#             order=0,
 734#             cval=-99.0,
 735#             grid_mode=True,
 736#         )
 737
 738#         to_model.rr_parameters.values[:, :, pos] = cropped_param
 739
 740
 741# def resample_array(
 742#     array,
 743#     res_in={"dx": 1, "dy": 1},
 744#     res_out={"dx": 1, "dy": 1},
 745#     # resampling_method="scipy-zoom",
 746#     order=3,
 747#     cval=-99.0,
 748#     grid_mode=True,
 749# ):
 750# """
 751
 752# Description
 753# -----------
 754
 755# Resample an array with input resolution to the output resolution. It use zoom function form scipy package.
 756
 757# Parameters
 758# ----------
 759
 760# array: numpy.array()
 761#     Input gridded numpy array shape=(n,m)
 762
 763# res_in: dict()
 764#     resolution of the input array in x and y direction. Disctionnary {"dx":, "dy":}
 765
 766# res_out: dict()
 767#     resolution of the output array in x and y direction. Disctionnary {"dx":, "dy":}
 768
 769# resampling_method: str()
 770#     scipy-zoom | numpy ; scipy-zoom use the zoom function from scipy (see function resample_array_scipy_zoom for other input arg). numpy only resample using basic function np.repeat ans basic calculation (not suitable for every case).
 771
 772# Return
 773# ------
 774
 775# numpy.array()
 776#     Resampled array according res_out
 777
 778# """
 779# if resampling_method == "numpy":
 780#     resampled_array = resample_array_numpy(array, res_in=res_in, res_out=res_out)
 781
 782#     if resampled_array is None:
 783#         print("</>Fallback to scipy-zoom resampling (scipy.ndimage.zoom method)")
 784#         resampling_method = "scipy-zoom"
 785
 786# if resampling_method == "scipy-zoom":
 787#     resampled_array = resample_array_scipy_zoom(
 788#         array, res_in, res_out, order=0, cval=-99.0, grid_mode=True
 789#     )
 790
 791# return resampled_array
 792
 793
 794# def resample_array_numpy(array, res_in={"dx": 1, "dy": 1}, res_out={"dx": 1, "dy": 1}):
 795#     """
 796
 797#     Description
 798#     -----------
 799
 800#     resample an array with input resolution to the output resolution. It simpy use numpy functions.
 801
 802#     Parameters
 803#     ----------
 804
 805#     array: numpy.array()
 806#         Input gridded numpy array shape=(n,m)
 807
 808#     res_in: dict()
 809#         resolution of the input array in x and y direction. Disctionnary {"dx":, "dy":}
 810
 811#     res_out: dict()
 812#         resolution of the output array in x and y direction. Disctionnary {"dx":, "dy":}
 813
 814#     Return
 815#     ------
 816
 817#     numpy.array()
 818#         Resampled array according res_out
 819
 820#     """
 821#     # 1Resampling
 822#     if (res_in["dx"] >= res_out["dx"]) and (res_in["dy"] >= res_out["dy"]):
 823#         ratio_x = int(res_in["dx"] / res_out["dx"])
 824#         ratio_y = int(res_in["dy"] / res_out["dy"])
 825#         # resampling input matrix to dx,dy smash si résolution demandée est inférieur
 826#         resampled_array = np.repeat(np.repeat(array, ratio_x, axis=0), ratio_y, axis=1)
 827
 828#     elif (res_in["dx"] < res_out["dx"]) and (res_in["dy"] < res_out["dy"]):
 829
 830#         ratio_x = int(res_out["dx"] / res_in["dx"])
 831#         ratio_y = int(res_out["dy"] / res_in["dy"])
 832#         # resampling input matrix si résolution demandée est supérieur:
 833#         xsize = int(np.ceil(array.shape[0] / ratio_x))
 834#         ysize = int(np.ceil(array.shape[1] / ratio_y))
 835#         resampled_array = np.full(shape=(xsize, ysize), fill_value=0)
 836
 837#         xr = 0
 838#         for x in range(0, xsize):
 839#             yr = 0
 840#             for y in range(0, ysize):
 841#                 buffer = array[xr : xr + ratio_x, yr : yr + ratio_y]
 842#                 average = np.mean(buffer, axis=(0, 1))
 843#                 resampled_array[x, y, :] = average
 844#                 yr = yr + ratio_y
 845#             xr = xr + ratio_x
 846#     else:
 847#         print(
 848#             "</> average resampling method impossible, different resampling resolution in x and y axis."
 849#         )
 850#         return None
 851
 852#     return resampled_array
 853
 854
 855# def import_parameters(model: Model, path_to_parameters: FilePath):
 856#     """
 857#     Description
 858#     -----------
 859#     Read a geotif, resample if necessarry then clip it on the bouning box of the smash mesh
 860
 861#     Parameters
 862#     ----------
 863#     model: object
 864#         SMASH model object
 865#     path_to_parameters: str
 866#         Path to the directory which contain the geotiff files (parameters)
 867
 868#     return
 869#     ------
 870#     np.ndarray
 871#         The data clipped on the SMASH bounding box
 872#     """
 873#     list_param = model.rr_parameters.keys
 874
 875#     for param in list_param:
 876#         if os.path.exists(os.path.join(path_to_parameters, param + ".tif")):
 877#             cropped_param = _rasterio_read_param2(
 878#                 path=os.path.join(path_to_parameters, param + ".tif"), mesh=model.mesh
 879#             )
 880
 881#             pos = np.argwhere(list_param == param).item()
 882#             model.rr_parameters.values[:, :, pos] = cropped_param
 883
 884#         else:
 885#             raise ValueError(f"Missing parameter {param} in {path_to_parameters}")
 886
 887
 888# def _rasterio_read_param(path: FilePath, mesh: MeshDT):
 889#     """
 890#     Description
 891#     -----------
 892#     Read a geotif, resample if necessarry then clip it on the bouning box of the smash mesh
 893
 894#     Parameters
 895#     ----------
 896#     path: str
 897#         Path to a geotiff file.
 898#     mesh: object
 899#         object of the smash mesh
 900
 901#     return
 902#     ------
 903#     np.ndarray
 904#         The data clipped on the SMASH bounding box
 905#     """
 906#     bounds = get_bbox_from_smash_mesh(mesh)
 907#     xres = mesh.xres
 908#     yres = mesh.yres
 909
 910#     # Open the larger raster
 911#     with rasterio.open(path) as dataset:
 912#         x_scale_factor = dataset.res[0] / xres
 913#         y_scale_factor = dataset.res[1] / yres
 914
 915#         # resampling first to avoid spatial shifting of the parameters
 916#         data = dataset.read(
 917#             out_shape=(
 918#                 dataset.count,
 919#                 int(dataset.height * y_scale_factor),
 920#                 int(dataset.width * x_scale_factor),
 921#             ),
 922#             resampling=Resampling.nearest,
 923#         )
 924
 925#     bbox_dataset = {
 926#         "left": dataset.bounds.left,
 927#         "bottom": dataset.bounds.bottom,
 928#         "right": dataset.bounds.right,
 929#         "top": dataset.bounds.top,
 930#     }
 931
 932#     bbox_intersection = intersection_bbox(bbox_dataset, bounds)
 933
 934#     window_intersection = get_cropped_window_from_bbox_intersection(
 935#         bbox_intersection, bbox_dataset, xres, yres
 936#     )
 937
 938#     cropped_array = data[
 939#         0,
 940#         window_intersection["row_off"] : window_intersection["row_off"]
 941#         + window_intersection["nrows"],
 942#         window_intersection["col_off"] : window_intersection["col_off"]
 943#         + window_intersection["ncols"],
 944#     ]
 945
 946#     # allocate out array: shape of bbox_out
 947#     array_out = np.zeros(
 948#         shape=(
 949#             int((bounds["top"] - bounds["bottom"]) / xres),
 950#             int((bounds["right"] - bounds["left"]) / yres),
 951#         )
 952#     )
 953
 954#     # window of bbox_intersection in the domain of bbox_smash
 955#     window_intersection_out = get_cropped_window_from_bbox_intersection(
 956#         bbox_intersection, bounds, xres, yres
 957#     )
 958
 959#     # copy crop_array (input matrix cropped) in array_out
 960#     array_out[
 961#         window_intersection_out["row_off"] : window_intersection_out["row_off"]
 962#         + window_intersection_out["nrows"],
 963#         window_intersection_out["col_off"] : window_intersection_out["col_off"]
 964#         + window_intersection_out["ncols"],
 965#     ] = cropped_array
 966
 967#     return array_out
 968
 969
 970# def _rasterio_read_param2(path: FilePath, mesh: MeshDT):
 971#     """
 972#     Description
 973#     -----------
 974#     Read a geotif, resample if necessarry then clip it on the bouning box of the smash mesh
 975
 976#     Parameters
 977#     ----------
 978#     path: str
 979#         Path to a geotiff file.
 980#     mesh: object
 981#         object of the smash mesh
 982
 983#     return
 984#     ------
 985#     np.ndarray
 986#         The data clipped on the SMASH bounding box
 987#     """
 988#     input_bbox = get_bbox_from_smash_mesh(mesh)
 989
 990#     xres = mesh.xres
 991#     yres = mesh.yres
 992
 993#     output_crs = rasterio.CRS.from_epsg(mesh.epsg)
 994
 995#     # Open the larger raster
 996#     with rasterio.open(path) as dataset:
 997
 998#         x_scale_factor = dataset.res[0] / xres
 999#         y_scale_factor = dataset.res[1] / yres
1000
1001#         transform = dataset.transform
1002#         height = dataset.height
1003#         width = dataset.width
1004#         crs = dataset.crs
1005
1006#         # resampling first to avoid spatial shifting of the parameters
1007#         data = dataset.read(
1008#             out_shape=(
1009#                 dataset.count,
1010#                 int(dataset.height * y_scale_factor),
1011#                 int(dataset.width * x_scale_factor),
1012#             ),
1013#             resampling=Resampling.nearest,
1014#         )
1015
1016#     # Use a memory dataset
1017#     with rasterio.io.MemoryFile() as memfile:
1018
1019#         with memfile.open(
1020#             driver="GTiff",
1021#             height=height,
1022#             width=width,
1023#             count=1,
1024#             dtype=data.dtype,
1025#             transform=transform,
1026#             crs=crs,
1027#         ) as dataset:
1028#             dataset.write(data[0, :, :], 1)
1029
1030#             new_width = int((input_bbox["right"] - input_bbox["left"]) / yres)
1031#             new_height = int((input_bbox["top"] - input_bbox["bottom"]) / xres)
1032#             new_transform = rasterio.transform.from_bounds(
1033#                 west=input_bbox["left"],
1034#                 south=input_bbox["bottom"],
1035#                 east=input_bbox["right"],
1036#                 north=input_bbox["top"],
1037#                 width=new_width,
1038#                 height=new_height,
1039#             )
1040
1041#             # Target array
1042#             new_array = np.empty((new_height, new_width), dtype=np.float32)
1043
1044#             # reproject dataset
1045#             rasterio.warp.reproject(
1046#                 source=rasterio.band(dataset, 1),
1047#                 destination=new_array,
1048#                 src_transform=transform,
1049#                 src_crs=crs,
1050#                 dst_transform=new_transform,
1051#                 dst_crs=output_crs,
1052#                 resampling=Resampling.nearest,
1053#             )
1054
1055#     return new_array
def xy_to_rowcol(x, y, xmin, ymax, xres, yres):
 8def xy_to_rowcol(x, y, xmin, ymax, xres, yres):
 9    row = int((ymax - y) / yres)
10    col = int((x - xmin) / xres)
11    return row, col
def rowcol_to_xy(row, col, xmin, ymax, xres, yres):
15def rowcol_to_xy(row, col, xmin, ymax, xres, yres):
16    x = int(col * xres + xmin)
17    y = int(ymax - row * yres)
18    return x, y
def get_bbox_from_smash_mesh(mesh):
21def get_bbox_from_smash_mesh(mesh):
22    """
23    Description
24    -----------
25    Compute the bbox from a Smash mesh dictionary
26    Parameters
27    ----------
28    mesh: dict
29        dict of smash mesh
30    return
31    ------
32    dict()
33        the bounding box of the smash mesh
34    """
35
36    if "xres" in mesh and "yres" in mesh:
37        dx = mesh["xres"]
38        dy = mesh["yres"]
39    else:
40        dx = np.mean(mesh["dx"])
41        dy = np.mean(mesh["dy"])
42
43    if "ncol" in mesh:
44        ncol = mesh["ncol"]
45        nrow = mesh["nrow"]
46    else:
47        nrow = mesh["active_cell"].shape[0]
48        ncol = mesh["active_cell"].shape[1]
49
50    left = mesh["xmin"]
51    right = mesh["xmin"] + ncol * dx
52    bottom = mesh["ymax"] - nrow * dy
53    top = mesh["ymax"]
54    bbox = {"left": left, "bottom": bottom, "right": right, "top": top}
55
56    return bbox

Description

Compute the bbox from a Smash mesh dictionary

Parameters

mesh: dict dict of smash mesh

return

dict() the bounding box of the smash mesh

def get_bbox_from_ascii_data(ascii_data):
59def get_bbox_from_ascii_data(ascii_data):
60    """
61
62    Description
63    -----------
64
65    Compute the boundingbox from ascii-data stored in a dictionnary (must contain the header)
66
67    Parameters
68    ----------
69
70    ascii_data: dict()
71        Un dictionnaire contenant un format ascii: {ascii_data,xllcorner,yllcorner,ncol,nrows,cellsize}, la clé ascii_data contient un sous dictionnaire contenant des clés associés a des sources de données {clé:numpy_array}
72
73    return
74    ------
75
76    dict()
77        Un dictionnaire contenant la boundingbox (coordonnées): {left, bottom, right, top}
78
79    """
80    ascii_data_l = {}
81    for key, value in ascii_data.items():
82        ascii_data_l.update({key.lower(): value})
83
84    bbox = {
85        "left": ascii_data_l["xllcorner"],
86        "bottom": ascii_data_l["yllcorner"],
87        "right": ascii_data_l["xllcorner"]
88        + ascii_data_l["ncols"] * ascii_data_l["cellsize"],
89        "top": ascii_data_l["yllcorner"]
90        + ascii_data_l["nrows"] * ascii_data_l["cellsize"],
91    }
92
93    return bbox

Description

Compute the boundingbox from ascii-data stored in a dictionnary (must contain the header)

Parameters

ascii_data: dict() Un dictionnaire contenant un format ascii: {ascii_data,xllcorner,yllcorner,ncol,nrows,cellsize}, la clé ascii_data contient un sous dictionnaire contenant des clés associés a des sources de données {clé:numpy_array}

return

dict() Un dictionnaire contenant la boundingbox (coordonnées): {left, bottom, right, top}

def check_bbox_consistency(bbox_model_active_cell, bbox_param):
 96def check_bbox_consistency(bbox_model_active_cell, bbox_param):
 97
 98    if bbox_model_active_cell["left"] < bbox_param["left"]:
 99        print(
100            f"Warning: Model domain is larger than the domain of the parameter. {bbox_model_active_cell['left']}<{bbox_param['left']} (bbox model left < bbox param left). Expect lacuna (-99.) in model parameters"
101        )
102
103    if bbox_model_active_cell["right"] > bbox_param["right"]:
104        print(
105            f"Warning: Model domain is larger than the domain of the parameter. {bbox_model_active_cell['right']}>{bbox_param['right']} (bbox model left < bbox param left). Expect lacuna (-99.) in model parameters"
106        )
107
108    if bbox_model_active_cell["bottom"] < bbox_param["bottom"]:
109        print(
110            f"Warning: Model domain is larger than the domain of the parameter. {bbox_model_active_cell['bottom']}<{bbox_param['bottom']} (bbox model left < bbox param left). Expect lacuna (-99.) in model parameters"
111        )
112
113    if bbox_model_active_cell["top"] < bbox_param["top"]:
114        print(
115            f"Warning: Model domain is larger than the domain of the parameter. {bbox_model_active_cell['top']}>{bbox_param['top']} (bbox model left < bbox param left). Expect lacuna (-99.) in model parameters"
116        )
def intersection_bbox(bbox1, bbox2):
119def intersection_bbox(bbox1, bbox2):
120    """
121
122    Description
123    -----------
124
125    Function which compute the bounding boxes intersection of 2 input bbox. It return the working bbox
126
127    Parameters
128    ----------
129
130    bbox1: dict()
131        containing the first bbox informations
132    bbox2 : dict()
133        containing the second bbox informations
134
135    returns
136    ----------
137
138    dict()
139        containing the bbox union
140
141    Examples
142    ----------
143
144    dataset=gdal_raster_open(filename)
145    possible_bbox=intersection_bbox(bbox,bbox_dataset)
146
147    """
148    left = max(bbox1["left"], bbox2["left"])
149    bottom = max(bbox1["bottom"], bbox2["bottom"])
150    right = min(bbox1["right"], bbox2["right"])
151    top = min(bbox1["top"], bbox2["top"])
152    if (left < right) and (bottom < top):
153        bbox_intersection = {
154            "left": left,
155            "bottom": bottom,
156            "right": right,
157            "top": top,
158        }
159        return bbox_intersection
160    else:
161        print("Impossible bounding boxes intersection")
162        return {"left": 0, "bottom": 0, "right": 0, "top": 0}

Description

Function which compute the bounding boxes intersection of 2 input bbox. It return the working bbox

Parameters

bbox1: dict() containing the first bbox informations bbox2 : dict() containing the second bbox informations

returns

dict() containing the bbox union

Examples

dataset=gdal_raster_open(filename) possible_bbox=intersection_bbox(bbox,bbox_dataset)

def get_window_from_bbox(mesh, bbox):
165def get_window_from_bbox(mesh, bbox):
166    """
167    Function to get the mesh window from a defined bbox
168
169    Parameters
170    ----------
171    dataset: gdal object
172    bbox : dict containing the bbox
173    ----------
174    returns
175    dic containing the computed windows
176
177    Examples
178    ----------
179    dataset=gdal_raster_open(filename)
180    bbox_dataset=get_bbox(dataset)
181    window=get_window_from_bbox(dataset,bbox_dataset)
182
183    """
184
185    if "xres" in mesh and "yres" in mesh:
186        dx = mesh["xres"]
187        dy = mesh["yres"]
188    else:
189        dx = np.mean(mesh["dx"])
190        dy = np.mean(mesh["dy"])
191
192    col_off = (bbox["left"] - mesh["xmin"]) / dx
193    row_off = (mesh["ymax"] - bbox["top"]) / dy
194    ncols = (bbox["right"] - bbox["left"]) / dx
195    nrows = (bbox["top"] - bbox["bottom"]) / dy
196
197    if (col_off < 0) or (row_off < 0):
198        raise Exception(
199            "The requested bounding box exceeds the limits of the raster domain."
200        )
201
202    window = {
203        "row_off": int(row_off),
204        "col_off": int(col_off),
205        "nrows": int(nrows),
206        "ncols": int(ncols),
207    }
208
209    return window

Function to get the mesh window from a defined bbox

Parameters

dataset: gdal object

bbox : dict containing the bbox

returns dic containing the computed windows

Examples

dataset=gdal_raster_open(filename) bbox_dataset=get_bbox(dataset) window=get_window_from_bbox(dataset,bbox_dataset)

def get_cropped_window_from_bbox_intersection(bbox_intersection, bbox_origin, dx, dy):
212def get_cropped_window_from_bbox_intersection(
213    bbox_intersection, bbox_origin, dx, dy
214):
215    """
216
217    Description
218    -----------
219
220    Function to compute the domain to crop between a bbox intersection (included into bbox_origin) and the origin bbox. This function return a window such as the domain with bbox_intersection can be cropped using the retruned window according the bbox_origin
221
222    Parameters
223    ----------
224
225    bbox_intersection: dict
226        A bbox that intersect bbox_origin
227
228    bbox_origin: dict
229        a bbox from which we want to extract data
230
231    dx: float
232        size of the grid in the x direction
233
234    dy: float
235        size of the grid in the y direction
236
237    Return
238    ------
239
240    dict()
241        a window dictionnary containing information to crop a matrix: {row_off, col_off, nrows, ncols}
242
243
244    """
245    if (
246        (bbox_intersection["left"] < bbox_origin["left"])
247        or (bbox_intersection["bottom"] < bbox_origin["bottom"])
248        or (bbox_intersection["right"] > bbox_origin["right"])
249        or (bbox_intersection["top"] > bbox_origin["top"])
250    ):
251        print(
252            "The domain of bbox_intersection is not included in the domain of bbox_out"
253        )
254        window = {"row_off": 0, "col_off": 0, "nrows": 0, "ncols": 0}
255        return window
256
257    col_off = (bbox_intersection["left"] - bbox_origin["left"]) / dx
258    row_off = (bbox_origin["top"] - bbox_intersection["top"]) / dy
259
260    ncols = (bbox_intersection["right"] - bbox_intersection["left"]) / dx
261    nrows = (bbox_intersection["top"] - bbox_intersection["bottom"]) / dy
262
263    window = {
264        "row_off": int(row_off),
265        "col_off": int(col_off),
266        "nrows": int(nrows),
267        "ncols": int(ncols),
268    }
269
270    return window

Description

Function to compute the domain to crop between a bbox intersection (included into bbox_origin) and the origin bbox. This function return a window such as the domain with bbox_intersection can be cropped using the retruned window according the bbox_origin

Parameters

bbox_intersection: dict A bbox that intersect bbox_origin

bbox_origin: dict a bbox from which we want to extract data

dx: float size of the grid in the x direction

dy: float size of the grid in the y direction

Return

dict() a window dictionnary containing information to crop a matrix: {row_off, col_off, nrows, ncols}

def write_array_to_geotiff(filename, array, xmin, ymax, xres, yres):
273def write_array_to_geotiff(filename, array, xmin, ymax, xres, yres):
274
275    metadata = {
276        "driver": "GTiff",
277        "dtype": "float64",
278        "nodata": None,
279        "width": array.shape[1],
280        "height": array.shape[0],
281        "count": 1,
282        "crs": None,
283        "transform": rasterio.Affine(xres, 0.0, xmin, 0.0, -yres, ymax),
284    }
285
286    rasterio_write_tiff(filename=filename, matrix=array, metadata=metadata)
def rasterio_write_tiff( filename='test.tif', matrix=array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]), metadata={}):
289def rasterio_write_tiff(filename="test.tif", matrix=np.zeros(10), metadata={}):
290
291    with rasterio.Env():
292        with rasterio.open(filename, "w", compress="lzw", **metadata) as dst:
293            dst.write(matrix, 1)
def crop_array( array, bbox_in, res_in, bbox_out, res_out, order=0, cval=-99.0, grid_mode=True):
296def crop_array(
297    array,
298    bbox_in,
299    res_in,
300    bbox_out,
301    res_out,
302    order=0,
303    cval=-99.0,
304    grid_mode=True,
305):
306    """
307
308    Description
309    --------------
310
311    Crop a part of a numpy array with an input bbox and resolution to a new bbox with a new resolution
312
313    Parameters
314    ----------
315
316    array : numpy.array()
317        Input gridded numpy array shape=(n,m)
318
319    bbox_in : dict()
320        bounding box of the input array. Dictionnary {"left":,"top":,"right":,"bottom":}
321
322    res_in : dict()
323        resolution of the input array in x and y direction. Disctionnary {"dx":, "dy":}
324
325    bbox_out : dict()
326        bounding box of the output array. Dictionnary {"left":,"top":,"right":,"bottom":}
327
328    res_out : dict()
329        resolution of the output array in x and y direction. Disctionnary {"dx":, "dy":}
330
331    order : int()
332        order of the resampling cubic interpolation
333
334    cval : float() | np.nan
335        fill value for the extended boundaries
336
337    grid_mode : bool()
338        True | False. if True coordinate start from the edge of the cell. If False coordinate starts from the center of the cell.
339
340    Return
341    ------
342
343    numpy.array()
344        Cropped and resampled array according bbox_out and res_out
345
346    """
347
348    # intersection bbox
349    bbox_intersection = intersection_bbox(bbox_in, bbox_out)
350
351    # ---------------------- fait une découpe grossière du domaine -------
352
353    # 1- crop array on bbox_intersection+-res_in at res in: shrink the domain, speed up futur resampling on large domain ?
354    if res_in["dx"] >= res_out["dx"] and res_in["dy"] >= res_out["dy"]:
355        res_shrinked = {"dx": res_in["dx"], "dy": res_in["dy"]}
356    elif res_in["dx"] < res_out["dx"] and res_in["dy"] < res_out["dy"]:
357        res_shrinked = {"dx": res_out["dy"], "dy": res_out["dy"]}
358    elif res_in["dx"] < res_out["dx"] and res_in["dy"] >= res_out["dy"]:
359        res_shrinked = {"dx": res_out["dx"], "dy": res_in["dy"]}
360    elif res_in["dx"] >= res_out["dx"] and res_in["dy"] < res_out["dy"]:
361        res_shrinked = {"dx": res_in["dx"], "dy": res_out["dy"]}
362
363    bbox_in_shrinked = {
364        "left": bbox_in["left"]
365        + int(
366            max(bbox_intersection["left"] - bbox_in["left"], 0)
367            / res_shrinked["dx"]
368        )
369        * res_shrinked["dx"],
370        "right": bbox_in["right"]
371        - int(
372            max(bbox_in["right"] - bbox_intersection["right"], 0)
373            / res_shrinked["dx"]
374        )
375        * res_shrinked["dx"],
376        "bottom": bbox_in["bottom"]
377        + int(
378            max(bbox_intersection["bottom"] - bbox_in["bottom"], 0)
379            / res_shrinked["dy"]
380        )
381        * res_shrinked["dy"],
382        "top": bbox_in["top"]
383        - int(
384            max(bbox_in["top"] - bbox_intersection["top"], 0)
385            / res_shrinked["dy"]
386        )
387        * res_shrinked["dy"],
388    }
389    bbox_intersection_shrinked = intersection_bbox(bbox_in, bbox_in_shrinked)
390
391    windows_wrap = get_window_from_bbox(
392        mesh={
393            "xmin": bbox_in["left"],
394            "ymax": bbox_in["top"],
395            "xres": res_shrinked["dx"],
396            "yres": res_shrinked["dy"],
397        },
398        bbox=bbox_intersection_shrinked,
399    )
400
401    # Erase input array and bbox_in
402    array = array[
403        windows_wrap["row_off"] : windows_wrap["row_off"]
404        + windows_wrap["nrows"],
405        windows_wrap["col_off"] : windows_wrap["col_off"]
406        + windows_wrap["ncols"],
407    ]
408
409    bbox_in = bbox_intersection_shrinked
410
411    # ---------------------------------------------------------------------------------------
412
413    # 2- resample the array to res_out
414    resampled_array = resample_array(
415        array,
416        res_in=res_in,
417        res_out=res_out,
418        order=order,
419        cval=cval,
420        grid_mode=grid_mode,
421    )
422
423    # 3- crop the array on the intersection of bbox_in and bbox_out
424    # window of bbox_intersection in the domain of bbox_in (bbox_prcp, matrix to read)
425    window_intersection = get_cropped_window_from_bbox_intersection(
426        bbox_intersection, bbox_in, res_out["dx"], res_out["dy"]
427    )
428
429    # reading the part of the matrix (array_in)
430    cropped_array = resampled_array[
431        window_intersection["row_off"] : window_intersection["row_off"]
432        + window_intersection["nrows"],
433        window_intersection["col_off"] : window_intersection["col_off"]
434        + window_intersection["ncols"],
435    ]
436
437    # allocate out array: shape of bbox_out
438    array_out = (
439        np.zeros(
440            shape=(
441                int((bbox_out["top"] - bbox_out["bottom"]) / res_out["dx"]),
442                int((bbox_out["right"] - bbox_out["left"]) / res_out["dy"]),
443            )
444        )
445        - 99.0
446    )
447
448    # window of bbox_intersection in the domain of bbox_smash
449    window_intersection_out = get_cropped_window_from_bbox_intersection(
450        bbox_intersection, bbox_out, res_out["dx"], res_out["dy"]
451    )
452
453    # prcp domain smaller than smash domain:
454    if window_intersection_out["ncols"] > cropped_array.shape[1]:
455        window_intersection_out["ncols"] = cropped_array.shape[1]
456    if window_intersection_out["nrows"] > cropped_array.shape[0]:
457        window_intersection_out["nrows"] = cropped_array.shape[0]
458
459    # copy crop_array (input matrix cropped) in array_out
460    array_out[
461        window_intersection_out["row_off"] : window_intersection_out["row_off"]
462        + window_intersection_out["nrows"],
463        window_intersection_out["col_off"] : window_intersection_out["col_off"]
464        + window_intersection_out["ncols"],
465    ] = cropped_array
466
467    return array_out

Description

Crop a part of a numpy array with an input bbox and resolution to a new bbox with a new resolution

Parameters

array : numpy.array() Input gridded numpy array shape=(n,m)

bbox_in : dict() bounding box of the input array. Dictionnary {"left":,"top":,"right":,"bottom":}

res_in : dict() resolution of the input array in x and y direction. Disctionnary {"dx":, "dy":}

bbox_out : dict() bounding box of the output array. Dictionnary {"left":,"top":,"right":,"bottom":}

res_out : dict() resolution of the output array in x and y direction. Disctionnary {"dx":, "dy":}

order : int() order of the resampling cubic interpolation

cval : float() | np.nan fill value for the extended boundaries

grid_mode : bool() True | False. if True coordinate start from the edge of the cell. If False coordinate starts from the center of the cell.

Return

numpy.array() Cropped and resampled array according bbox_out and res_out

def resample_array( array, res_in={'dx': 1, 'dy': 1}, res_out={'dx': 1, 'dy': 1}, order=0, cval=-99.0, grid_mode=True):
470def resample_array(
471    array,
472    res_in={"dx": 1, "dy": 1},
473    res_out={"dx": 1, "dy": 1},
474    order=0,
475    cval=-99.0,
476    grid_mode=True,
477):
478    """
479
480    Parameters
481    ----------
482
483    array: numpy.array()
484        Input gridded numpy array shape=(n,m)
485
486    res_in: dict()
487        resolution of the input array in x and y direction. Disctionnary {"dx":, "dy":}
488
489    res_out: dict()
490        resolution of the output array in x and y direction. Disctionnary {"dx":, "dy":}
491
492    order: int()
493        order of the resampling cubic interpolation
494
495    cval: float() | np.nan
496        fill value for the extended boundaries
497
498    grid_mode: bool()
499        True | False. if True coordinate start from the edge of the cell. If False coordinate starts from the center of the cell.
500
501    Return
502    ------
503
504    numpy.array()
505        Cropped and resampled array according bbox_out and res_out
506
507    """
508
509    ratio_x = res_in["dx"] / res_out["dx"]
510    ratio_y = res_in["dy"] / res_out["dy"]
511    resampled_array = zoom(
512        array,
513        (ratio_y, ratio_x),
514        order=order,
515        mode="grid-constant",
516        cval=-99.0,
517        grid_mode=True,
518    )
519
520    return resampled_array

Parameters

array: numpy.array() Input gridded numpy array shape=(n,m)

res_in: dict() resolution of the input array in x and y direction. Disctionnary {"dx":, "dy":}

res_out: dict() resolution of the output array in x and y direction. Disctionnary {"dx":, "dy":}

order: int() order of the resampling cubic interpolation

cval: float() | np.nan fill value for the extended boundaries

grid_mode: bool() True | False. if True coordinate start from the edge of the cell. If False coordinate starts from the center of the cell.

Return

numpy.array() Cropped and resampled array according bbox_out and res_out