smashbox.model.atmos_data_connector

Created on Tue Jul 15 10:39:36 2025

@author: maxime

  1#!/usr/bin/env python3
  2# -*- coding: utf-8 -*-
  3"""
  4Created on Tue Jul 15 10:39:36 2025
  5
  6@author: maxime
  7"""
  8
  9import numpy as np
 10import datetime
 11import rasterio
 12from rasterio.transform import from_origin
 13from rasterio.io import MemoryFile
 14from rasterio.enums import Resampling
 15from rasterio.warp import reproject
 16from rasterio.windows import from_bounds
 17
 18from tqdm import tqdm
 19
 20from smashbox.tools import geo_toolbox
 21
 22
 23class atmos_data_connector:
 24    """Class atmos_data_connector. This class handle external atmos data (precipitation
 25    and potential evapotranspiration) provider. Precipiation and evapotranspiration
 26    can be provided to Smash as a numpy.ndarray. This class has some functions to read
 27    and crop the array."""
 28
 29    def __init__(
 30        self,
 31        input_prcp: np.ndarray | None = None,
 32        input_pet: np.ndarray | None = None,
 33        input_dt: float = 3600.0,
 34        input_res: tuple | list = (1000.0, 1000.0),
 35        input_start_time: str = "2050-01-01 01:00",
 36        input_bbox: dict | None = None,
 37        input_epsg: int = 2154,
 38    ):
 39        """
 40
 41        :param input_prcp: Matrix of shape (nbx, nby, nbts) of the precipitation,
 42         defaults to None
 43        :type input_prcp: np.ndarray | None, optional
 44        :param input_pet: Matrix of shape (nbx, nby,nbts) of the evapotranspiration,
 45        defaults to None
 46        :type input_pet: np.ndarray | None, optional
 47        :param input_dt: Time step of the input atmos data in seconds, defaults to 3600.0
 48        :type input_dt: float, optional
 49        :param input_res: Resolution of the input data in meters, tuple or list of the
 50         corresponding resolution in x and y direction, defaults to (1000.0, 1000.0)
 51        :type input_res: tuple | list, optional
 52        :param input_start_time: The custom start time of the input atmos data chronicle,
 53         defaults to "2050-01-01 01:00"
 54        :type input_start_time: str, optional
 55        :param input_bbox: The bbox of the input atmos data, defaults to None. if None,
 56        the bbox of the Smash model is used and the left bottom corner of the matrix is
 57        positionned according the smash bbox.
 58        :type input_bbox: dict | None, optional
 59        :param input_epsg: The Epsg code of the input bbox coordinate, defaults to 2154
 60        :type input_epsg: int, optional
 61
 62        """
 63
 64        self.input_prcp = input_prcp
 65        """Input precipiation matrix. np.ndarray of shape (nbx,nby,nbts)."""
 66        self.input_pet = input_pet
 67        """Input evapotranspiration matrix. np.ndarray of shape (nbx,nby,nbts)."""
 68        self.input_res = input_res
 69        """Input resolution of the atmos data matrix. tuple | list of the corresponding
 70        resolution in the x and y direction (meters)"""
 71        self.input_dt = input_dt
 72        """Input time-step in seconds of the atmos data."""
 73        self.input_bbox = input_bbox
 74        """Input bounding box of the atmos data matrix. dictionnary
 75        {'left':, 'right':,'top':,'bottom':}"""
 76        self.input_epsg = input_epsg
 77        """Input epsg code of the corresponding coordinates of the bounding box."""
 78        self.input_start_time = None
 79        """Custom start-time date of the chronicle."""
 80        self.input_ntimestep = None
 81        """Number of time-step of the chronicle."""
 82
 83        self.smash_prcp = None
 84        """Cropped Smash precipitation matrix that will be copied into the Smash model."""
 85        self.smash_pet = None
 86        """Cropped PET matrix that will be copied into the Smash model."""
 87        self.smash_start_time = None
 88        """Futur Smash setup start-time"""
 89        self.smash_end_time = None
 90        """Futur Smash setup end_time"""
 91
 92        if input_prcp is not None:
 93            self.get_ntimestep(input_data=input_prcp)
 94            self.set_setup_datetime(input_dt=input_dt, input_start_time=input_start_time)
 95
 96        if input_pet is not None:
 97            if input_pet.shape != input_prcp.shape:
 98                raise ValueError(
 99                    "The shape of input_pet and input_prcp must be the same."
100                )
101
102    def get_ntimestep(self, input_data: np.ndarray | None = None):
103        """
104        Compute the number of time-step of the input atmos data.
105        """
106
107        if input_data is not None:
108
109            if len(input_data.shape) != 3:
110                raise ValueError(
111                    "</> Input atmos data matrix 'input_data' must be an"
112                    "array of shape (nrow, ncol, n_time_step)."
113                )
114
115            self.input_ntimestep = input_data.shape[2]
116
117    def set_setup_datetime(
118        self, input_dt: float = 3600.0, input_start_time: str = "2050-01-01 01:00"
119    ):
120        """
121        Compute the start-time and end-time of the futur Smash simulation.
122
123        :param input_dt: The input time-step of the atmos-data in seconds,
124         defaults to 3600.0
125        :type input_dt: float, optional
126        :param input_start_time: The custom input-start time, defaults to "2050-01-01 01:00"
127        :type input_start_time: str, optional
128
129        """
130
131        if self.input_ntimestep is None:
132            raise ValueError(
133                "</> self.input_ntimestep is None. Use self.get_ntimestep() first."
134            )
135
136        end_time = datetime.datetime.fromisoformat(input_start_time) + datetime.timedelta(
137            seconds=input_dt * self.input_ntimestep
138        )
139
140        self.smash_start_time = input_start_time
141        self.smash_end_time = end_time.strftime("%Y-%m-%d %H:%M")
142
143    def change_setup(self, mysetup):
144        """
145        Change the properties of the setup.mysetup() class.
146        :param mysetup: Class setup.mysetup()
147        :type mysetup: TYPE
148
149        """
150        mysetup.set_setup("start_time", self.smash_start_time)
151        mysetup.set_setup("end_time", self.smash_end_time)
152        mysetup.set_setup("read_qobs", False)
153
154        if self.input_prcp is not None:
155            mysetup.set_setup("read_prcp", False)
156
157        if self.input_pet is not None:
158            mysetup.set_setup("read_pet", False)
159
160    def read_input_atmos_data(
161        self,
162        output_bbox: dict = None,
163        output_epsg: int = 2154,
164        output_res: tuple | list = (1000.0, 1000.0),
165        output_shape: tuple | list = (-99, -99),
166        resampling_method: str = "home_made_with_scipy_zoom",
167    ):
168        """
169        Read, crop and resample input atmos_data if required. Three methods are provided
170        with options resampling_method.
171        :param output_bbox: The output bounding box of the matrix, defaults to None
172        :type output_bbox: dict, optional
173        :param output_epsg: The output EPSG code of the coordinates, defaults to 2154
174        :type output_epsg: int, optional
175        :param output_res: The output resolution of the atmos data matrix,
176        defaults to (1000.0, 1000.0)
177        :type output_res: tuple | list, optional
178        :param output_shape: The ouput shape of the atmosdata matrix.
179        If output_shape and output_bbox are provided and if the input atmos_data matrix
180         matches these characteritics, the resampling is skipped (much faster).
181          Defaults to (-99, -99)
182        :type output_shape: tuple | list, optional
183        :param resampling_method: The resampling method to use, choice are rasterio_1,
184        rasterio_2 and , home_made_with_scipy_zoom. Defaults to "home_made_with_scipy_zoom"
185        :type resampling_method: str, optional
186
187        """
188
189        if resampling_method == "rasterio_1":
190            if self.input_prcp is not None:
191                array = self.read_input_data(
192                    self.input_prcp,
193                    output_bbox=output_bbox,
194                    output_epsg=output_epsg,
195                    output_res=output_res,
196                    output_shape=output_shape,
197                )
198                self.smash_prcp = array
199
200            if self.input_pet is not None:
201                array = self.read_input_data(
202                    self.input_pet,
203                    output_bbox=output_bbox,
204                    output_epsg=output_epsg,
205                    output_res=output_res,
206                    output_shape=output_shape,
207                )
208                self.smash_pet = array
209
210        elif resampling_method == "rasterio_2":
211            if self.input_prcp is not None:
212                array = self.read_input_data2(
213                    self.input_prcp,
214                    output_bbox=output_bbox,
215                    output_epsg=output_epsg,
216                    output_res=output_res,
217                    output_shape=output_shape,
218                )
219
220                self.smash_prcp = array
221
222            if self.input_pet is not None:
223                array = self.read_input_data2(
224                    self.input_pet,
225                    output_bbox=output_bbox,
226                    output_epsg=output_epsg,
227                    output_res=output_res,
228                    output_shape=output_shape,
229                )
230                self.smash_pet = array
231
232        elif resampling_method == "home_made_with_scipy_zoom":
233            if self.input_prcp is not None:
234                array = self.read_input_data3(
235                    self.input_prcp,
236                    output_bbox=output_bbox,
237                    output_epsg=output_epsg,
238                    output_res=output_res,
239                    output_shape=output_shape,
240                )
241                self.smash_prcp = array
242
243            if self.input_pet is not None:
244                array = self.read_input_data3(
245                    self.input_pet,
246                    output_bbox=output_bbox,
247                    output_epsg=output_epsg,
248                    output_res=output_res,
249                    output_shape=output_shape,
250                )
251                self.smash_pet = array
252
253    def read_input_data(
254        self,
255        input_data: np.ndarray | None,
256        output_bbox: dict = None,
257        output_epsg: int = 2154,
258        output_res: tuple | list = (1000.0, 1000.0),
259        output_shape: tuple | list = (-99, -99),
260    ):
261        """
262        Read, crop and resample an input matrix. Use Rasterio MemoryFile and
263         Rasterio reproject function. Slowest method.
264        :param input_data: Input data as a matrix np.ndarray with shape (nbx,nby,nbts)
265        :type input_data: np.ndarray | None
266        :param output_bbox: The output bounding box of the matrix, defaults to None
267        :type output_bbox: dict, optional
268        :param output_epsg: The output EPSG code of the coordinates, defaults to 2154
269        :type output_epsg: int, optional
270        :param output_res: The output resolution of the atmos data matrix,
271        defaults to (1000.0, 1000.0)
272        :type output_res: tuple | list, optional
273        :param output_shape: The ouput shape of the atmosdata matrix.
274        If output_shape and output_bbox are provided and if the input atmos_data matrix
275         matches these characteritics, the resampling is skipped (much faster).
276          Defaults to (-99, -99)
277        :type output_shape: tuple | list, optional
278        :return: Array with shape ans resolution corresponding to the Smash model
279        :rtype: np.ndarray
280
281        """
282
283        height, width, ntimestep = input_data.shape
284        input_crs = f"EPSG:{self.input_epsg}"
285        output_crs = f"EPSG:{output_epsg}"
286
287        # Créer le transform (origine en haut à gauche, donc top et left)
288        if self.input_bbox is None:
289            print(
290                "</> Warning: no bbox or crs was provided with the graffas"
291                " prcp. We suppose the domain of the Graffas prcp equal to the"
292                " domain of the Smash mesh."
293            )
294            self.input_bbox = output_bbox
295
296        if (
297            sorted(self.input_bbox) == sorted(output_bbox)
298            and input_data.shape[0:2] == output_shape
299        ):
300            return input_data
301
302        transform = from_origin(
303            self.input_bbox["left"],
304            self.input_bbox["top"],
305            self.input_res[0],
306            self.input_res[1],
307        )
308
309        with MemoryFile() as memfile:
310            with memfile.open(
311                driver="GTiff",
312                height=height,
313                width=width,
314                count=self.input_ntimestep,
315                dtype=input_data.dtype,
316                transform=transform,
317                crs=input_crs,
318            ) as dataset:
319                print("</> Writing input data matrix in memory")
320                for t in tqdm(range(self.input_ntimestep)):
321                    dataset.write(input_data[:, :, t], t + 1)
322
323                new_width = int(
324                    (output_bbox["right"] - output_bbox["left"]) / output_res[0]
325                )
326                new_height = int(
327                    (output_bbox["top"] - output_bbox["bottom"]) / output_res[1]
328                )
329                new_transform = from_origin(
330                    output_bbox["left"], output_bbox["top"], output_res[0], output_res[1]
331                )
332
333                # # Créer un tableau cible pour les données re-projetées
334                new_array = np.empty(
335                    (self.input_ntimestep, new_height, new_width), dtype=np.float32
336                )
337
338                print("</>Reproject and resample input data matrix.")
339                for t in tqdm(range(self.input_ntimestep)):
340                    reproject(
341                        source=rasterio.band(dataset, t + 1),
342                        destination=new_array[t],
343                        src_transform=transform,
344                        src_crs=input_crs,
345                        dst_transform=new_transform,
346                        dst_crs=output_crs,
347                        resampling=Resampling.nearest,
348                    )
349
350        return np.transpose(new_array, axes=(1, 2, 0))
351        # self.smash_prcp=np.transpose(new_array,axes=(1,2,0))
352
353    def read_input_data2(
354        self,
355        input_data: np.ndarray | None,
356        output_bbox: dict = None,
357        output_epsg: int = 2154,
358        output_res: tuple | list = (1000.0, 1000.0),
359        output_shape: tuple | list = (-99, -99),
360    ):
361        """
362        Read, crop and resample an input matrix. Use Rasterio MemoryFile and
363         Rasterio read function for resampling.
364        :param input_data: Input data as a matrix np.ndarray with shape (nbx,nby,nbts)
365        :type input_data: np.ndarray | None
366        :param output_bbox: The output bounding box of the matrix, defaults to None
367        :type output_bbox: dict, optional
368        :param output_epsg: The output EPSG code of the coordinates, defaults to 2154
369        :type output_epsg: int, optional
370        :param output_res: The output resolution of the atmos data matrix,
371        defaults to (1000.0, 1000.0)
372        :type output_res: tuple | list, optional
373        :param output_shape: The ouput shape of the atmosdata matrix.
374        If output_shape and output_bbox are provided and if the input atmos_data matrix
375         matches these characteritics, the resampling is skipped (much faster).
376          Defaults to (-99, -99)
377        :type output_shape: tuple | list, optional
378        :return: Array with shape ans resolution corresponding to the Smash model
379        :rtype: np.ndarray
380
381        """
382
383        if input_data is None:
384            raise ValueError(
385                "</> self.input_data is None. Use grafas.grafas(input_data=np.array()) or"
386                " sb.model.graffas_connector(input_data=np.array()) "
387                "to upload precipitation."
388            )
389
390        height, width, ntimestep = input_data.shape
391        crs = f"EPSG:{self.input_epsg}"
392
393        if self.input_bbox is None:
394            print(
395                "</> Warning: no bbox or crs was provided with the graffas"
396                " prcp. We suppose the domain of the Graffas prcp equal to"
397                " the domain of the Smash mesh."
398            )
399            self.input_bbox = output_bbox
400
401        if (
402            sorted(self.input_bbox) == sorted(output_bbox)
403            and input_data.shape[0:2] == output_shape
404        ):
405            return input_data
406
407        transform = from_origin(
408            self.input_bbox["left"],
409            self.input_bbox["top"],
410            self.input_res[0],
411            self.input_res[1],
412        )
413
414        with MemoryFile() as memfile:
415            with memfile.open(
416                driver="GTiff",
417                height=height,
418                width=width,
419                count=self.input_ntimestep,
420                dtype=input_data.dtype,
421                transform=transform,
422                crs=crs,
423            ) as dataset:
424                for t in tqdm(range(self.input_ntimestep)):
425                    dataset.write(input_data[:, :, t], t + 1)
426
427                x_scale_factor = dataset.res[0] / output_res[0]
428                y_scale_factor = dataset.res[1] / output_res[1]
429
430                # resampling first to avoid spatial shifting of the parameters
431                prcp_resampled = dataset.read(
432                    out_shape=(
433                        dataset.count,
434                        int(dataset.height * y_scale_factor),
435                        int(dataset.width * x_scale_factor),
436                    ),
437                    resampling=Resampling.nearest,
438                )
439
440        # scale image transform
441        scaled_transform = dataset.transform * dataset.transform.scale(
442            (dataset.width / prcp_resampled.shape[-1]),
443            (dataset.height / prcp_resampled.shape[-2]),
444        )
445
446        # Get a window that corresponds to the smaller raster's bounds
447        window = from_bounds(**output_bbox, transform=scaled_transform)
448
449        prcp_cropped = np.transpose(
450            prcp_resampled[
451                :,
452                int(window.row_off) : int(window.row_off + window.height),
453                int(window.col_off) : int(window.col_off + window.width),
454            ],
455            axes=(1, 2, 0),
456        )
457        return prcp_cropped
458
459    def read_input_data3(
460        self,
461        input_data: np.ndarray | None,
462        output_bbox: dict = None,
463        output_epsg: int = 2154,
464        output_res: tuple | list = (1000.0, 1000.0),
465        output_shape: tuple | list = (-99, -99),
466    ):
467        """
468        Read, crop and resample an input matrix. Use home made functions to crop he array.
469        Use scipy zoom method for resampling.
470        :param input_data: Input data as a matrix np.ndarray with shape (nbx,nby,nbts)
471        :type input_data: np.ndarray | None
472        :param output_bbox: The output bounding box of the matrix, defaults to None
473        :type output_bbox: dict, optional
474        :param output_epsg: The output EPSG code of the coordinates, defaults to 2154
475        :type output_epsg: int, optional
476        :param output_res: The output resolution of the atmos data matrix,
477        defaults to (1000.0, 1000.0)
478        :type output_res: tuple | list, optional
479        :param output_shape: The ouput shape of the atmosdata matrix.
480        If output_shape and output_bbox are provided and if the input atmos_data matrix
481         matches these characteritics, the resampling is skipped (much faster).
482          Defaults to (-99, -99)
483        :type output_shape: tuple | list, optional
484        :return: Array with shape ans resolution corresponding to the Smash model
485        :rtype: np.ndarray
486
487        """
488        if self.input_bbox is None:
489            print(
490                "</> Warning: no bbox or crs was provided with the graffas"
491                " prcp. We suppose the domain of the Graffas prcp equal to the"
492                " domain of the Smash mesh."
493            )
494            self.input_bbox = output_bbox
495
496        if (
497            sorted(self.input_bbox) == sorted(output_bbox)
498            and input_data.shape[0:2] == output_shape
499        ):
500            return input_data
501
502        print("</> use 'home_made_with_scipy_zoom' to crop and resample the input array")
503        # # Créer un tableau cible pour les données re-projetées
504        new_array = np.empty((*output_shape, self.input_ntimestep), dtype=np.float32)
505
506        for t in tqdm(range(self.input_ntimestep)):
507            new_array[:, :, t] = geo_toolbox.crop_array(
508                input_data[:, :, t],
509                bbox_in=self.input_bbox,
510                res_in={"dx": self.input_res[0], "dy": self.input_res[1]},
511                bbox_out=output_bbox,
512                res_out={"dx": output_res[0], "dy": output_res[1]},
513                order=0,
514                cval=-99.0,
515                grid_mode=True,
516            )
517
518        return new_array
class atmos_data_connector:
 24class atmos_data_connector:
 25    """Class atmos_data_connector. This class handle external atmos data (precipitation
 26    and potential evapotranspiration) provider. Precipiation and evapotranspiration
 27    can be provided to Smash as a numpy.ndarray. This class has some functions to read
 28    and crop the array."""
 29
 30    def __init__(
 31        self,
 32        input_prcp: np.ndarray | None = None,
 33        input_pet: np.ndarray | None = None,
 34        input_dt: float = 3600.0,
 35        input_res: tuple | list = (1000.0, 1000.0),
 36        input_start_time: str = "2050-01-01 01:00",
 37        input_bbox: dict | None = None,
 38        input_epsg: int = 2154,
 39    ):
 40        """
 41
 42        :param input_prcp: Matrix of shape (nbx, nby, nbts) of the precipitation,
 43         defaults to None
 44        :type input_prcp: np.ndarray | None, optional
 45        :param input_pet: Matrix of shape (nbx, nby,nbts) of the evapotranspiration,
 46        defaults to None
 47        :type input_pet: np.ndarray | None, optional
 48        :param input_dt: Time step of the input atmos data in seconds, defaults to 3600.0
 49        :type input_dt: float, optional
 50        :param input_res: Resolution of the input data in meters, tuple or list of the
 51         corresponding resolution in x and y direction, defaults to (1000.0, 1000.0)
 52        :type input_res: tuple | list, optional
 53        :param input_start_time: The custom start time of the input atmos data chronicle,
 54         defaults to "2050-01-01 01:00"
 55        :type input_start_time: str, optional
 56        :param input_bbox: The bbox of the input atmos data, defaults to None. if None,
 57        the bbox of the Smash model is used and the left bottom corner of the matrix is
 58        positionned according the smash bbox.
 59        :type input_bbox: dict | None, optional
 60        :param input_epsg: The Epsg code of the input bbox coordinate, defaults to 2154
 61        :type input_epsg: int, optional
 62
 63        """
 64
 65        self.input_prcp = input_prcp
 66        """Input precipiation matrix. np.ndarray of shape (nbx,nby,nbts)."""
 67        self.input_pet = input_pet
 68        """Input evapotranspiration matrix. np.ndarray of shape (nbx,nby,nbts)."""
 69        self.input_res = input_res
 70        """Input resolution of the atmos data matrix. tuple | list of the corresponding
 71        resolution in the x and y direction (meters)"""
 72        self.input_dt = input_dt
 73        """Input time-step in seconds of the atmos data."""
 74        self.input_bbox = input_bbox
 75        """Input bounding box of the atmos data matrix. dictionnary
 76        {'left':, 'right':,'top':,'bottom':}"""
 77        self.input_epsg = input_epsg
 78        """Input epsg code of the corresponding coordinates of the bounding box."""
 79        self.input_start_time = None
 80        """Custom start-time date of the chronicle."""
 81        self.input_ntimestep = None
 82        """Number of time-step of the chronicle."""
 83
 84        self.smash_prcp = None
 85        """Cropped Smash precipitation matrix that will be copied into the Smash model."""
 86        self.smash_pet = None
 87        """Cropped PET matrix that will be copied into the Smash model."""
 88        self.smash_start_time = None
 89        """Futur Smash setup start-time"""
 90        self.smash_end_time = None
 91        """Futur Smash setup end_time"""
 92
 93        if input_prcp is not None:
 94            self.get_ntimestep(input_data=input_prcp)
 95            self.set_setup_datetime(input_dt=input_dt, input_start_time=input_start_time)
 96
 97        if input_pet is not None:
 98            if input_pet.shape != input_prcp.shape:
 99                raise ValueError(
100                    "The shape of input_pet and input_prcp must be the same."
101                )
102
103    def get_ntimestep(self, input_data: np.ndarray | None = None):
104        """
105        Compute the number of time-step of the input atmos data.
106        """
107
108        if input_data is not None:
109
110            if len(input_data.shape) != 3:
111                raise ValueError(
112                    "</> Input atmos data matrix 'input_data' must be an"
113                    "array of shape (nrow, ncol, n_time_step)."
114                )
115
116            self.input_ntimestep = input_data.shape[2]
117
118    def set_setup_datetime(
119        self, input_dt: float = 3600.0, input_start_time: str = "2050-01-01 01:00"
120    ):
121        """
122        Compute the start-time and end-time of the futur Smash simulation.
123
124        :param input_dt: The input time-step of the atmos-data in seconds,
125         defaults to 3600.0
126        :type input_dt: float, optional
127        :param input_start_time: The custom input-start time, defaults to "2050-01-01 01:00"
128        :type input_start_time: str, optional
129
130        """
131
132        if self.input_ntimestep is None:
133            raise ValueError(
134                "</> self.input_ntimestep is None. Use self.get_ntimestep() first."
135            )
136
137        end_time = datetime.datetime.fromisoformat(input_start_time) + datetime.timedelta(
138            seconds=input_dt * self.input_ntimestep
139        )
140
141        self.smash_start_time = input_start_time
142        self.smash_end_time = end_time.strftime("%Y-%m-%d %H:%M")
143
144    def change_setup(self, mysetup):
145        """
146        Change the properties of the setup.mysetup() class.
147        :param mysetup: Class setup.mysetup()
148        :type mysetup: TYPE
149
150        """
151        mysetup.set_setup("start_time", self.smash_start_time)
152        mysetup.set_setup("end_time", self.smash_end_time)
153        mysetup.set_setup("read_qobs", False)
154
155        if self.input_prcp is not None:
156            mysetup.set_setup("read_prcp", False)
157
158        if self.input_pet is not None:
159            mysetup.set_setup("read_pet", False)
160
161    def read_input_atmos_data(
162        self,
163        output_bbox: dict = None,
164        output_epsg: int = 2154,
165        output_res: tuple | list = (1000.0, 1000.0),
166        output_shape: tuple | list = (-99, -99),
167        resampling_method: str = "home_made_with_scipy_zoom",
168    ):
169        """
170        Read, crop and resample input atmos_data if required. Three methods are provided
171        with options resampling_method.
172        :param output_bbox: The output bounding box of the matrix, defaults to None
173        :type output_bbox: dict, optional
174        :param output_epsg: The output EPSG code of the coordinates, defaults to 2154
175        :type output_epsg: int, optional
176        :param output_res: The output resolution of the atmos data matrix,
177        defaults to (1000.0, 1000.0)
178        :type output_res: tuple | list, optional
179        :param output_shape: The ouput shape of the atmosdata matrix.
180        If output_shape and output_bbox are provided and if the input atmos_data matrix
181         matches these characteritics, the resampling is skipped (much faster).
182          Defaults to (-99, -99)
183        :type output_shape: tuple | list, optional
184        :param resampling_method: The resampling method to use, choice are rasterio_1,
185        rasterio_2 and , home_made_with_scipy_zoom. Defaults to "home_made_with_scipy_zoom"
186        :type resampling_method: str, optional
187
188        """
189
190        if resampling_method == "rasterio_1":
191            if self.input_prcp is not None:
192                array = self.read_input_data(
193                    self.input_prcp,
194                    output_bbox=output_bbox,
195                    output_epsg=output_epsg,
196                    output_res=output_res,
197                    output_shape=output_shape,
198                )
199                self.smash_prcp = array
200
201            if self.input_pet is not None:
202                array = self.read_input_data(
203                    self.input_pet,
204                    output_bbox=output_bbox,
205                    output_epsg=output_epsg,
206                    output_res=output_res,
207                    output_shape=output_shape,
208                )
209                self.smash_pet = array
210
211        elif resampling_method == "rasterio_2":
212            if self.input_prcp is not None:
213                array = self.read_input_data2(
214                    self.input_prcp,
215                    output_bbox=output_bbox,
216                    output_epsg=output_epsg,
217                    output_res=output_res,
218                    output_shape=output_shape,
219                )
220
221                self.smash_prcp = array
222
223            if self.input_pet is not None:
224                array = self.read_input_data2(
225                    self.input_pet,
226                    output_bbox=output_bbox,
227                    output_epsg=output_epsg,
228                    output_res=output_res,
229                    output_shape=output_shape,
230                )
231                self.smash_pet = array
232
233        elif resampling_method == "home_made_with_scipy_zoom":
234            if self.input_prcp is not None:
235                array = self.read_input_data3(
236                    self.input_prcp,
237                    output_bbox=output_bbox,
238                    output_epsg=output_epsg,
239                    output_res=output_res,
240                    output_shape=output_shape,
241                )
242                self.smash_prcp = array
243
244            if self.input_pet is not None:
245                array = self.read_input_data3(
246                    self.input_pet,
247                    output_bbox=output_bbox,
248                    output_epsg=output_epsg,
249                    output_res=output_res,
250                    output_shape=output_shape,
251                )
252                self.smash_pet = array
253
254    def read_input_data(
255        self,
256        input_data: np.ndarray | None,
257        output_bbox: dict = None,
258        output_epsg: int = 2154,
259        output_res: tuple | list = (1000.0, 1000.0),
260        output_shape: tuple | list = (-99, -99),
261    ):
262        """
263        Read, crop and resample an input matrix. Use Rasterio MemoryFile and
264         Rasterio reproject function. Slowest method.
265        :param input_data: Input data as a matrix np.ndarray with shape (nbx,nby,nbts)
266        :type input_data: np.ndarray | None
267        :param output_bbox: The output bounding box of the matrix, defaults to None
268        :type output_bbox: dict, optional
269        :param output_epsg: The output EPSG code of the coordinates, defaults to 2154
270        :type output_epsg: int, optional
271        :param output_res: The output resolution of the atmos data matrix,
272        defaults to (1000.0, 1000.0)
273        :type output_res: tuple | list, optional
274        :param output_shape: The ouput shape of the atmosdata matrix.
275        If output_shape and output_bbox are provided and if the input atmos_data matrix
276         matches these characteritics, the resampling is skipped (much faster).
277          Defaults to (-99, -99)
278        :type output_shape: tuple | list, optional
279        :return: Array with shape ans resolution corresponding to the Smash model
280        :rtype: np.ndarray
281
282        """
283
284        height, width, ntimestep = input_data.shape
285        input_crs = f"EPSG:{self.input_epsg}"
286        output_crs = f"EPSG:{output_epsg}"
287
288        # Créer le transform (origine en haut à gauche, donc top et left)
289        if self.input_bbox is None:
290            print(
291                "</> Warning: no bbox or crs was provided with the graffas"
292                " prcp. We suppose the domain of the Graffas prcp equal to the"
293                " domain of the Smash mesh."
294            )
295            self.input_bbox = output_bbox
296
297        if (
298            sorted(self.input_bbox) == sorted(output_bbox)
299            and input_data.shape[0:2] == output_shape
300        ):
301            return input_data
302
303        transform = from_origin(
304            self.input_bbox["left"],
305            self.input_bbox["top"],
306            self.input_res[0],
307            self.input_res[1],
308        )
309
310        with MemoryFile() as memfile:
311            with memfile.open(
312                driver="GTiff",
313                height=height,
314                width=width,
315                count=self.input_ntimestep,
316                dtype=input_data.dtype,
317                transform=transform,
318                crs=input_crs,
319            ) as dataset:
320                print("</> Writing input data matrix in memory")
321                for t in tqdm(range(self.input_ntimestep)):
322                    dataset.write(input_data[:, :, t], t + 1)
323
324                new_width = int(
325                    (output_bbox["right"] - output_bbox["left"]) / output_res[0]
326                )
327                new_height = int(
328                    (output_bbox["top"] - output_bbox["bottom"]) / output_res[1]
329                )
330                new_transform = from_origin(
331                    output_bbox["left"], output_bbox["top"], output_res[0], output_res[1]
332                )
333
334                # # Créer un tableau cible pour les données re-projetées
335                new_array = np.empty(
336                    (self.input_ntimestep, new_height, new_width), dtype=np.float32
337                )
338
339                print("</>Reproject and resample input data matrix.")
340                for t in tqdm(range(self.input_ntimestep)):
341                    reproject(
342                        source=rasterio.band(dataset, t + 1),
343                        destination=new_array[t],
344                        src_transform=transform,
345                        src_crs=input_crs,
346                        dst_transform=new_transform,
347                        dst_crs=output_crs,
348                        resampling=Resampling.nearest,
349                    )
350
351        return np.transpose(new_array, axes=(1, 2, 0))
352        # self.smash_prcp=np.transpose(new_array,axes=(1,2,0))
353
354    def read_input_data2(
355        self,
356        input_data: np.ndarray | None,
357        output_bbox: dict = None,
358        output_epsg: int = 2154,
359        output_res: tuple | list = (1000.0, 1000.0),
360        output_shape: tuple | list = (-99, -99),
361    ):
362        """
363        Read, crop and resample an input matrix. Use Rasterio MemoryFile and
364         Rasterio read function for resampling.
365        :param input_data: Input data as a matrix np.ndarray with shape (nbx,nby,nbts)
366        :type input_data: np.ndarray | None
367        :param output_bbox: The output bounding box of the matrix, defaults to None
368        :type output_bbox: dict, optional
369        :param output_epsg: The output EPSG code of the coordinates, defaults to 2154
370        :type output_epsg: int, optional
371        :param output_res: The output resolution of the atmos data matrix,
372        defaults to (1000.0, 1000.0)
373        :type output_res: tuple | list, optional
374        :param output_shape: The ouput shape of the atmosdata matrix.
375        If output_shape and output_bbox are provided and if the input atmos_data matrix
376         matches these characteritics, the resampling is skipped (much faster).
377          Defaults to (-99, -99)
378        :type output_shape: tuple | list, optional
379        :return: Array with shape ans resolution corresponding to the Smash model
380        :rtype: np.ndarray
381
382        """
383
384        if input_data is None:
385            raise ValueError(
386                "</> self.input_data is None. Use grafas.grafas(input_data=np.array()) or"
387                " sb.model.graffas_connector(input_data=np.array()) "
388                "to upload precipitation."
389            )
390
391        height, width, ntimestep = input_data.shape
392        crs = f"EPSG:{self.input_epsg}"
393
394        if self.input_bbox is None:
395            print(
396                "</> Warning: no bbox or crs was provided with the graffas"
397                " prcp. We suppose the domain of the Graffas prcp equal to"
398                " the domain of the Smash mesh."
399            )
400            self.input_bbox = output_bbox
401
402        if (
403            sorted(self.input_bbox) == sorted(output_bbox)
404            and input_data.shape[0:2] == output_shape
405        ):
406            return input_data
407
408        transform = from_origin(
409            self.input_bbox["left"],
410            self.input_bbox["top"],
411            self.input_res[0],
412            self.input_res[1],
413        )
414
415        with MemoryFile() as memfile:
416            with memfile.open(
417                driver="GTiff",
418                height=height,
419                width=width,
420                count=self.input_ntimestep,
421                dtype=input_data.dtype,
422                transform=transform,
423                crs=crs,
424            ) as dataset:
425                for t in tqdm(range(self.input_ntimestep)):
426                    dataset.write(input_data[:, :, t], t + 1)
427
428                x_scale_factor = dataset.res[0] / output_res[0]
429                y_scale_factor = dataset.res[1] / output_res[1]
430
431                # resampling first to avoid spatial shifting of the parameters
432                prcp_resampled = dataset.read(
433                    out_shape=(
434                        dataset.count,
435                        int(dataset.height * y_scale_factor),
436                        int(dataset.width * x_scale_factor),
437                    ),
438                    resampling=Resampling.nearest,
439                )
440
441        # scale image transform
442        scaled_transform = dataset.transform * dataset.transform.scale(
443            (dataset.width / prcp_resampled.shape[-1]),
444            (dataset.height / prcp_resampled.shape[-2]),
445        )
446
447        # Get a window that corresponds to the smaller raster's bounds
448        window = from_bounds(**output_bbox, transform=scaled_transform)
449
450        prcp_cropped = np.transpose(
451            prcp_resampled[
452                :,
453                int(window.row_off) : int(window.row_off + window.height),
454                int(window.col_off) : int(window.col_off + window.width),
455            ],
456            axes=(1, 2, 0),
457        )
458        return prcp_cropped
459
460    def read_input_data3(
461        self,
462        input_data: np.ndarray | None,
463        output_bbox: dict = None,
464        output_epsg: int = 2154,
465        output_res: tuple | list = (1000.0, 1000.0),
466        output_shape: tuple | list = (-99, -99),
467    ):
468        """
469        Read, crop and resample an input matrix. Use home made functions to crop he array.
470        Use scipy zoom method for resampling.
471        :param input_data: Input data as a matrix np.ndarray with shape (nbx,nby,nbts)
472        :type input_data: np.ndarray | None
473        :param output_bbox: The output bounding box of the matrix, defaults to None
474        :type output_bbox: dict, optional
475        :param output_epsg: The output EPSG code of the coordinates, defaults to 2154
476        :type output_epsg: int, optional
477        :param output_res: The output resolution of the atmos data matrix,
478        defaults to (1000.0, 1000.0)
479        :type output_res: tuple | list, optional
480        :param output_shape: The ouput shape of the atmosdata matrix.
481        If output_shape and output_bbox are provided and if the input atmos_data matrix
482         matches these characteritics, the resampling is skipped (much faster).
483          Defaults to (-99, -99)
484        :type output_shape: tuple | list, optional
485        :return: Array with shape ans resolution corresponding to the Smash model
486        :rtype: np.ndarray
487
488        """
489        if self.input_bbox is None:
490            print(
491                "</> Warning: no bbox or crs was provided with the graffas"
492                " prcp. We suppose the domain of the Graffas prcp equal to the"
493                " domain of the Smash mesh."
494            )
495            self.input_bbox = output_bbox
496
497        if (
498            sorted(self.input_bbox) == sorted(output_bbox)
499            and input_data.shape[0:2] == output_shape
500        ):
501            return input_data
502
503        print("</> use 'home_made_with_scipy_zoom' to crop and resample the input array")
504        # # Créer un tableau cible pour les données re-projetées
505        new_array = np.empty((*output_shape, self.input_ntimestep), dtype=np.float32)
506
507        for t in tqdm(range(self.input_ntimestep)):
508            new_array[:, :, t] = geo_toolbox.crop_array(
509                input_data[:, :, t],
510                bbox_in=self.input_bbox,
511                res_in={"dx": self.input_res[0], "dy": self.input_res[1]},
512                bbox_out=output_bbox,
513                res_out={"dx": output_res[0], "dy": output_res[1]},
514                order=0,
515                cval=-99.0,
516                grid_mode=True,
517            )
518
519        return new_array

