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