Line data Source code
1 : !> \file mo_read_spatial_data.f90
2 : !> \brief \copybrief mo_read_spatial_data
3 : !> \details \copydetails mo_read_spatial_data
4 :
5 : !> \brief Reads spatial input data.
6 : !> \details This module is to read spatial input data, e.g. dem, aspect, flow direction.
7 : !! The module provides a subroutine for ASCII files.
8 : !! (Subroutine for NetCDF files will come with release 5.1).
9 : !! The data are read from the specified directory.
10 : !> \authors Juliane Mai
11 : !> \date Dec 2012
12 : !> \copyright Copyright 2005-\today, the mHM Developers, Luis Samaniego, Sabine Attinger: All rights reserved.
13 : !! mHM is released under the LGPLv3+ license \license_note
14 : !> \ingroup f_common
15 : MODULE mo_read_spatial_data
16 :
17 : ! This module provides routines to read spatial data.
18 :
19 : ! Written Juliane Mai, Jan 2013
20 : ! Modified
21 :
22 : USE mo_kind, ONLY : i4, dp
23 : USE mo_os, ONLY : check_path_isfile
24 : USE mo_message, only: error_message
25 : use mo_string_utils, only : num2str
26 :
27 : IMPLICIT NONE
28 : PUBLIC :: check_uniform_axis, get_header_info_from_nc, read_header_nc_or_ascii
29 : PUBLIC :: read_header_ascii ! Reads header of ASCII files
30 : PUBLIC :: read_spatial_data_nc_or_ascii ! Reads netcdf and ASCII files
31 : PUBLIC :: read_spatial_data_nc ! Reads netcdf files
32 : PUBLIC :: read_spatial_data_ascii ! Reads ASCII files
33 :
34 :
35 : ! ------------------------------------------------------------------
36 :
37 : ! NAME
38 : ! read_spatial_data_nc_or_ascii
39 :
40 : ! PURPOSE
41 : !> \brief Reads spatial data files of nc or ASCII format.
42 :
43 : !> \details Reads spatial input data, e.g. dem, aspect, flow direction.
44 :
45 : ! HISTORY
46 : !> \authors Simon Lüdke
47 :
48 : !> \date June 2025
49 :
50 : INTERFACE read_spatial_data_nc_or_ascii
51 : MODULE PROCEDURE read_spatial_data_nc_or_ascii_i4, read_spatial_data_nc_or_ascii_dp
52 : END INTERFACE read_spatial_data_nc_or_ascii
53 :
54 : ! ------------------------------------------------------------------
55 :
56 : ! NAME
57 : ! read_spatial_data_nc
58 :
59 : ! PURPOSE
60 : !> \brief Reads spatial data files of nc format.
61 :
62 : !> \details Reads spatial input data, e.g. dem, aspect, flow direction.
63 :
64 : ! HISTORY
65 : !> \authors Simon Lüdke
66 :
67 : !> \date June 2025
68 :
69 : INTERFACE read_spatial_data_nc
70 : MODULE PROCEDURE read_spatial_data_nc_i4, read_spatial_data_nc_dp
71 : END INTERFACE read_spatial_data_nc
72 :
73 : ! ------------------------------------------------------------------
74 :
75 : ! NAME
76 : ! read_spatial_data_ascii
77 :
78 : ! PURPOSE
79 : !> \brief Reads spatial data files of ASCII format.
80 :
81 : !> \details Reads spatial input data, e.g. dem, aspect, flow direction.
82 :
83 : ! HISTORY
84 : !> \authors Juliane Mai
85 :
86 : !> \date Jan 2013
87 :
88 : ! Modifications:
89 : ! Matthias Zink Feb 2013 - , added interface and routine for datatype i4
90 : ! David Schaefer Mar 2015 - , removed double allocation of temporary data
91 : ! Robert Schweppe Jun 2018 - refactoring and reformatting
92 :
93 : INTERFACE read_spatial_data_ascii
94 : MODULE PROCEDURE read_spatial_data_ascii_i4, read_spatial_data_ascii_dp
95 : END INTERFACE read_spatial_data_ascii
96 :
97 : public :: check_header
98 :
99 :
100 :
101 : ! ------------------------------------------------------------------
102 :
103 : PRIVATE
104 :
105 : ! ------------------------------------------------------------------
106 :
107 : CONTAINS
108 :
109 : ! ------------------------------------------------------------------
110 :
111 : ! NAME
112 : ! read_spatial_data_ascii_dp
113 :
114 : ! PURPOSE
115 : !> \brief TODO: add description
116 :
117 : !> \details TODO: add description
118 :
119 : ! INTENT(IN)
120 : !> \param[in] "character(len = *) :: filename" filename with location
121 : !> \param[in] "integer(i4) :: fileunit" unit for opening the file
122 : !> \param[in] "integer(i4) :: header_nCols" number of columns of data fields:
123 : !> \param[in] "integer(i4) :: header_nRows" number of rows of data fields:
124 : !> \param[in] "real(dp) :: header_xllcorner" header read in lower left corner
125 : !> \param[in] "real(dp) :: header_yllcorner" header read in lower left corner
126 : !> \param[in] "real(dp) :: header_cellsize" header read in cellsize
127 :
128 : ! INTENT(OUT)
129 : !> \param[out] "real(dp), dimension(:, :) :: data" data
130 : !> \param[out] "logical, dimension(:, :) :: mask" mask
131 :
132 : ! HISTORY
133 : !> \authors Robert Schweppe
134 :
135 : !> \date Jun 2018
136 :
137 : ! Modifications:
138 :
139 66 : subroutine read_spatial_data_ascii_dp(filename, fileunit, header_ncols, header_nrows, header_xllcorner, &
140 : header_yllcorner, header_cellsize, data, mask)
141 : implicit none
142 :
143 : ! filename with location
144 : character(len = *), intent(in) :: filename
145 :
146 : ! unit for opening the file
147 : integer(i4), intent(in) :: fileunit
148 :
149 : ! number of rows of data fields:
150 : integer(i4), intent(in) :: header_nRows
151 :
152 : ! number of columns of data fields:
153 : integer(i4), intent(in) :: header_nCols
154 :
155 : ! header read in lower left corner
156 : real(dp), intent(in) :: header_xllcorner
157 :
158 : ! header read in lower left corner
159 : real(dp), intent(in) :: header_yllcorner
160 :
161 : ! header read in cellsize
162 : real(dp), intent(in) :: header_cellsize
163 :
164 : ! data
165 : real(dp), dimension(:, :), allocatable, intent(out) :: data
166 :
167 : ! mask
168 : logical, dimension(:, :), allocatable, intent(out) :: mask
169 :
170 : ! number of rows of data fields:
171 : integer(i4) :: file_nRows
172 :
173 : ! number of columns of data fields:
174 : integer(i4) :: file_nCols
175 :
176 : ! file read in lower left corner
177 66 : real(dp) :: file_xllcorner
178 :
179 : ! file read in lower left corner
180 66 : real(dp) :: file_yllcorner
181 :
182 : ! file read in cellsize
183 66 : real(dp) :: file_cellsize
184 :
185 : ! file read in nodata value
186 66 : real(dp) :: file_nodata
187 :
188 : integer(i4) :: i, j
189 :
190 : ! data
191 66 : real(dp), dimension(:, :), allocatable :: tmp_data
192 :
193 : ! mask
194 66 : logical, dimension(:, :), allocatable :: tmp_mask
195 :
196 : call read_header_ascii(filename, fileunit, &
197 : file_ncols, file_nrows, &
198 66 : file_xllcorner, file_yllcorner, file_cellsize, file_nodata)
199 : call check_header(file_ncols, file_nrows, file_xllcorner, file_yllcorner, file_cellsize, &
200 66 : header_ncols, header_nrows, header_xllcorner, header_yllcorner, header_cellsize)
201 : ! allocation and initialization of matrices
202 264 : allocate(tmp_data(file_nrows, file_ncols))
203 7829412 : tmp_data = file_nodata
204 264 : allocate(tmp_mask(file_nrows, file_ncols))
205 7829412 : tmp_mask = .true.
206 :
207 :
208 : !checking whether the file exists
209 66 : call check_path_isfile(path = filename, raise=.true.)
210 : ! read in
211 : ! recl is only a rough estimate on bytes per line in the ascii
212 : ! default for nag: recl=1024(byte) which is not enough for 100s of columns
213 66 : open (unit = fileunit, file = filename, action = 'read', status = 'old', recl = 48 * file_ncols)
214 : ! (a) skip header
215 462 : do i = 1, 6
216 462 : read(fileunit, *)
217 : end do
218 : ! (b) read data
219 27426 : do i = 1, file_nrows
220 7837986 : read(fileunit, *) (tmp_data(i, j), j = 1, file_ncols)
221 : end do
222 66 : close(fileunit)
223 :
224 : ! set mask .false. if nodata value appeared
225 7829346 : where (abs(tmp_data - file_nodata) .lt. tiny(1.0_dp))
226 : tmp_mask = .false.
227 : end where
228 :
229 : ! transpose of data due to longitude-latitude ordering
230 264 : allocate(data(file_ncols, file_nrows))
231 7838052 : data = transpose(tmp_data)
232 66 : deallocate(tmp_data)
233 :
234 264 : allocate(mask(file_ncols, file_nrows))
235 7838052 : mask = transpose(tmp_mask)
236 66 : deallocate(tmp_mask)
237 :
238 132 : end subroutine read_spatial_data_ascii_dp
239 :
240 : ! NAME
241 : ! read_spatial_data_ascii_i4
242 :
243 : ! PURPOSE
244 : !> \brief TODO: add description
245 :
246 : !> \details TODO: add description
247 :
248 : ! INTENT(IN)
249 : !> \param[in] "character(len = *) :: filename" filename with location
250 : !> \param[in] "integer(i4) :: fileunit" unit for opening the file
251 : !> \param[in] "integer(i4) :: header_nCols" number of columns of data fields:
252 : !> \param[in] "integer(i4) :: header_nRows" number of rows of data fields:
253 : !> \param[in] "real(dp) :: header_xllcorner" header read in lower left corner
254 : !> \param[in] "real(dp) :: header_yllcorner" header read in lower left corner
255 : !> \param[in] "real(dp) :: header_cellsize" header read in cellsize
256 :
257 : ! INTENT(OUT)
258 : !> \param[out] "integer(i4), dimension(:, :) :: data" data
259 : !> \param[out] "logical, dimension(:, :) :: mask" mask
260 :
261 : ! HISTORY
262 : !> \authors Robert Schweppe
263 :
264 : !> \date Jun 2018
265 :
266 : ! Modifications:
267 :
268 192 : subroutine read_spatial_data_ascii_i4(filename, fileunit, header_ncols, header_nrows, header_xllcorner, &
269 : header_yllcorner, header_cellsize, data, mask)
270 : implicit none
271 :
272 : ! filename with location
273 : character(len = *), intent(in) :: filename
274 :
275 : ! unit for opening the file
276 : integer(i4), intent(in) :: fileunit
277 :
278 : ! number of rows of data fields:
279 : integer(i4), intent(in) :: header_nRows
280 :
281 : ! number of columns of data fields:
282 : integer(i4), intent(in) :: header_nCols
283 :
284 : ! header read in lower left corner
285 : real(dp), intent(in) :: header_xllcorner
286 :
287 : ! header read in lower left corner
288 : real(dp), intent(in) :: header_yllcorner
289 :
290 : ! header read in cellsize
291 : real(dp), intent(in) :: header_cellsize
292 :
293 : ! data
294 : integer(i4), dimension(:, :), allocatable, intent(out) :: data
295 :
296 : ! mask
297 : logical, dimension(:, :), allocatable, intent(out) :: mask
298 :
299 : ! number of rows of data fields:
300 : integer(i4) :: file_nRows
301 :
302 : ! number of columns of data fields:
303 : integer(i4) :: file_nCols
304 :
305 : ! file read in lower left corner
306 192 : real(dp) :: file_xllcorner
307 :
308 : ! file read in lower left corner
309 192 : real(dp) :: file_yllcorner
310 :
311 : ! file read in cellsize
312 192 : real(dp) :: file_cellsize
313 :
314 : ! file read in nodata value
315 192 : real(dp) :: file_nodata
316 :
317 : integer(i4) :: i, j
318 :
319 : ! data
320 192 : integer(i4), dimension(:, :), allocatable :: tmp_data
321 :
322 : ! mask
323 192 : logical, dimension(:, :), allocatable :: tmp_mask
324 :
325 : call read_header_ascii(filename, fileunit, &
326 : file_ncols, file_nrows, &
327 192 : file_xllcorner, file_yllcorner, file_cellsize, file_nodata)
328 : call check_header(file_ncols, file_nrows, file_xllcorner, file_yllcorner, file_cellsize, &
329 192 : header_ncols, header_nrows, header_xllcorner, header_yllcorner, header_cellsize)
330 : ! allocation and initialization of matrices
331 768 : allocate(tmp_data(file_nrows, file_ncols))
332 22673136 : tmp_data = int(file_nodata, i4)
333 768 : allocate(tmp_mask(file_nrows, file_ncols))
334 22673136 : tmp_mask = .true.
335 :
336 : !checking whether the file exists
337 192 : call check_path_isfile(path = filename, raise=.true.)
338 : ! read in
339 : ! recl is only a rough estimate on bytes per line in the ascii
340 : ! default for nag: recl=1024(byte) which is not enough for 100s of columns
341 192 : open (unit = fileunit, file = filename, action = 'read', status = 'old', recl = 48 * file_ncols)
342 : ! (a) skip header
343 1344 : do i = 1, 6
344 1344 : read(fileunit, *)
345 : end do
346 : ! (b) read data
347 79488 : do i = 1, file_nrows
348 22697856 : read(fileunit, *) (tmp_data(i, j), j = 1, file_ncols)
349 : end do
350 192 : close(fileunit)
351 :
352 : ! set mask .false. if nodata value appeared
353 22672944 : where (tmp_data .EQ. int(file_nodata, i4))
354 : tmp_mask = .false.
355 : end where
356 :
357 : ! transpose of data due to longitude-latitude ordering
358 768 : allocate(data(file_ncols, file_nrows))
359 22698048 : data = transpose(tmp_data)
360 192 : deallocate(tmp_data)
361 :
362 768 : allocate(mask(file_ncols, file_nrows))
363 22698048 : mask = transpose(tmp_mask)
364 192 : deallocate(tmp_mask)
365 :
366 66 : end subroutine read_spatial_data_ascii_i4
367 :
368 : ! ------------------------------------------------------------------
369 :
370 : ! NAME
371 : ! read_header_ascii
372 :
373 : ! PURPOSE
374 : !> \brief Reads header lines of ASCII files.
375 :
376 : !> \details Reads header lines of ASCII files, e.g. dem, aspect, flow direction.
377 :
378 : ! INTENT(IN)
379 : !> \param[in] "character(len = *) :: filename" Name of file and its location
380 : !> \param[in] "integer(i4) :: fileunit" File unit for open file
381 :
382 : ! INTENT(OUT)
383 : !> \param[out] "integer(i4) :: header_nCols" Reference number of columns
384 : !> \param[out] "integer(i4) :: header_nRows" Reference number of rows
385 : !> \param[out] "real(dp) :: header_xllcorner" Reference lower left corner (x)
386 : !> \param[out] "real(dp) :: header_yllcorner" Reference lower left corner (y)
387 : !> \param[out] "real(dp) :: header_cellsize" Reference cell size [m]
388 : !> \param[out] "real(dp) :: header_nodata" Reference nodata value
389 :
390 : ! HISTORY
391 : !> \authors Juliane Mai
392 :
393 : !> \date Jan 2013
394 :
395 : ! Modifications:
396 : ! Robert Schweppe Jun 2018 - refactoring and reformatting
397 :
398 306 : subroutine read_header_ascii(filename, fileunit, header_ncols, header_nrows, header_xllcorner, header_yllcorner, &
399 : header_cellsize, header_nodata)
400 192 : use mo_common_constants, only : nodata_dp
401 : implicit none
402 :
403 : ! Name of file and its location
404 : character(len = *), intent(in) :: filename
405 :
406 : ! File unit for open file
407 : integer(i4), intent(in) :: fileunit
408 :
409 : ! Reference number of rows
410 : integer(i4), intent(out) :: header_nRows
411 :
412 : ! Reference number of columns
413 : integer(i4), intent(out) :: header_nCols
414 :
415 : ! Reference lower left corner (x)
416 : real(dp), intent(out) :: header_xllcorner
417 :
418 : ! Reference lower left corner (y)
419 : real(dp), intent(out) :: header_yllcorner
420 :
421 : ! Reference cell size [m]
422 : real(dp), intent(out) :: header_cellsize
423 :
424 : ! Reference nodata value
425 : real(dp), intent(out) :: header_nodata
426 :
427 : character(5) :: dummy
428 : integer(i4) :: io
429 :
430 : !checking whether the file exists
431 306 : call check_path_isfile(path = filename, raise=.true.)
432 : ! reading header from a file
433 306 : open (unit = fileunit, file = filename, status = 'old')
434 306 : read (fileunit, *) dummy, header_nCols
435 306 : read (fileunit, *) dummy, header_nRows
436 306 : read (fileunit, *) dummy, header_xllcorner
437 306 : read (fileunit, *) dummy, header_yllcorner
438 306 : read (fileunit, *) dummy, header_cellsize
439 306 : read (fileunit, *, iostat=io) dummy, header_nodata
440 : ! EOF reached (nodata not present, use default value)
441 306 : if (io < 0) header_nodata = nodata_dp
442 306 : close(fileunit)
443 306 : dummy = dummy // '' ! only to avoid warning
444 :
445 306 : end subroutine read_header_ascii
446 :
447 284 : subroutine check_header(ncols, nrows, xllcorner, yllcorner, &
448 : cellsize, ref_ncols, ref_nrows, ref_xllcorner, ref_yllcorner, ref_cellsize, &
449 : tolerance, context)
450 :
451 306 : use mo_kind, only : dp, i4
452 : use mo_message, only : error_message
453 : use mo_string_utils, only : num2str
454 : use mo_common_constants, only: defaultTolerance_dp
455 :
456 : implicit none
457 :
458 : integer(i4), intent(in) :: ncols
459 : integer(i4), intent(in) :: nrows
460 : real(dp), intent(in) :: xllcorner
461 : real(dp), intent(in) :: yllcorner
462 : real(dp), intent(in) :: cellsize
463 : integer(i4), intent(in) :: ref_nrows
464 : integer(i4), intent(in) :: ref_ncols
465 : real(dp), intent(in) :: ref_xllcorner
466 : real(dp), intent(in) :: ref_yllcorner
467 : real(dp), intent(in) :: ref_cellsize
468 : real(dp), intent(in), optional :: tolerance
469 : character(*), intent(in), optional :: context
470 :
471 284 : real(dp) :: tol_local
472 284 : character(len=:), allocatable :: err_message
473 :
474 284 : if (present(tolerance)) then
475 0 : tol_local = tolerance
476 : else
477 : tol_local = defaultTolerance_dp
478 : end if
479 :
480 284 : err_message = ''
481 284 : if ((ref_ncols .ne. ncols)) then
482 : err_message = err_message // 'read_spatial_data_ascii: ncols mismatch file='// &
483 : trim(num2str(ncols))//' expected='//trim(num2str(ref_ncols))// &
484 0 : new_line('a')
485 : end if
486 284 : if ((nrows .ne. ref_nrows)) then
487 : err_message = err_message // 'read_spatial_data_ascii: nrows mismatch file='// &
488 : trim(num2str(nrows))//' expected='//trim(num2str(ref_nrows))// &
489 0 : new_line('a')
490 : end if
491 284 : if ((abs(xllcorner - ref_xllcorner) .gt. tol_local)) then
492 : err_message = err_message // 'read_spatial_data_ascii: xllcorner mismatch file='// &
493 : trim(num2str(xllcorner, '(G32.16)'))//' expected='// &
494 : trim(num2str(ref_xllcorner, '(G32.16)'))// &
495 0 : new_line('a')
496 : end if
497 284 : if ((abs(yllcorner - ref_yllcorner) .gt. tol_local)) then
498 : err_message = err_message // 'read_spatial_data_ascii: yllcorner mismatch file='// &
499 : trim(num2str(yllcorner, '(G32.16)'))//' expected='// &
500 : trim(num2str(ref_yllcorner, '(G32.16)'))// &
501 0 : new_line('a')
502 : end if
503 284 : if ((abs(cellsize - ref_cellsize) .gt. tol_local)) then
504 : err_message = err_message // 'read_spatial_data_ascii: cellsize mismatch file='// &
505 : trim(num2str(cellsize, '(G32.16)'))//' expected='// &
506 : trim(num2str(ref_cellsize, '(G32.16)'))// &
507 0 : new_line('a')
508 : end if
509 :
510 284 : if (len_trim(err_message) > 0) then
511 0 : if (present(context)) then
512 0 : err_message = trim(context) // new_line('a') // err_message
513 : end if
514 0 : call error_message(trim(err_message))
515 : end if
516 :
517 284 : end subroutine check_header
518 :
519 : !> \brief Read header information from either NetCDF or ASCII depending on file name/presence.
520 : !> \authors Simon Lüdke
521 : !> \date Feb 2025
522 22 : subroutine read_header_nc_or_ascii(filepath, fileunit, header_ncols, header_nrows, header_xllcorner, header_yllcorner, &
523 : header_cellsize, header_nodata, varName)
524 284 : use mo_os, only : path_root, path_isfile
525 : implicit none
526 : character(len=*), intent(in) :: filepath
527 : integer(i4), intent(in) :: fileunit
528 : integer(i4), intent(out) :: header_ncols, header_nrows
529 : real(dp), intent(out) :: header_xllcorner, header_yllcorner, header_cellsize, header_nodata
530 : character(len=*), intent(in), optional :: varName
531 :
532 22 : character(len=:), allocatable :: ncname
533 22 : character(len=:), allocatable :: trimmed
534 : integer(i4) :: len_path
535 : logical :: is_nc
536 :
537 22 : trimmed = trim(filepath)
538 22 : len_path = len_trim(trimmed)
539 22 : is_nc = .false.
540 22 : if (len_path >= 3) then
541 22 : if ((trimmed(len_path-2:len_path) == '.nc') .or. (trimmed(len_path-2:len_path) == '.NC')) is_nc = .true.
542 : end if
543 :
544 22 : ncname = trimmed
545 22 : if (.not. is_nc) ncname = path_root(trimmed) // '.nc'
546 :
547 44 : if (is_nc .or. path_isfile(ncname)) then
548 : call get_header_info_from_nc(ncname, fileunit, header_ncols, header_nrows, header_xllcorner, header_yllcorner, &
549 0 : header_cellsize, header_nodata, var=varName)
550 : else
551 22 : call read_header_ascii(trimmed, fileunit, header_ncols, header_nrows, header_xllcorner, header_yllcorner, &
552 : header_cellsize, header_nodata)
553 : end if
554 22 : end subroutine read_header_nc_or_ascii
555 :
556 : !> \brief check if given axis is a uniform axis.
557 : !> \authors Sebastian Müller
558 : !> \date Mar 2024
559 0 : subroutine check_uniform_axis(var, cellsize, origin, increasing, tol)
560 22 : use mo_netcdf, only : NcVariable
561 : use mo_utils, only: is_close
562 : implicit none
563 : type(NcVariable), intent(in) :: var !< NetCDF variable for corresponding axis
564 : real(dp), optional, intent(out) :: cellsize !< cellsize of the uniform axis
565 : real(dp), optional, intent(out) :: origin !< origin of the axis vertices
566 : logical, optional, intent(out) :: increasing !< whether the axis has increasing values
567 : real(dp), intent(in), optional :: tol !< tolerance for cell size comparisson (default: 1.e-7)
568 0 : real(dp), dimension(:), allocatable :: axis
569 0 : real(dp), dimension(:,:), allocatable :: bounds
570 0 : real(dp) :: diff, tol_
571 : integer(i4) :: i_ub, i_lb
572 : logical :: has_bnds
573 : type(NcVariable) :: bnds
574 : character(len=256) :: name
575 :
576 0 : call var%getData(axis)
577 0 : if (var%hasAttribute("bounds")) then
578 0 : has_bnds = .true.
579 0 : call var%getAttribute("bounds", name)
580 0 : bnds = var%parent%getVariable(trim(name))
581 0 : call bnds%getData(bounds)
582 : else
583 : has_bnds = .false.
584 : end if
585 : ! store var name for error messages
586 0 : name = var%getName()
587 :
588 0 : tol_ = 1.e-7_dp
589 0 : if ( present(tol) ) tol_ = tol
590 :
591 0 : if (size(axis) == 0_i4) &
592 0 : call error_message("check_uniform_axis: axis is empty: ", name)
593 :
594 0 : if (size(axis) > 1_i4) then
595 0 : diff = (axis(size(axis)) - axis(1)) / real(size(axis) - 1_i4, dp)
596 0 : if (.not.all(is_close(axis(2:size(axis))-axis(1:size(axis)-1), diff, rtol=0.0_dp, atol=tol_))) &
597 0 : call error_message("check_uniform_axis: given axis is not uniform: ", name)
598 : else
599 0 : if (.not. has_bnds) &
600 0 : call error_message("check_uniform_axis: can't check axis of size 1 when no bounds are given: ", name)
601 0 : diff = bounds(2,1) - bounds(1,1)
602 : end if
603 :
604 0 : if (has_bnds) then
605 : ! be forgiving if the bounds don't have the same monotonicity as the axis (cf-convetion is hard)
606 0 : i_lb = 1
607 0 : i_ub = 2
608 0 : if (size(bounds, dim=2)>1) then
609 0 : if (.not. is_close(bounds(2,1), bounds(1,2), rtol=0.0_dp, atol=tol_) &
610 0 : .and. is_close(bounds(1,1), bounds(2,2), rtol=0.0_dp, atol=tol_)) then
611 0 : print *, "check_uniform_axis: bounds actually have wrong monotonicity: ", name
612 0 : i_lb = 2
613 0 : i_ub = 1
614 : end if
615 : end if
616 0 : if (.not.all(is_close(bounds(i_ub,:)-bounds(i_lb,:), diff, rtol=0.0_dp, atol=tol_))) &
617 0 : call error_message("check_uniform_axis: given bounds are not uniform: ", name)
618 0 : if (.not.all(is_close(axis(:)-bounds(i_lb,:), 0.5_dp*diff, rtol=0.0_dp, atol=tol_))) &
619 0 : call error_message("check_uniform_axis: given bounds are not centered around axis points: ", name)
620 : end if
621 :
622 0 : if ( present(cellsize) ) cellsize = abs(diff)
623 0 : if ( present(origin) ) origin = minval(axis) - 0.5_dp * abs(diff)
624 0 : if ( present(increasing) ) increasing = diff > 0.0_dp
625 :
626 0 : end subroutine check_uniform_axis
627 :
628 : !> \brief check if given variable is a x-axis.
629 : !> \return `logical :: is_x_axis`
630 : !> \authors Sebastian Müller
631 : !> \date Mar 2024
632 0 : logical function is_x_axis(var)
633 0 : use mo_netcdf, only : NcVariable
634 : implicit none
635 : type(NcVariable), intent(in) :: var !< NetCDF variable to check
636 : character(len=256) :: tmp_str
637 :
638 0 : is_x_axis = .false.
639 0 : if (var%hasAttribute("standard_name")) then
640 0 : call var%getAttribute("standard_name", tmp_str)
641 0 : if (trim(tmp_str) == "projection_x_coordinate") is_x_axis = .true.
642 0 : else if (var%hasAttribute("axis")) then
643 0 : call var%getAttribute("axis", tmp_str)
644 0 : if (trim(tmp_str) == "X") is_x_axis = .true.
645 0 : else if (var%hasAttribute("_CoordinateAxisType")) then
646 0 : call var%getAttribute("_CoordinateAxisType", tmp_str)
647 0 : if (trim(tmp_str) == "GeoX") is_x_axis = .true.
648 : end if
649 0 : end function is_x_axis
650 :
651 : !> \brief check if given variable is a y-axis.
652 : !> \return `logical :: is_y_axis`
653 : !> \authors Sebastian Müller
654 : !> \date Mar 2024
655 0 : logical function is_y_axis(var)
656 0 : use mo_netcdf, only : NcVariable
657 : implicit none
658 : type(NcVariable), intent(in) :: var !< NetCDF variable to check
659 : character(len=256) :: tmp_str
660 :
661 0 : is_y_axis = .false.
662 0 : if (var%hasAttribute("standard_name")) then
663 0 : call var%getAttribute("standard_name", tmp_str)
664 0 : if (trim(tmp_str) == "projection_y_coordinate") is_y_axis = .true.
665 0 : else if (var%hasAttribute("axis")) then
666 0 : call var%getAttribute("axis", tmp_str)
667 0 : if (trim(tmp_str) == "Y") is_y_axis = .true.
668 0 : else if (var%hasAttribute("_CoordinateAxisType")) then
669 0 : call var%getAttribute("_CoordinateAxisType", tmp_str)
670 0 : if (trim(tmp_str) == "GeoY") is_y_axis = .true.
671 : end if
672 0 : end function is_y_axis
673 :
674 : !> \brief check if given variable is a lon coordinate.
675 : !> \return `logical :: is_lon_coord`
676 : !> \authors Sebastian Müller
677 : !> \date Mar 2024
678 0 : logical function is_lon_coord(var)
679 0 : use mo_netcdf, only : NcVariable
680 : implicit none
681 : type(NcVariable), intent(in) :: var !< NetCDF variable to check
682 : character(len=256) :: tmp_str
683 :
684 0 : is_lon_coord = .false.
685 0 : if (var%hasAttribute("standard_name")) then
686 0 : call var%getAttribute("standard_name", tmp_str)
687 0 : if (trim(tmp_str) == "longitude") is_lon_coord = .true.
688 0 : else if (var%hasAttribute("units")) then
689 0 : call var%getAttribute("units", tmp_str)
690 0 : if (trim(tmp_str) == "degreeE") is_lon_coord = .true.
691 0 : if (trim(tmp_str) == "degree_E") is_lon_coord = .true.
692 0 : if (trim(tmp_str) == "degree_east") is_lon_coord = .true.
693 0 : if (trim(tmp_str) == "degreesE") is_lon_coord = .true.
694 0 : if (trim(tmp_str) == "degrees_E") is_lon_coord = .true.
695 0 : if (trim(tmp_str) == "degrees_east") is_lon_coord = .true.
696 0 : else if (var%hasAttribute("_CoordinateAxisType")) then
697 0 : call var%getAttribute("_CoordinateAxisType", tmp_str)
698 0 : if (trim(tmp_str) == "Lon") is_lon_coord = .true.
699 0 : else if (var%hasAttribute("long_name")) then
700 0 : call var%getAttribute("long_name", tmp_str)
701 0 : if (trim(tmp_str) == "longitude") is_lon_coord = .true.
702 : end if
703 :
704 0 : end function is_lon_coord
705 :
706 : !> \brief check if given variable is a lat coordinate.
707 : !> \return `logical :: is_lat_coord`
708 : !> \authors Sebastian Müller
709 : !> \date Mar 2024
710 0 : logical function is_lat_coord(var)
711 0 : use mo_netcdf, only : NcVariable
712 : implicit none
713 : type(NcVariable), intent(in) :: var !< NetCDF variable to check
714 : character(len=256) :: tmp_str
715 :
716 0 : is_lat_coord = .false.
717 0 : if (var%hasAttribute("standard_name")) then
718 0 : call var%getAttribute("standard_name", tmp_str)
719 0 : if (trim(tmp_str) == "latitude") is_lat_coord = .true.
720 0 : else if (var%hasAttribute("units")) then
721 0 : call var%getAttribute("units", tmp_str)
722 0 : if (trim(tmp_str) == "degreeN") is_lat_coord = .true.
723 0 : if (trim(tmp_str) == "degree_N") is_lat_coord = .true.
724 0 : if (trim(tmp_str) == "degree_north") is_lat_coord = .true.
725 0 : if (trim(tmp_str) == "degreesN") is_lat_coord = .true.
726 0 : if (trim(tmp_str) == "degrees_N") is_lat_coord = .true.
727 0 : if (trim(tmp_str) == "degrees_north") is_lat_coord = .true.
728 0 : else if (var%hasAttribute("_CoordinateAxisType")) then
729 0 : call var%getAttribute("_CoordinateAxisType", tmp_str)
730 0 : if (trim(tmp_str) == "Lat") is_lat_coord = .true.
731 0 : else if (var%hasAttribute("long_name")) then
732 0 : call var%getAttribute("long_name", tmp_str)
733 0 : if (trim(tmp_str) == "latitude") is_lat_coord = .true.
734 : end if
735 0 : end function is_lat_coord
736 :
737 :
738 : !> \brief Read NetCDF header information (matching read_header_ascii signature)
739 : !> \details Reads grid metadata (ncols, nrows, corner, cellsize, nodata) from a NetCDF file.
740 : !> \authors Sebastian Müller, Simon Lüdke
741 : !> \date Mar 2024 / Feb 2025
742 0 : subroutine get_header_info_from_nc(ncname, fileunit, header_ncols, header_nrows, header_xllcorner, header_yllcorner, &
743 : header_cellsize, header_nodata, mask, var)
744 0 : use mo_netcdf, only : NcDataset, NcVariable, NcDimension
745 : use mo_utils, only : is_close
746 : use mo_string_utils, only : num2str
747 : implicit none
748 : character(*), intent(in) :: ncname !< NetCDF filename
749 : integer(i4), intent(in) :: fileunit !< Unused (kept for signature compatibility)
750 : integer(i4), intent(out) :: header_ncols !< number of columns
751 : integer(i4), intent(out) :: header_nrows !< number of rows
752 : real(dp), intent(out) :: header_xllcorner !< lower left x
753 : real(dp), intent(out) :: header_yllcorner !< lower left y
754 : real(dp), intent(out) :: header_cellsize !< cell size
755 : real(dp), intent(out) :: header_nodata !< nodata value
756 : logical, optional, allocatable, dimension(:,:), intent(out) :: mask !< mask
757 : character(*), intent(in), optional :: var !< optional variable name
758 :
759 : type(NcDataset) :: nc
760 : type(NcVariable) :: ncvar, xvar, yvar
761 0 : type(NcDimension), dimension(:), allocatable :: dims
762 : integer(i4) :: rnk, y_dir
763 : integer(i4) :: bottom_up, top_down
764 0 : real(dp) :: cs_x, cs_y, tol
765 : logical :: y_inc, x_sph, y_sph, x_cart, y_cart, flip_y
766 : integer(i4) :: keep_y
767 : character(len=256) :: varName
768 :
769 0 : keep_y = -1_i4
770 0 : y_dir = keep_y
771 :
772 : ! set defaults
773 0 : tol = 1.e-7_dp
774 0 : bottom_up = 1_i4
775 0 : top_down = 0_i4
776 :
777 0 : nc = NcDataset(ncname, "r")
778 :
779 0 : if (present(var)) then
780 0 : varName = trim(var)
781 : else
782 0 : varName = determine_data_var_name(nc, ncname)
783 : end if
784 :
785 0 : ncvar = nc%getVariable(varName)
786 0 : rnk = ncvar%getRank()
787 0 : if (rnk < 2) call error_message("grid % from_netcdf: given variable has too few dimensions: ", trim(nc%fname), ":", trim(varName))
788 :
789 0 : dims = ncvar%getDimensions()
790 0 : header_ncols = dims(1)%getLength()
791 0 : header_nrows = dims(2)%getLength()
792 0 : xvar = nc%getVariable(trim(dims(1)%getName()))
793 0 : yvar = nc%getVariable(trim(dims(2)%getName()))
794 :
795 : ! check if x/y axis are x/y/lon/lat by standard_name, units, axistype or long_name
796 0 : if (is_x_axis(yvar).or.is_lon_coord(yvar).or.is_y_axis(xvar).or.is_lat_coord(xvar)) &
797 0 : call error_message("grid % from_netcdf: variable seems to have wrong axis order (not y-x): ", trim(nc%fname), ":", trim(varName))
798 :
799 0 : x_cart = is_x_axis(xvar)
800 0 : y_cart = is_y_axis(yvar)
801 0 : x_sph = is_lon_coord(xvar)
802 0 : y_sph = is_lat_coord(yvar)
803 :
804 0 : if (.not.(x_cart.or.x_sph)) &
805 0 : call error_message("grid % from_netcdf: can't determine coordinate system from x-axis: ", trim(nc%fname), ":", trim(varName))
806 0 : if (.not.(y_cart.or.y_sph)) &
807 0 : call error_message("grid % from_netcdf: can't determine coordinate system from y-axis: ", trim(nc%fname), ":", trim(varName))
808 0 : if (.not.(x_sph.eqv.y_sph)) &
809 0 : call error_message("grid % from_netcdf: x and y axis seem to have different coordinate systems: ", trim(nc%fname), ":", trim(varName))
810 :
811 : ! check axis uniformity and monotonicity
812 0 : call check_uniform_axis(xvar, cellsize=cs_x, origin=header_xllcorner, tol=tol)
813 0 : call check_uniform_axis(yvar, cellsize=cs_y, origin=header_yllcorner, increasing=y_inc, tol=tol)
814 0 : if (y_dir == keep_y) then
815 0 : y_dir = top_down
816 0 : if (y_inc) y_dir = bottom_up
817 : end if
818 : ! check y_dir
819 0 : if (.not.any(y_dir==[bottom_up, top_down])) &
820 0 : call error_message("grid % from_netcdf: y-direction not valid: ", trim(num2str(y_dir)))
821 :
822 : ! warn about flipping if present axis is not in desired direction
823 0 : flip_y = y_inc.neqv.(y_dir==bottom_up)
824 0 : if (flip_y) then
825 0 : print *, "grid % from_netcdf: y axis direction is oposite to desired one (inefficient flipping). ", &
826 0 : "You could flip the file beforehand with: 'cdo invertlat <ifile> <ofile>'. ", trim(nc%fname), ":", trim(varName)
827 : end if
828 : ! check cellsize in x and y direction
829 0 : if (.not.is_close(cs_x, cs_y, rtol=0.0_dp, atol=tol)) &
830 0 : call error_message("grid % from_netcdf: x and y axis have different cell sizes: ", trim(nc%fname), ":", trim(varName))
831 0 : header_cellsize = cs_x
832 :
833 : ! Determine no-data value
834 0 : if (ncvar%hasAttribute("_FillValue")) then
835 0 : call ncvar%getAttribute("_FillValue", header_nodata)
836 0 : else if (ncvar%hasAttribute("missing_value")) then
837 0 : call ncvar%getAttribute("missing_value", header_nodata)
838 : else
839 0 : call error_message('***ERROR: read_nc_header: missing _FillValue or missing_value attribute')
840 : end if
841 :
842 0 : call nc%close()
843 0 : end subroutine get_header_info_from_nc
844 :
845 0 : function determine_data_var_name(nc, ncname) result(varName)
846 0 : use mo_netcdf, only : NcDataset, NcVariable
847 : use mo_message, only : error_message
848 : use mo_os, only : path_stem
849 : class(NcDataset), intent(in) :: nc
850 : character(len=*), intent(in) :: ncname
851 : character(len=256) :: varName
852 :
853 0 : type(NcVariable), dimension(:), allocatable :: vars
854 0 : character(len=256), dimension(:), allocatable :: data_var_names
855 : integer(i4) :: i, data_var_count, data_var_index
856 : character(len=256) :: stem
857 :
858 0 : varName = ''
859 0 : vars = nc%getVariables()
860 :
861 0 : data_var_count = 0
862 0 : do i = 1, size(vars)
863 0 : if (vars(i)%getNoDimensions() >= 2) data_var_count = data_var_count + 1
864 : end do
865 :
866 0 : if (data_var_count == 0) then
867 0 : call error_message('read_spatial_data_nc: could not determine variable name for file: ' // trim(ncname))
868 : end if
869 :
870 0 : allocate(data_var_names(data_var_count))
871 0 : data_var_index = 0
872 0 : do i = 1, size(vars)
873 0 : if (vars(i)%getNoDimensions() >= 2) then
874 0 : data_var_index = data_var_index + 1
875 0 : data_var_names(data_var_index) = trim(vars(i)%getName())
876 : end if
877 : end do
878 :
879 0 : if (data_var_count == 1) then
880 0 : varName = trim(data_var_names(1))
881 0 : return
882 : end if
883 :
884 0 : stem = trim(path_stem(ncname))
885 0 : do i = 1, data_var_count
886 0 : if ((trim(data_var_names(i)) == stem) .or. (trim(data_var_names(i)) == 'data')) then
887 0 : varName = trim(data_var_names(i))
888 0 : return
889 : end if
890 : end do
891 :
892 0 : call error_message('read_spatial_data_nc: variable name could not be determined for file: ' // trim(ncname))
893 0 : end function determine_data_var_name
894 :
895 :
896 : ! NAME
897 : ! read_spatial_data_nc_i4
898 :
899 : ! PURPOSE
900 : !> \brief Read file from filepath. If there is a nc file with the correct name it is prefered over the asci file.
901 :
902 : !> \details TODO: add description
903 :
904 : ! HISTORY
905 : !> \authors Simon Lüdke
906 :
907 : !> \date June 2025
908 0 : subroutine read_spatial_data_nc_i4(ncname, data, maskout, ncols, nrows, xllcorner, yllcorner, cellsize, nodata_value, varName)
909 0 : use mo_netcdf, only : NcDataset, NcVariable
910 : use mo_message, only : error_message
911 : implicit none
912 :
913 : character(len=*), intent(in) :: ncname
914 : character(len=*), intent(in), optional :: varName
915 : integer(i4), dimension(:, :), allocatable, intent(out) :: data
916 : logical, optional, allocatable, dimension(:, :), intent(out) :: maskout
917 : integer(i4), intent(out) :: ncols, nrows
918 : real(dp), intent(out) :: xllcorner, yllcorner, cellsize
919 : real(dp), intent(out) :: nodata_value
920 :
921 : type(NcDataset) :: nc
922 : type(NcVariable) :: var
923 0 : integer(i4), allocatable :: var_shape(:), start(:), cnt(:)
924 : character(len=256) :: varName_local
925 :
926 : ! Open NetCDF dataset
927 0 : nc = NcDataset(ncname, "r")
928 :
929 0 : if (present(varName)) then
930 0 : varName_local = trim(varName)
931 : else
932 0 : varName_local = determine_data_var_name(nc, ncname)
933 : end if
934 :
935 : ! Extract header info
936 0 : call get_header_info_from_nc(ncname, 0_i4, ncols, nrows, xllcorner, yllcorner, cellsize, nodata_value, var=varName_local)
937 :
938 : ! Retrieve variable
939 0 : var = nc%getVariable(trim(varName_local))
940 :
941 : ! Determine shape and prepare read extents
942 0 : var_shape = var%getShape()
943 0 : allocate(start(size(var_shape)), source=1_i4)
944 0 : allocate(cnt(size(var_shape)), source=1_i4)
945 0 : cnt(1:2) = var_shape(1:2)
946 :
947 : ! Determine no-data value
948 0 : if (var%hasAttribute("_FillValue")) then
949 0 : call var%getAttribute("_FillValue", nodata_value)
950 0 : else if (var%hasAttribute("missing_value")) then
951 0 : call var%getAttribute("missing_value", nodata_value)
952 : else
953 0 : call error_message('***ERROR: read_nc_i4_data: missing _FillValue or missing_value attribute')
954 : end if
955 :
956 : ! Allocate and read data
957 0 : allocate(data(var_shape(1), var_shape(2)))
958 0 : call var%getData(data, start=start, cnt=cnt)
959 :
960 0 : if (present(maskout)) then
961 0 : allocate(maskout(var_shape(1), var_shape(2)))
962 0 : maskout = abs(real(data, dp) - nodata_value) .gt. tiny(1.0_dp)
963 : end if
964 :
965 0 : call nc%close()
966 0 : end subroutine read_spatial_data_nc_i4
967 :
968 :
969 : ! NAME
970 : ! read_spatial_data_nc_dp
971 :
972 : ! PURPOSE
973 : !> \brief Read file from filepath. If there is a nc file with the correct name it is prefered over the asci file.
974 :
975 : !> \details TODO: add description
976 :
977 : ! HISTORY
978 : !> \authors Simon Lüdke
979 :
980 : !> \date June 2025
981 0 : subroutine read_spatial_data_nc_dp(ncname, data, maskout, ncols, nrows, xllcorner, yllcorner, cellsize, nodata_value, varName)
982 0 : use mo_netcdf, only : NcDataset, NcVariable
983 : use mo_message, only : error_message
984 : implicit none
985 :
986 : character(len=*), intent(in) :: ncname
987 : character(len=*), intent(in), optional :: varName
988 : real(dp), dimension(:, :), allocatable, intent(out) :: data
989 : logical, optional, allocatable, dimension(:, :), intent(out) :: maskout
990 : integer(i4), intent(out) :: ncols, nrows
991 : real(dp), intent(out) :: xllcorner, yllcorner, cellsize
992 : real(dp), intent(out) :: nodata_value
993 :
994 : type(NcDataset) :: nc
995 : type(NcVariable) :: var
996 0 : integer(i4), allocatable :: var_shape(:), start(:), cnt(:)
997 : character(len=256) :: varName_local
998 :
999 : ! Open NetCDF dataset
1000 0 : nc = NcDataset(ncname, "r")
1001 :
1002 0 : if (present(varName)) then
1003 0 : varName_local = trim(varName)
1004 : else
1005 0 : varName_local = determine_data_var_name(nc, ncname)
1006 : end if
1007 :
1008 : ! Extract header info
1009 0 : call get_header_info_from_nc(ncname, 0_i4, ncols, nrows, xllcorner, yllcorner, cellsize, nodata_value, var=varName_local)
1010 :
1011 : ! Retrieve variable
1012 0 : var = nc%getVariable(trim(varName_local))
1013 :
1014 : ! Determine shape and prepare read extents
1015 0 : var_shape = var%getShape()
1016 0 : allocate(start(size(var_shape)), source=1_i4)
1017 0 : allocate(cnt(size(var_shape)), source=1_i4)
1018 0 : cnt(1:2) = var_shape(1:2)
1019 :
1020 : ! Determine no-data value
1021 0 : if (var%hasAttribute("_FillValue")) then
1022 0 : call var%getAttribute("_FillValue", nodata_value)
1023 0 : else if (var%hasAttribute("missing_value")) then
1024 0 : call var%getAttribute("missing_value", nodata_value)
1025 : else
1026 0 : call error_message('***ERROR: read_nc_i4_data: missing _FillValue or missing_value attribute')
1027 : end if
1028 :
1029 : ! Allocate and read data
1030 0 : allocate(data(var_shape(1), var_shape(2)))
1031 0 : call var%getData(data, start=start, cnt=cnt)
1032 :
1033 0 : if (present(maskout)) then
1034 0 : allocate(maskout(var_shape(1), var_shape(2)))
1035 0 : maskout = abs(data - nodata_value) .gt. tiny(1.0_dp)
1036 : end if
1037 :
1038 0 : call nc%close()
1039 0 : end subroutine read_spatial_data_nc_dp
1040 :
1041 :
1042 :
1043 : ! NAME
1044 : ! read_spatial_data_nc_or_ascii_dp
1045 :
1046 : ! PURPOSE
1047 : !> \brief Read file from filepath. If there is a nc file with the correct name it is prefered over the asci file.
1048 :
1049 : !> \details TODO: add description
1050 :
1051 : ! HISTORY
1052 : !> \authors Simon Lüdke
1053 :
1054 : !> \date June 2025
1055 :
1056 66 : subroutine read_spatial_data_nc_or_ascii_dp(filepath, fileunit, header_ncols, header_nrows, header_xllcorner, &
1057 : header_yllcorner, header_cellsize, data, maskout, &
1058 : out_nCols, out_nRows, out_xllcorner, out_yllcorner, out_cellsize, out_nodata_value)
1059 0 : use mo_os, only : path_root, path_isfile
1060 : implicit none
1061 :
1062 : ! filename with location
1063 : character(len = *), intent(in) :: filepath
1064 :
1065 : ! unit for opening the file
1066 : integer(i4), intent(in) :: fileunit
1067 :
1068 : ! number of rows of data fields:
1069 : integer(i4), intent(in) :: header_nRows
1070 :
1071 : ! number of columns of data fields:
1072 : integer(i4), intent(in) :: header_nCols
1073 :
1074 : ! header read in lower left corner
1075 : real(dp), intent(in) :: header_xllcorner
1076 :
1077 : ! header read in lower left corner
1078 : real(dp), intent(in) :: header_yllcorner
1079 :
1080 : ! header read in cellsize
1081 : real(dp), intent(in) :: header_cellsize
1082 :
1083 : ! data
1084 : real(dp), dimension(:, :), allocatable, intent(out) :: data
1085 :
1086 : ! mask
1087 : logical, optional, dimension(:, :), allocatable, intent(out) :: maskout
1088 :
1089 : ! number of rows of data fields:
1090 : integer(i4), optional, intent(out) :: out_nRows
1091 :
1092 : ! number of columns of data fields:
1093 : integer(i4), optional, intent(out) :: out_nCols
1094 :
1095 : ! header read in lower left corner
1096 : real(dp), optional, intent(out) :: out_xllcorner
1097 :
1098 : ! header read in lower left corner
1099 : real(dp), optional, intent(out) :: out_yllcorner
1100 :
1101 : ! header read in cellsize
1102 : real(dp), optional, intent(out) :: out_cellsize
1103 :
1104 : !
1105 : real(dp), optional, intent(out) :: out_nodata_value
1106 :
1107 : ! file exists
1108 66 : real(dp) :: nodata_value
1109 : integer(i4) :: nrows, ncols
1110 66 : real(dp) :: xllcorner, yllcorner, cellsize
1111 66 : character(len=:), allocatable :: ncname
1112 :
1113 : ! Prefer explicit NetCDF path if it already ends with .nc, otherwise use sidecar .nc
1114 66 : if (len_trim(filepath) >= 3) then
1115 66 : if ((filepath(len_trim(filepath)-2:len_trim(filepath)) == '.nc') .or. (filepath(len_trim(filepath)-2:len_trim(filepath)) == '.NC')) then
1116 0 : ncname = trim(filepath)
1117 : else
1118 66 : ncname = path_root(filepath) // '.nc'
1119 : end if
1120 : else
1121 0 : ncname = path_root(filepath) // '.nc'
1122 : end if
1123 :
1124 : ! preferably use nc file if it exists alternatively the asci version
1125 : ! print *, "Check if ", ncname, " existis: ", path_isfile(ncname)
1126 132 : if (path_isfile(ncname)) then
1127 0 : print *, "Read nc file: ", ncname
1128 0 : call read_spatial_data_nc(ncname, data, maskout, ncols, nrows, xllcorner, yllcorner, cellsize, nodata_value)
1129 : else
1130 66 : print *, "Read ascii file: ", filepath
1131 66 : if ((header_ncols /= 0_i4) .and. (header_nrows /= 0_i4)) then
1132 66 : ncols = header_ncols
1133 66 : nrows = header_nrows
1134 66 : yllcorner = header_yllcorner
1135 66 : xllcorner = header_xllcorner
1136 66 : cellsize = header_cellsize
1137 : else
1138 : call read_header_ascii(filepath, fileunit, &
1139 : ncols, nrows, xllcorner, &
1140 0 : yllcorner, cellsize, nodata_value)
1141 :
1142 : end if
1143 : ! print *, "run ascii dp reading with ", filepath
1144 : call read_spatial_data_ascii(filepath, fileunit, ncols, nrows, xllcorner, &
1145 66 : yllcorner, cellsize, data, maskout)
1146 : end if
1147 : ! if header information is needed as output it is saved to the output variables
1148 66 : if (present(out_nCols)) &
1149 0 : out_nCols = ncols
1150 66 : if (present(out_nRows)) &
1151 0 : out_nRows = nrows
1152 66 : if (present(out_xllcorner)) &
1153 0 : out_xllcorner = xllcorner
1154 66 : if (present(out_yllcorner)) &
1155 0 : out_yllcorner = yllcorner
1156 66 : if (present(out_cellsize)) &
1157 0 : out_cellsize = cellsize
1158 66 : if (present(out_nodata_value)) &
1159 0 : out_nodata_value = nodata_value
1160 :
1161 66 : end subroutine read_spatial_data_nc_or_ascii_dp
1162 :
1163 : ! NAME
1164 : ! read_spatial_data_nc_or_ascii_i4
1165 :
1166 : ! PURPOSE
1167 : !> \brief Read file from filepath. If there is a nc file with the correct name it is prefered over the asci file.
1168 :
1169 : !> \details TODO: add description
1170 :
1171 : ! HISTORY
1172 : !> \authors Simon Lüdke
1173 :
1174 : !> \date June 2025
1175 :
1176 192 : subroutine read_spatial_data_nc_or_ascii_i4(filepath, fileunit, header_ncols, header_nrows, header_xllcorner, &
1177 : header_yllcorner, header_cellsize, data, maskout, &
1178 : out_nCols, out_nRows, out_xllcorner, out_yllcorner, out_cellsize, out_nodata_value)
1179 66 : use mo_os, only : path_root, path_isfile
1180 : implicit none
1181 :
1182 : ! filename with location
1183 : character(len = *), intent(in) :: filepath
1184 :
1185 : ! unit for opening the file
1186 : integer(i4), intent(in) :: fileunit
1187 :
1188 : ! number of rows of data fields:
1189 : integer(i4), intent(in) :: header_nRows
1190 :
1191 : ! number of columns of data fields:
1192 : integer(i4), intent(in) :: header_nCols
1193 :
1194 : ! header read in lower left corner
1195 : real(dp), intent(in) :: header_xllcorner
1196 :
1197 : ! header read in lower left corner
1198 : real(dp), intent(in) :: header_yllcorner
1199 :
1200 : ! header read in cellsize
1201 : real(dp), intent(in) :: header_cellsize
1202 :
1203 : ! data
1204 : integer(i4), dimension(:, :), allocatable, intent(out) :: data
1205 :
1206 : ! mask
1207 : logical, optional, dimension(:, :), allocatable, intent(out) :: maskout
1208 :
1209 : ! number of rows of data fields:
1210 : integer(i4), optional, intent(out) :: out_nRows
1211 :
1212 : ! number of columns of data fields:
1213 : integer(i4), optional, intent(out) :: out_nCols
1214 :
1215 : ! header read in lower left corner
1216 : real(dp), optional, intent(out) :: out_xllcorner
1217 :
1218 : ! header read in lower left corner
1219 : real(dp), optional, intent(out) :: out_yllcorner
1220 :
1221 : ! header read in cellsize
1222 : real(dp), optional, intent(out) :: out_cellsize
1223 :
1224 : real(dp), optional, intent(out) :: out_nodata_value
1225 :
1226 : ! file exists
1227 192 : real(dp) :: nodata_value
1228 : integer(i4) :: nrows, ncols
1229 192 : real(dp) :: xllcorner, yllcorner, cellsize
1230 192 : character(len=:), allocatable :: ncname
1231 :
1232 : ! Prefer explicit NetCDF path if it already ends with .nc, otherwise use sidecar .nc
1233 192 : if (len_trim(filepath) >= 3) then
1234 192 : if ((filepath(len_trim(filepath)-2:len_trim(filepath)) == '.nc') .or. (filepath(len_trim(filepath)-2:len_trim(filepath)) == '.NC')) then
1235 0 : ncname = trim(filepath)
1236 : else
1237 192 : ncname = path_root(filepath) // '.nc'
1238 : end if
1239 : else
1240 0 : ncname = path_root(filepath) // '.nc'
1241 : end if
1242 :
1243 : ! preferably use nc file if it exists alternatively the asci version
1244 : ! print *, "Check if ", ncname, " existis: ", path_isfile(ncname)
1245 384 : if (path_isfile(ncname)) then
1246 0 : print *, "Read nc file: ", ncname
1247 0 : call read_spatial_data_nc(ncname, data, maskout, ncols, nrows, xllcorner, yllcorner, cellsize, nodata_value)
1248 : else
1249 192 : print *, "Read ascii file: ", filepath
1250 192 : if ((header_ncols /= 0_i4) .and. (header_nrows /= 0_i4)) then
1251 192 : ncols = header_ncols
1252 192 : nrows = header_nrows
1253 192 : yllcorner = header_yllcorner
1254 192 : xllcorner = header_xllcorner
1255 192 : cellsize = header_cellsize
1256 : else
1257 : call read_header_ascii(filepath, fileunit, &
1258 : ncols, nrows, xllcorner, &
1259 0 : yllcorner, cellsize, nodata_value)
1260 :
1261 : end if
1262 : ! print *, "run ascii i4 reading with ", filepath, " as ", ncname, " does not exists"
1263 : call read_spatial_data_ascii(filepath, fileunit, ncols, nrows, xllcorner, &
1264 192 : yllcorner, cellsize, data, maskout)
1265 : end if
1266 : ! if header information is needed as output it is saved to the output variables
1267 192 : if (present(out_nCols)) &
1268 0 : out_nCols = ncols
1269 192 : if (present(out_nRows)) &
1270 0 : out_nRows = nrows
1271 192 : if (present(out_xllcorner)) &
1272 0 : out_xllcorner = xllcorner
1273 192 : if (present(out_yllcorner)) &
1274 0 : out_yllcorner = yllcorner
1275 192 : if (present(out_cellsize)) &
1276 0 : out_cellsize = cellsize
1277 192 : if (present(out_nodata_value)) &
1278 0 : out_nodata_value = nodata_value
1279 192 : end subroutine read_spatial_data_nc_or_ascii_i4
1280 :
1281 :
1282 0 : END MODULE mo_read_spatial_data
|