Class atmos_data_connector. This class handle external atmos data (precipitation and potential evapotranspiration) provider. Precipiation and evapotranspiration can be provided to Smash as a numpy.ndarray. This class has some functions to read and crop the array.

atmos_data_connector( input_prcp: numpy.ndarray | None = None, input_pet: numpy.ndarray | None = None, input_dt: float = 3600.0, input_res: tuple | list = (1000.0, 1000.0), input_start_time: str = '2050-01-01 01:00', input_bbox: dict | None = None, input_epsg: int = 2154)
 30    def __init__(
 31        self,
 32        input_prcp: np.ndarray | None = None,
 33        input_pet: np.ndarray | None = None,
 34        input_dt: float = 3600.0,
 35        input_res: tuple | list = (1000.0, 1000.0),
 36        input_start_time: str = "2050-01-01 01:00",
 37        input_bbox: dict | None = None,
 38        input_epsg: int = 2154,
 39    ):
 40        """
 41
 42        :param input_prcp: Matrix of shape (nbx, nby, nbts) of the precipitation,
 43         defaults to None
 44        :type input_prcp: np.ndarray | None, optional
 45        :param input_pet: Matrix of shape (nbx, nby,nbts) of the evapotranspiration,
 46        defaults to None
 47        :type input_pet: np.ndarray | None, optional
 48        :param input_dt: Time step of the input atmos data in seconds, defaults to 3600.0
 49        :type input_dt: float, optional
 50        :param input_res: Resolution of the input data in meters, tuple or list of the
 51         corresponding resolution in x and y direction, defaults to (1000.0, 1000.0)
 52        :type input_res: tuple | list, optional
 53        :param input_start_time: The custom start time of the input atmos data chronicle,
 54         defaults to "2050-01-01 01:00"
 55        :type input_start_time: str, optional
 56        :param input_bbox: The bbox of the input atmos data, defaults to None. if None,
 57        the bbox of the Smash model is used and the left bottom corner of the matrix is
 58        positionned according the smash bbox.
 59        :type input_bbox: dict | None, optional
 60        :param input_epsg: The Epsg code of the input bbox coordinate, defaults to 2154
 61        :type input_epsg: int, optional
 62
 63        """
 64
 65        self.input_prcp = input_prcp
 66        """Input precipiation matrix. np.ndarray of shape (nbx,nby,nbts)."""
 67        self.input_pet = input_pet
 68        """Input evapotranspiration matrix. np.ndarray of shape (nbx,nby,nbts)."""
 69        self.input_res = input_res
 70        """Input resolution of the atmos data matrix. tuple | list of the corresponding
 71        resolution in the x and y direction (meters)"""
 72        self.input_dt = input_dt
 73        """Input time-step in seconds of the atmos data."""
 74        self.input_bbox = input_bbox
 75        """Input bounding box of the atmos data matrix. dictionnary
 76        {'left':, 'right':,'top':,'bottom':}"""
 77        self.input_epsg = input_epsg
 78        """Input epsg code of the corresponding coordinates of the bounding box."""
 79        self.input_start_time = None
 80        """Custom start-time date of the chronicle."""
 81        self.input_ntimestep = None
 82        """Number of time-step of the chronicle."""
 83
 84        self.smash_prcp = None
 85        """Cropped Smash precipitation matrix that will be copied into the Smash model."""
 86        self.smash_pet = None
 87        """Cropped PET matrix that will be copied into the Smash model."""
 88        self.smash_start_time = None
 89        """Futur Smash setup start-time"""
 90        self.smash_end_time = None
 91        """Futur Smash setup end_time"""
 92
 93        if input_prcp is not None:
 94            self.get_ntimestep(input_data=input_prcp)
 95            self.set_setup_datetime(input_dt=input_dt, input_start_time=input_start_time)
 96
 97        if input_pet is not None:
 98            if input_pet.shape != input_prcp.shape:
 99                raise ValueError(
100                    "The shape of input_pet and input_prcp must be the same."
101                )
Parameters
  • input_prcp: Matrix of shape (nbx, nby, nbts) of the precipitation, defaults to None
  • input_pet: Matrix of shape (nbx, nby,nbts) of the evapotranspiration, defaults to None
  • input_dt: Time step of the input atmos data in seconds, defaults to 3600.0
  • input_res: Resolution of the input data in meters, tuple or list of the corresponding resolution in x and y direction, defaults to (1000.0, 1000.0)
  • input_start_time: The custom start time of the input atmos data chronicle, defaults to "2050-01-01 01:00"
  • input_bbox: The bbox of the input atmos data, defaults to None. if None, the bbox of the Smash model is used and the left bottom corner of the matrix is positionned according the smash bbox.
  • input_epsg: The Epsg code of the input bbox coordinate, defaults to 2154
input_prcp

Input precipiation matrix. np.ndarray of shape (nbx,nby,nbts).

input_pet

Input evapotranspiration matrix. np.ndarray of shape (nbx,nby,nbts).

input_res

Input resolution of the atmos data matrix. tuple | list of the corresponding resolution in the x and y direction (meters)

input_dt

Input time-step in seconds of the atmos data.

input_bbox

Input bounding box of the atmos data matrix. dictionnary {'left':, 'right':,'top':,'bottom':}

input_epsg

Input epsg code of the corresponding coordinates of the bounding box.

input_start_time

Custom start-time date of the chronicle.

input_ntimestep

Number of time-step of the chronicle.

smash_prcp

Cropped Smash precipitation matrix that will be copied into the Smash model.

smash_pet

Cropped PET matrix that will be copied into the Smash model.

smash_start_time

Futur Smash setup start-time

smash_end_time

Futur Smash setup end_time

def get_ntimestep(self, input_data: numpy.ndarray | None = None):
103    def get_ntimestep(self, input_data: np.ndarray | None = None):
104        """
105        Compute the number of time-step of the input atmos data.
106        """
107
108        if input_data is not None:
109
110            if len(input_data.shape) != 3:
111                raise ValueError(
112                    "</> Input atmos data matrix 'input_data' must be an"
113                    "array of shape (nrow, ncol, n_time_step)."
114                )
115
116            self.input_ntimestep = input_data.shape[2]

Compute the number of time-step of the input atmos data.

def set_setup_datetime( self, input_dt: float = 3600.0, input_start_time: str = '2050-01-01 01:00'):
118    def set_setup_datetime(
119        self, input_dt: float = 3600.0, input_start_time: str = "2050-01-01 01:00"
120    ):
121        """
122        Compute the start-time and end-time of the futur Smash simulation.
123
124        :param input_dt: The input time-step of the atmos-data in seconds,
125         defaults to 3600.0
126        :type input_dt: float, optional
127        :param input_start_time: The custom input-start time, defaults to "2050-01-01 01:00"
128        :type input_start_time: str, optional
129
130        """
131
132        if self.input_ntimestep is None:
133            raise ValueError(
134                "</> self.input_ntimestep is None. Use self.get_ntimestep() first."
135            )
136
137        end_time = datetime.datetime.fromisoformat(input_start_time) + datetime.timedelta(
138            seconds=input_dt * self.input_ntimestep
139        )
140
141        self.smash_start_time = input_start_time
142        self.smash_end_time = end_time.strftime("%Y-%m-%d %H:%M")

Compute the start-time and end-time of the futur Smash simulation.

Parameters
  • input_dt: The input time-step of the atmos-data in seconds, defaults to 3600.0
  • input_start_time: The custom input-start time, defaults to "2050-01-01 01: 00"
def change_setup(self, mysetup):
144    def change_setup(self, mysetup):
145        """
146        Change the properties of the setup.mysetup() class.
147        :param mysetup: Class setup.mysetup()
148        :type mysetup: TYPE
149
150        """
151        mysetup.set_setup("start_time", self.smash_start_time)
152        mysetup.set_setup("end_time", self.smash_end_time)
153        mysetup.set_setup("read_qobs", False)
154
155        if self.input_prcp is not None:
156            mysetup.set_setup("read_prcp", False)
157
158        if self.input_pet is not None:
159            mysetup.set_setup("read_pet", False)

Change the properties of the setup.mysetup() class.

Parameters
  • mysetup: Class setup.mysetup()
def read_input_atmos_data( self, output_bbox: dict = None, output_epsg: int = 2154, output_res: tuple | list = (1000.0, 1000.0), output_shape: tuple | list = (-99, -99), resampling_method: str = 'home_made_with_scipy_zoom'):
161    def read_input_atmos_data(
162        self,
163        output_bbox: dict = None,
164        output_epsg: int = 2154,
165        output_res: tuple | list = (1000.0, 1000.0),
166        output_shape: tuple | list = (-99, -99),
167        resampling_method: str = "home_made_with_scipy_zoom",
168    ):
169        """
170        Read, crop and resample input atmos_data if required. Three methods are provided
171        with options resampling_method.
172        :param output_bbox: The output bounding box of the matrix, defaults to None
173        :type output_bbox: dict, optional
174        :param output_epsg: The output EPSG code of the coordinates, defaults to 2154
175        :type output_epsg: int, optional
176        :param output_res: The output resolution of the atmos data matrix,
177        defaults to (1000.0, 1000.0)
178        :type output_res: tuple | list, optional
179        :param output_shape: The ouput shape of the atmosdata matrix.
180        If output_shape and output_bbox are provided and if the input atmos_data matrix
181         matches these characteritics, the resampling is skipped (much faster).
182          Defaults to (-99, -99)
183        :type output_shape: tuple | list, optional
184        :param resampling_method: The resampling method to use, choice are rasterio_1,
185        rasterio_2 and , home_made_with_scipy_zoom. Defaults to "home_made_with_scipy_zoom"
186        :type resampling_method: str, optional
187
188        """
189
190        if resampling_method == "rasterio_1":
191            if self.input_prcp is not None:
192                array = self.read_input_data(
193                    self.input_prcp,
194                    output_bbox=output_bbox,
195                    output_epsg=output_epsg,
196                    output_res=output_res,
197                    output_shape=output_shape,
198                )
199                self.smash_prcp = array
200
201            if self.input_pet is not None:
202                array = self.read_input_data(
203                    self.input_pet,
204                    output_bbox=output_bbox,
205                    output_epsg=output_epsg,
206                    output_res=output_res,
207                    output_shape=output_shape,
208                )
209                self.smash_pet = array
210
211        elif resampling_method == "rasterio_2":
212            if self.input_prcp is not None:
213                array = self.read_input_data2(
214                    self.input_prcp,
215                    output_bbox=output_bbox,
216                    output_epsg=output_epsg,
217                    output_res=output_res,
218                    output_shape=output_shape,
219                )
220
221                self.smash_prcp = array
222
223            if self.input_pet is not None:
224                array = self.read_input_data2(
225                    self.input_pet,
226                    output_bbox=output_bbox,
227                    output_epsg=output_epsg,
228                    output_res=output_res,
229                    output_shape=output_shape,
230                )
231                self.smash_pet = array
232
233        elif resampling_method == "home_made_with_scipy_zoom":
234            if self.input_prcp is not None:
235                array = self.read_input_data3(
236                    self.input_prcp,
237                    output_bbox=output_bbox,
238                    output_epsg=output_epsg,
239                    output_res=output_res,
240                    output_shape=output_shape,
241                )
242                self.smash_prcp = array
243
244            if self.input_pet is not None:
245                array = self.read_input_data3(
246                    self.input_pet,
247                    output_bbox=output_bbox,
248                    output_epsg=output_epsg,
249                    output_res=output_res,
250                    output_shape=output_shape,
251                )
252                self.smash_pet = array

Read, crop and resample input atmos_data if required. Three methods are provided with options resampling_method.

Parameters
  • output_bbox: The output bounding box of the matrix, defaults to None
  • output_epsg: The output EPSG code of the coordinates, defaults to 2154
  • output_res: The output resolution of the atmos data matrix, defaults to (1000.0, 1000.0)
  • output_shape: The ouput shape of the atmosdata matrix. If output_shape and output_bbox are provided and if the input atmos_data matrix matches these characteritics, the resampling is skipped (much faster). Defaults to (-99, -99)
  • resampling_method: The resampling method to use, choice are rasterio_1, rasterio_2 and , home_made_with_scipy_zoom. Defaults to "home_made_with_scipy_zoom"
def read_input_data( self, input_data: numpy.ndarray | None, output_bbox: dict = None, output_epsg: int = 2154, output_res: tuple | list = (1000.0, 1000.0), output_shape: tuple | list = (-99, -99)):
254    def read_input_data(
255        self,
256        input_data: np.ndarray | None,
257        output_bbox: dict = None,
258        output_epsg: int = 2154,
259        output_res: tuple | list = (1000.0, 1000.0),
260        output_shape: tuple | list = (-99, -99),
261    ):
262        """
263        Read, crop and resample an input matrix. Use Rasterio MemoryFile and
264         Rasterio reproject function. Slowest method.
265        :param input_data: Input data as a matrix np.ndarray with shape (nbx,nby,nbts)
266        :type input_data: np.ndarray | None
267        :param output_bbox: The output bounding box of the matrix, defaults to None
268        :type output_bbox: dict, optional
269        :param output_epsg: The output EPSG code of the coordinates, defaults to 2154
270        :type output_epsg: int, optional
271        :param output_res: The output resolution of the atmos data matrix,
272        defaults to (1000.0, 1000.0)
273        :type output_res: tuple | list, optional
274        :param output_shape: The ouput shape of the atmosdata matrix.
275        If output_shape and output_bbox are provided and if the input atmos_data matrix
276         matches these characteritics, the resampling is skipped (much faster).
277          Defaults to (-99, -99)
278        :type output_shape: tuple | list, optional
279        :return: Array with shape ans resolution corresponding to the Smash model
280        :rtype: np.ndarray
281
282        """
283
284        height, width, ntimestep = input_data.shape
285        input_crs = f"EPSG:{self.input_epsg}"
286        output_crs = f"EPSG:{output_epsg}"
287
288        # Créer le transform (origine en haut à gauche, donc top et left)
289        if self.input_bbox is None:
290            print(
291                "</> Warning: no bbox or crs was provided with the graffas"
292                " prcp. We suppose the domain of the Graffas prcp equal to the"
293                " domain of the Smash mesh."
294            )
295            self.input_bbox = output_bbox
296
297        if (
298            sorted(self.input_bbox) == sorted(output_bbox)
299            and input_data.shape[0:2] == output_shape
300        ):
301            return input_data
302
303        transform = from_origin(
304            self.input_bbox["left"],
305            self.input_bbox["top"],
306            self.input_res[0],
307            self.input_res[1],
308        )
309
310        with MemoryFile() as memfile:
311            with memfile.open(
312                driver="GTiff",
313                height=height,
314                width=width,
315                count=self.input_ntimestep,
316                dtype=input_data.dtype,
317                transform=transform,
318                crs=input_crs,
319            ) as dataset:
320                print("</> Writing input data matrix in memory")
321                for t in tqdm(range(self.input_ntimestep)):
322                    dataset.write(input_data[:, :, t], t + 1)
323
324                new_width = int(
325                    (output_bbox["right"] - output_bbox["left"]) / output_res[0]
326                )
327                new_height = int(
328                    (output_bbox["top"] - output_bbox["bottom"]) / output_res[1]
329                )
330                new_transform = from_origin(
331                    output_bbox["left"], output_bbox["top"], output_res[0], output_res[1]
332                )
333
334                # # Créer un tableau cible pour les données re-projetées
335                new_array = np.empty(
336                    (self.input_ntimestep, new_height, new_width), dtype=np.float32
337                )
338
339                print("</>Reproject and resample input data matrix.")
340                for t in tqdm(range(self.input_ntimestep)):
341                    reproject(
342                        source=rasterio.band(dataset, t + 1),
343                        destination=new_array[t],
344                        src_transform=transform,
345                        src_crs=input_crs,
346                        dst_transform=new_transform,
347                        dst_crs=output_crs,
348                        resampling=Resampling.nearest,
349                    )
350
351        return np.transpose(new_array, axes=(1, 2, 0))
352        # self.smash_prcp=np.transpose(new_array,axes=(1,2,0))

Read, crop and resample an input matrix. Use Rasterio MemoryFile and Rasterio reproject function. Slowest method.

Parameters
  • input_data: Input data as a matrix np.ndarray with shape (nbx,nby,nbts)
  • output_bbox: The output bounding box of the matrix, defaults to None
  • output_epsg: The output EPSG code of the coordinates, defaults to 2154
  • output_res: The output resolution of the atmos data matrix, defaults to (1000.0, 1000.0)
  • output_shape: The ouput shape of the atmosdata matrix. If output_shape and output_bbox are provided and if the input atmos_data matrix matches these characteritics, the resampling is skipped (much faster). Defaults to (-99, -99)
Returns

Array with shape ans resolution corresponding to the Smash model

def read_input_data2( self, input_data: numpy.ndarray | None, output_bbox: dict = None, output_epsg: int = 2154, output_res: tuple | list = (1000.0, 1000.0), output_shape: tuple | list = (-99, -99)):
354    def read_input_data2(
355        self,
356        input_data: np.ndarray | None,
357        output_bbox: dict = None,
358        output_epsg: int = 2154,
359        output_res: tuple | list = (1000.0, 1000.0),
360        output_shape: tuple | list = (-99, -99),
361    ):
362        """
363        Read, crop and resample an input matrix. Use Rasterio MemoryFile and
364         Rasterio read function for resampling.
365        :param input_data: Input data as a matrix np.ndarray with shape (nbx,nby,nbts)
366        :type input_data: np.ndarray | None
367        :param output_bbox: The output bounding box of the matrix, defaults to None
368        :type output_bbox: dict, optional
369        :param output_epsg: The output EPSG code of the coordinates, defaults to 2154
370        :type output_epsg: int, optional
371        :param output_res: The output resolution of the atmos data matrix,
372        defaults to (1000.0, 1000.0)
373        :type output_res: tuple | list, optional
374        :param output_shape: The ouput shape of the atmosdata matrix.
375        If output_shape and output_bbox are provided and if the input atmos_data matrix
376         matches these characteritics, the resampling is skipped (much faster).
377          Defaults to (-99, -99)
378        :type output_shape: tuple | list, optional
379        :return: Array with shape ans resolution corresponding to the Smash model
380        :rtype: np.ndarray
381
382        """
383
384        if input_data is None:
385            raise ValueError(
386                "</> self.input_data is None. Use grafas.grafas(input_data=np.array()) or"
387                " sb.model.graffas_connector(input_data=np.array()) "
388                "to upload precipitation."
389            )
390
391        height, width, ntimestep = input_data.shape
392        crs = f"EPSG:{self.input_epsg}"
393
394        if self.input_bbox is None:
395            print(
396                "</> Warning: no bbox or crs was provided with the graffas"
397                " prcp. We suppose the domain of the Graffas prcp equal to"
398                " the domain of the Smash mesh."
399            )
400            self.input_bbox = output_bbox
401
402        if (
403            sorted(self.input_bbox) == sorted(output_bbox)
404            and input_data.shape[0:2] == output_shape
405        ):
406            return input_data
407
408        transform = from_origin(
409            self.input_bbox["left"],
410            self.input_bbox["top"],
411            self.input_res[0],
412            self.input_res[1],
413        )
414
415        with MemoryFile() as memfile:
416            with memfile.open(
417                driver="GTiff",
418                height=height,
419                width=width,
420                count=self.input_ntimestep,
421                dtype=input_data.dtype,
422                transform=transform,
423                crs=crs,
424            ) as dataset:
425                for t in tqdm(range(self.input_ntimestep)):
426                    dataset.write(input_data[:, :, t], t + 1)
427
428                x_scale_factor = dataset.res[0] / output_res[0]
429                y_scale_factor = dataset.res[1] / output_res[1]
430
431                # resampling first to avoid spatial shifting of the parameters
432                prcp_resampled = dataset.read(
433                    out_shape=(
434                        dataset.count,
435                        int(dataset.height * y_scale_factor),
436                        int(dataset.width * x_scale_factor),
437                    ),
438                    resampling=Resampling.nearest,
439                )
440
441        # scale image transform
442        scaled_transform = dataset.transform * dataset.transform.scale(
443            (dataset.width / prcp_resampled.shape[-1]),
444            (dataset.height / prcp_resampled.shape[-2]),
445        )
446
447        # Get a window that corresponds to the smaller raster's bounds
448        window = from_bounds(**output_bbox, transform=scaled_transform)
449
450        prcp_cropped = np.transpose(
451            prcp_resampled[
452                :,
453                int(window.row_off) : int(window.row_off + window.height),
454                int(window.col_off) : int(window.col_off + window.width),
455            ],
456            axes=(1, 2, 0),
457        )
458        return prcp_cropped

Read, crop and resample an input matrix. Use Rasterio MemoryFile and Rasterio read function for resampling.

Parameters
  • input_data: Input data as a matrix np.ndarray with shape (nbx,nby,nbts)
  • output_bbox: The output bounding box of the matrix, defaults to None
  • output_epsg: The output EPSG code of the coordinates, defaults to 2154
  • output_res: The output resolution of the atmos data matrix, defaults to (1000.0, 1000.0)
  • output_shape: The ouput shape of the atmosdata matrix. If output_shape and output_bbox are provided and if the input atmos_data matrix matches these characteritics, the resampling is skipped (much faster). Defaults to (-99, -99)
Returns

Array with shape ans resolution corresponding to the Smash model

def read_input_data3( self, input_data: numpy.ndarray | None, output_bbox: dict = None, output_epsg: int = 2154, output_res: tuple | list = (1000.0, 1000.0), output_shape: tuple | list = (-99, -99)):
460    def read_input_data3(
461        self,
462        input_data: np.ndarray | None,
463        output_bbox: dict = None,
464        output_epsg: int = 2154,
465        output_res: tuple | list = (1000.0, 1000.0),
466        output_shape: tuple | list = (-99, -99),
467    ):
468        """
469        Read, crop and resample an input matrix. Use home made functions to crop he array.
470        Use scipy zoom method for resampling.
471        :param input_data: Input data as a matrix np.ndarray with shape (nbx,nby,nbts)
472        :type input_data: np.ndarray | None
473        :param output_bbox: The output bounding box of the matrix, defaults to None
474        :type output_bbox: dict, optional
475        :param output_epsg: The output EPSG code of the coordinates, defaults to 2154
476        :type output_epsg: int, optional
477        :param output_res: The output resolution of the atmos data matrix,
478        defaults to (1000.0, 1000.0)
479        :type output_res: tuple | list, optional
480        :param output_shape: The ouput shape of the atmosdata matrix.
481        If output_shape and output_bbox are provided and if the input atmos_data matrix
482         matches these characteritics, the resampling is skipped (much faster).
483          Defaults to (-99, -99)
484        :type output_shape: tuple | list, optional
485        :return: Array with shape ans resolution corresponding to the Smash model
486        :rtype: np.ndarray
487
488        """
489        if self.input_bbox is None:
490            print(
491                "</> Warning: no bbox or crs was provided with the graffas"
492                " prcp. We suppose the domain of the Graffas prcp equal to the"
493                " domain of the Smash mesh."
494            )
495            self.input_bbox = output_bbox
496
497        if (
498            sorted(self.input_bbox) == sorted(output_bbox)
499            and input_data.shape[0:2] == output_shape
500        ):
501            return input_data
502
503        print("</> use 'home_made_with_scipy_zoom' to crop and resample the input array")
504        # # Créer un tableau cible pour les données re-projetées
505        new_array = np.empty((*output_shape, self.input_ntimestep), dtype=np.float32)
506
507        for t in tqdm(range(self.input_ntimestep)):
508            new_array[:, :, t] = geo_toolbox.crop_array(
509                input_data[:, :, t],
510                bbox_in=self.input_bbox,
511                res_in={"dx": self.input_res[0], "dy": self.input_res[1]},
512                bbox_out=output_bbox,
513                res_out={"dx": output_res[0], "dy": output_res[1]},
514                order=0,
515                cval=-99.0,
516                grid_mode=True,
517            )
518
519        return new_array

Read, crop and resample an input matrix. Use home made functions to crop he array. Use scipy zoom method for resampling.

Parameters
  • input_data: Input data as a matrix np.ndarray with shape (nbx,nby,nbts)
  • output_bbox: The output bounding box of the matrix, defaults to None
  • output_epsg: The output EPSG code of the coordinates, defaults to 2154
  • output_res: The output resolution of the atmos data matrix, defaults to (1000.0, 1000.0)
  • output_shape: The ouput shape of the atmosdata matrix. If output_shape and output_bbox are provided and if the input atmos_data matrix matches these characteritics, the resampling is skipped (much faster). Defaults to (-99, -99)
Returns

Array with shape ans resolution corresponding to the Smash model