MODFLOW 6  version 6.9.0.dev0
USGS Modular Hydrologic Model
NCModel.f90
Go to the documentation of this file.
1 !> @brief This module contains the NCModelExportModule
2 !!
3 !! This module defines a model export and base type for
4 !! supported netcdf files and is not dependent on
5 !! netcdf libraries.
6 !!
7 !<
9 
10  use kindmodule, only: dp, i4b, lgp
19  use listmodule, only: listtype
20 
21  implicit none
22  private
24  public :: ncexportannotation
25  public :: exportpackagetype
32 
33  !> @brief netcdf export types enumerator
34  !<
35  ENUM, BIND(C)
36  ENUMERATOR :: netcdf_undef = 0 !< undefined netcdf export type
37  ENUMERATOR :: netcdf_structured = 1 !< netcdf structrured export
38  ENUMERATOR :: netcdf_mesh2d = 2 !< netcdf ugrid layered mesh export
39  END ENUM
40 
42  type(modflowinputtype) :: mf6_input !< description of modflow6 input
43  character(len=LINELENGTH), dimension(:), allocatable :: param_names !< dynamic param tagnames
44  type(readstatevartype), dimension(:), allocatable :: param_reads !< param read states
45  integer(I4B), dimension(:, :), allocatable :: varids_param
46  integer(I4B), dimension(:, :), allocatable :: varids_aux
47  integer(I4B), dimension(:), pointer, contiguous :: mshape => null() !< model shape
48  integer(I4B), pointer :: iper !< most recent package rp load
49  integer(I4B) :: eper !< most recent period of netcdf package export
50  integer(I4B) :: nparam !< number of in scope params
51  integer(I4B) :: naux !< number of auxiliary variables
52  contains
53  procedure :: init => epkg_init
54  procedure :: destroy => epkg_destroy
55  end type exportpackagetype
56 
57  !> @brief netcdf export attribute annotations
58  !<
60  character(len=LINELENGTH) :: title !< file scoped title attribute
61  character(len=LINELENGTH) :: model !< file scoped model attribute
62  character(len=LINELENGTH) :: mesh !< mesh type
63  character(len=LINELENGTH) :: grid !< grid type
64  character(len=LINELENGTH) :: history !< file scoped history attribute
65  character(len=LINELENGTH) :: source !< file scoped source attribute
66  character(len=LINELENGTH) :: conventions !< file scoped conventions attribute
67  character(len=LINELENGTH) :: stdname !< dependent variable standard name
68  character(len=LINELENGTH) :: longname !< dependent variable long name
69  contains
70  procedure :: set
71  end type ncexportannotation
72 
73  !> @brief base class for an export model
74  !<
76  type(listtype) :: pkglist
77  character(len=LENMODELNAME) :: modelname !< name of model
78  character(len=LENCOMPONENTNAME) :: modeltype !< type of model
79  character(len=LINELENGTH) :: modelfname !< name of model input file
80  character(len=LINELENGTH) :: nc_fname !< name of netcdf export file
81  character(len=LINELENGTH) :: gridmap_name !< name of grid mapping variable
82  character(len=LINELENGTH) :: mesh_name = 'mesh' !< name of mesh container variable
83  character(len=LENMEMPATH) :: dis_mempath !< discretization input mempath
84  character(len=LENMEMPATH) :: ncf_mempath !< netcdf utility package input mempath
85  character(len=LENBIGLINE) :: wkt !< WKT1 (OGC 01-009) user string
86  character(len=LENBIGLINE) :: crs_wkt !< WKT2 (ISO 19162:2019) user string; falls back to wkt if empty
87  character(len=LINELENGTH) :: datetime !< export file creation time
88  character(len=LINELENGTH) :: xname !< dependent variable name
89  character(len=LINELENGTH) :: lenunits !< unidata udunits length units
90  type(ncexportannotation) :: annotation !< export file annotation
91  real(dp), dimension(:), pointer, contiguous :: x !< dependent variable pointer
92  integer(I4B) :: disenum !< type of discretization
93  integer(I4B) :: ncid !< netcdf file descriptor
94  integer(I4B) :: totnstp !< simulation total number of steps
95  integer(I4B), pointer :: deflate !< variable deflate level
96  integer(I4B), pointer :: shuffle !< variable shuffle filter
97  integer(I4B), pointer :: input_attr !< assign variable input attr
98  integer(I4B), pointer :: chunk_time !< chunking parameter for time dimension
99  integer(I4B) :: iout !< lst file descriptor
100  logical(LGP) :: chunking_active !< have chunking parameters been provided
101  contains
102  procedure :: init => export_init
103  procedure :: get => export_get
104  procedure :: input_attribute
105  procedure :: istp
106  procedure :: destroy => export_destroy
107  end type ncmodelexporttype
108 
109  !> @brief abstract type for model netcdf export type
110  !<
111  type, abstract, extends(ncmodelexporttype) :: ncbasemodelexporttype
112  contains
113  procedure :: export_input
114  procedure(model_define), deferred :: df
115  procedure(package_export), deferred :: export_df
116  procedure(model_step), deferred :: step
117  procedure(package_export), deferred :: package_step
118  end type ncbasemodelexporttype
119 
120  !> @brief abstract interfaces for model netcdf export type
121  !<
122  abstract interface
123  subroutine model_define(this)
124  import ncbasemodelexporttype
125  class(ncbasemodelexporttype), intent(inout) :: this
126  end subroutine
127  subroutine model_step(this)
128  import ncbasemodelexporttype
129  class(ncbasemodelexporttype), intent(inout) :: this
130  end subroutine
131  subroutine package_export(this, export_pkg)
133  class(ncbasemodelexporttype), intent(inout) :: this
134  class(exportpackagetype), pointer, intent(in) :: export_pkg
135  end subroutine
136  subroutine package_export_ilayer(this, export_pkg, ilayer_varname, &
137  ilayer)
139  class(ncbasemodelexporttype), intent(inout) :: this
140  class(exportpackagetype), pointer, intent(in) :: export_pkg
141  character(len=*), intent(in) :: ilayer_varname
142  integer(I4B), intent(in) :: ilayer
143  end subroutine
144  end interface
145 
146 contains
147 
148  !> @brief initialize dynamic package export object
149  !<
150  subroutine epkg_init(this, mf6_input, mshape, naux, param_names, &
151  nparam)
156  class(exportpackagetype), intent(inout) :: this
157  type(modflowinputtype), intent(in) :: mf6_input
158  integer(I4B), dimension(:), pointer, contiguous, intent(in) :: mshape !< model shape
159  integer(I4B), intent(in) :: naux
160  character(len=LINELENGTH), dimension(:), allocatable, &
161  intent(in) :: param_names
162  integer(I4B), intent(in) :: nparam
163  integer(I4B) :: n
164  character(len=LENVARNAME) :: rs_varname
165  character(len=LENMEMPATH) :: input_mempath
166  integer(I4B), pointer :: rsvar
167 
168  this%mf6_input = mf6_input
169  this%mshape => mshape
170  this%nparam = nparam
171  this%naux = naux
172  this%eper = 0
173 
174  input_mempath = create_mem_path(component=mf6_input%component_name, &
175  subcomponent=mf6_input%subcomponent_name, &
176  context=idm_context)
177 
178  ! allocate param arrays
179  allocate (this%param_names(nparam))
180  allocate (this%param_reads(nparam))
181  allocate (this%varids_param(nparam, mshape(1)))
182  allocate (this%varids_aux(naux, mshape(1)))
183 
184  ! set param arrays
185  do n = 1, nparam
186  this%param_names(n) = param_names(n)
187  rs_varname = rsv_name(param_names(n))
188  call mem_setptr(rsvar, rs_varname, mf6_input%mempath)
189  this%param_reads(n)%invar => rsvar
190  end do
191 
192  ! set pointer to loaded input period
193  call mem_setptr(this%iper, 'IPER', mf6_input%mempath)
194  end subroutine epkg_init
195 
196  !> @brief destroy dynamic package export object
197  !<
198  subroutine epkg_destroy(this)
200  class(exportpackagetype), intent(inout) :: this
201  if (allocated(this%param_names)) deallocate (this%param_names)
202  end subroutine epkg_destroy
203 
204  !> @brief set netcdf file scoped attributes
205  !<
206  subroutine set(this, modelname, modeltype, modelfname, nctype, disenum)
207  use versionmodule, only: fullversion
208  use inputoutputmodule, only: lowcase
209  class(ncexportannotation), intent(inout) :: this
210  character(len=*), intent(in) :: modelname
211  character(len=*), intent(in) :: modeltype
212  character(len=*), intent(in) :: modelfname
213  integer(I4B), intent(in) :: nctype
214  integer(I4B), intent(in) :: disenum
215  integer :: values(8)
216 
217  this%title = ''
218  this%model = ''
219  this%mesh = ''
220  this%grid = ''
221  this%history = ''
222  this%source = ''
223  this%conventions = ''
224  this%stdname = ''
225  this%longname = ''
226 
227  ! set file conventions
228  this%conventions = 'CF-1.13'
229  if (nctype == netcdf_mesh2d) this%conventions = &
230  trim(this%conventions)//' UGRID-1.0'
231 
232  ! set model specific attributes
233  select case (modeltype)
234  case ('GWF')
235  this%title = trim(modelname)//' hydraulic head'
236  this%longname = 'head'
237  case ('GWT')
238  this%title = trim(modelname)//' concentration'
239  this%longname = 'concentration'
240  case ('GWE')
241  this%title = trim(modelname)//' temperature'
242  this%longname = 'temperature'
243  case default
244  errmsg = trim(modeltype)//' models not supported for NetCDF export.'
245  call store_error(errmsg)
246  call store_error_filename(modelfname)
247  end select
248 
249  if (isim_mode == mvalidate) then
250  this%title = trim(this%title)//' array input'
251  end if
252 
253  ! set mesh type
254  if (nctype == netcdf_mesh2d) then
255  this%mesh = 'layered'
256  end if
257 
258  ! set grid type
259  if (disenum == dis) then
260  this%grid = 'structured'
261  else if (disenum == disv) then
262  this%grid = 'vertex'
263  end if
264 
265  ! model description string (lowercase, no version suffix, matching CF convention)
266  this%model = trim(modeltype)//': '//trim(modelname)
267  call lowcase(this%model)
268 
269  ! modflow6 version string
270  this%source = 'MODFLOW 6 '//trim(adjustl(fullversion))
271 
272  ! create timestamp
273  call date_and_time(values=values)
274  write (this%history, '(a,i0,a,i0,a,i0,a,i0,a,i0,a,i0,a,i0)') &
275  'first created ', values(1), '/', values(2), '/', values(3), ' ', &
276  values(5), ':', values(6), ':', values(7), '.', values(8)
277  end subroutine set
278 
279  !> @brief initialization of model netcdf export
280  !<
281  subroutine export_init(this, modelname, modeltype, modelfname, nc_fname, &
282  disenum, nctype, iout)
283  use tdismodule, only: datetime0, nper, nstp
287  use inputoutputmodule, only: lowcase
290  class(ncmodelexporttype), intent(inout) :: this
291  character(len=*), intent(in) :: modelname
292  character(len=*), intent(in) :: modeltype
293  character(len=*), intent(in) :: modelfname
294  character(len=*), intent(in) :: nc_fname
295  integer(I4B), intent(in) :: disenum
296  integer(I4B), intent(in) :: nctype
297  integer(I4B), intent(in) :: iout
298  character(len=LENMEMPATH) :: model_mempath
299  type(utlncfparamfoundtype) :: ncf_found
300 
301  ! allocate
302  allocate (this%deflate)
303  allocate (this%shuffle)
304  allocate (this%input_attr)
305  allocate (this%chunk_time)
306 
307  ! initialize
308  this%modelname = modelname
309  this%modeltype = modeltype
310  this%modelfname = modelfname
311  this%nc_fname = nc_fname
312  this%gridmap_name = ''
313  this%ncf_mempath = ''
314  this%wkt = ''
315  this%crs_wkt = ''
316  this%datetime = ''
317  this%xname = ''
318  this%lenunits = ''
319  this%disenum = disenum
320  this%ncid = 0
321  this%totnstp = 0
322  this%deflate = -1
323  this%shuffle = 0
324  this%input_attr = 1
325  this%chunk_time = -1
326  this%iout = iout
327  this%chunking_active = .false.
328 
329  ! set file scoped attributes
330  call this%annotation%set(modelname, modeltype, modelfname, nctype, disenum)
331 
332  ! set dependent variable basename
333  select case (modeltype)
334  case ('GWF')
335  this%xname = 'head'
336  case ('GWT')
337  this%xname = 'concentration'
338  case ('GWE')
339  this%xname = 'temperature'
340  case default
341  errmsg = trim(modeltype)//' models not supported for NetCDF export.'
342  call store_error(errmsg)
343  call store_error_filename(modelfname)
344  end select
345 
346  ! set discretization input mempath
347  if (disenum == dis) then
348  this%dis_mempath = create_mem_path(modelname, 'DIS', idm_context)
349  else if (disenum == disu) then
350  this%dis_mempath = create_mem_path(modelname, 'DISU', idm_context)
351  else if (disenum == disv) then
352  this%dis_mempath = create_mem_path(modelname, 'DISV', idm_context)
353  end if
354 
355  ! set dependent variable pointer
356  model_mempath = create_mem_path(component=modelname)
357  call mem_setptr(this%x, 'X', model_mempath)
358 
359  ! set ncf_mempath if provided
360  if (filein_fname(this%ncf_mempath, 'NCF6_MEMPATH', this%dis_mempath, &
361  modelfname)) then
362  call mem_set_value(this%wkt, 'WKT', this%ncf_mempath, &
363  ncf_found%wkt)
364  call mem_set_value(this%crs_wkt, 'CRS_WKT', this%ncf_mempath, &
365  ncf_found%crs_wkt)
366  call mem_set_value(this%deflate, 'DEFLATE', this%ncf_mempath, &
367  ncf_found%deflate)
368  call mem_set_value(this%shuffle, 'SHUFFLE', this%ncf_mempath, &
369  ncf_found%shuffle)
370  call mem_set_value(this%input_attr, 'ATTR_OFF', this%ncf_mempath, &
371  ncf_found%attr_off)
372  call mem_set_value(this%chunk_time, 'CHUNK_TIME', this%ncf_mempath, &
373  ncf_found%chunk_time)
374  end if
375 
376  if (ncf_found%wkt .or. ncf_found%crs_wkt) then
377  this%gridmap_name = 'projection'
378  end if
379 
380  ! ATTR_OFF turns off modflow 6 input attributes
381  if (ncf_found%attr_off) then
382  this%input_attr = 0
383  end if
384 
385  ! set datetime string
386  if (datetime0 /= '') then
387  this%datetime = 'days since '//trim(datetime0)
388  else
389  ! January 1, 1970 at 00:00:00 UTC
390  this%datetime = 'days since 1970-01-01T00:00:00'
391  end if
392 
393  ! set total nstp
394  if (isim_mode == mvalidate) then
395  this%totnstp = nper
396  else
397  this%totnstp = sum(nstp)
398  end if
399  end subroutine export_init
400 
401  !> @brief retrieve dynamic export object from package list
402  !<
403  function export_get(this, idx) result(res)
404  use listmodule, only: listtype
405  class(ncmodelexporttype), intent(inout) :: this
406  integer(I4B), intent(in) :: idx
407  class(exportpackagetype), pointer :: res
408  class(*), pointer :: obj
409  nullify (res)
410  obj => this%pkglist%GetItem(idx)
411  if (associated(obj)) then
412  select type (obj)
413  class is (exportpackagetype)
414  res => obj
415  end select
416  end if
417  end function export_get
418 
419  !> @brief build modflow_input attribute string
420  !<
421  function input_attribute(this, pkgname, idt) result(attr)
422  use inputoutputmodule, only: lowcase
425  class(ncmodelexporttype), intent(inout) :: this
426  character(len=*), intent(in) :: pkgname
427  type(inputparamdefinitiontype), pointer, intent(in) :: idt
428  character(len=LINELENGTH) :: attr
429  attr = ''
430  if (this%input_attr > 0) then
431  attr = trim(this%modelname)//mempathseparator//trim(pkgname)// &
432  mempathseparator//trim(idt%tagname)
433  call lowcase(attr)
434  end if
435  end function input_attribute
436 
437  !> @brief step index for timeseries data
438  !<
439  function istp(this)
440  use tdismodule, only: kstp, kper, nstp
441  class(ncmodelexporttype), intent(inout) :: this
442  integer(I4B) :: n, istp
443  istp = kstp
444  if (kper > 1) then
445  do n = 1, kper - 1
446  istp = istp + nstp(n)
447  end do
448  end if
449  end function istp
450 
451  !> @brief build netcdf variable name
452  !<
453  function export_varname(pkgname, tagname, mempath, layer, iaux) &
454  result(varname)
457  use inputoutputmodule, only: lowcase
458  character(len=*), intent(in) :: pkgname
459  character(len=*), intent(in) :: tagname
460  character(len=*), intent(in) :: mempath
461  integer(I4B), optional, intent(in) :: layer
462  integer(I4B), optional, intent(in) :: iaux
463  character(len=LINELENGTH) :: varname
464  type(characterstringtype), dimension(:), pointer, &
465  contiguous :: auxnames
466  character(len=LINELENGTH) :: pname, vname
467  vname = tagname
468  pname = pkgname
469 
470  if (present(iaux)) then
471  if (iaux > 0) then
472  if (tagname == 'AUX') then
473  ! reset vname to auxiliary variable name
474  call mem_setptr(auxnames, 'AUXILIARY', mempath)
475  vname = auxnames(iaux)
476  end if
477  end if
478  end if
479 
480  call lowcase(vname)
481  call lowcase(pname)
482  varname = trim(pname)//'_'//trim(vname)
483 
484  if (present(layer)) then
485  if (layer > 0) then
486  !write (varname, '(a,i0)') trim(varname)//'_L', layer
487  write (varname, '(a,i0)') trim(varname)//'_l', layer
488  end if
489  end if
490  end function export_varname
491 
492  !> @brief build netcdf variable longname
493  !<
494  function export_longname(longname, pkgname, tagname, mempath, layer, iaux, &
495  component_type, subcomponent_type) result(lname)
498  use inputoutputmodule, only: lowcase
500  character(len=*), intent(in) :: longname
501  character(len=*), intent(in) :: pkgname
502  character(len=*), intent(in) :: tagname
503  character(len=*), intent(in) :: mempath
504  integer(I4B), optional, intent(in) :: layer
505  integer(I4B), optional, intent(in) :: iaux
506  character(len=*), optional, intent(in) :: component_type
507  character(len=*), optional, intent(in) :: subcomponent_type
508  character(len=LINELENGTH) :: lname
509  type(characterstringtype), dimension(:), pointer, &
510  contiguous :: auxnames
511  character(len=LINELENGTH) :: pname, vname, auxname
512  pname = pkgname
513  vname = tagname
514  call lowcase(pname)
515  call lowcase(vname)
516  if (longname == '') then
517  lname = trim(pname)//' '//trim(vname)
518  else
519  lname = longname
520  if (present(component_type) .and. present(subcomponent_type)) then
521  if (idm_multi_package(component_type, subcomponent_type)) then
522  lname = trim(pname)//' '//trim(lname)
523  end if
524  end if
525  end if
526 
527  if (present(iaux)) then
528  if (iaux > 0) then
529  if (tagname == 'AUX') then
530  ! reset vname to auxiliary variable name
531  call mem_setptr(auxnames, 'AUXILIARY', mempath)
532  auxname = auxnames(iaux)
533  call lowcase(auxname)
534  lname = trim(lname)//' '//trim(auxname)
535  end if
536  end if
537  end if
538 
539  if (present(layer)) then
540  if (layer > 0) then
541  write (lname, '(a,i0)') trim(lname)//' layer ', layer
542  end if
543  end if
544  end function export_longname
545 
546  !> @brief netcdf dynamic package period export
547  !<
548  subroutine export_input(this)
549  use tdismodule, only: kper
550  class(ncbasemodelexporttype), intent(inout) :: this
551  integer(I4B) :: idx
552  class(exportpackagetype), pointer :: export_pkg
553  do idx = 1, this%pkglist%Count()
554  export_pkg => this%get(idx)
555  ! period input already exported
556  if (export_pkg%eper >= kper) cycle
557  ! update export package
558  call this%package_step(export_pkg)
559  ! update exported iper
560  export_pkg%eper = kper
561  end do
562  end subroutine export_input
563 
564  !> @brief map a WKT string to a CF-1.13 grid_mapping_name
565  !!
566  !! Accepts either WKT1 (OGC 01-009, PROJECTION["name"]) or WKT2
567  !! (ISO 19162:2019, METHOD["name"]) format. WKT1 is tried first;
568  !! if PROJECTION[ is absent the WKT2 METHOD[ path is attempted.
569  !! Returns '' for geographic CRS or unrecognised projection names.
570  !! Only common groundwater projections included. Matching is
571  !! case-insensitive.
572  !<
573  function wkt_to_cf_gridmapping(wkt) result(gmname)
574  use inputoutputmodule, only: upcase
575  character(len=*), intent(in) :: wkt
576  character(len=LINELENGTH) :: gmname
577  character(len=LINELENGTH) :: proj_name
578  character(len=LENBIGLINE) :: wkt_upper
579  integer :: istart, iend
580 
581  gmname = ''
582  proj_name = ''
583 
584  ! uppercase copy for case-insensitive keyword search
585  wkt_upper = wkt
586  call upcase(wkt_upper)
587 
588  ! --- WKT1 path: PROJECTION["name"] ---
589  istart = index(wkt_upper, 'PROJECTION[')
590  if (istart /= 0) then
591  istart = istart + len('PROJECTION[')
592  do while (istart <= len(wkt))
593  if (wkt(istart:istart) == '"') exit
594  istart = istart + 1
595  end do
596  if (istart > len(wkt)) return
597  istart = istart + 1
598  iend = index(wkt(istart:), '"')
599  if (iend == 0) return
600  iend = istart + iend - 2
601  if (iend < istart) return
602  proj_name = wkt(istart:iend)
603  call upcase(proj_name)
604  select case (trim(proj_name))
605  case ('TRANSVERSE_MERCATOR')
606  gmname = 'transverse_mercator'
607  case ('LAMBERT_CONFORMAL_CONIC_2SP', 'LAMBERT_CONFORMAL_CONIC_1SP')
608  gmname = 'lambert_conformal_conic'
609  case ('ALBERS_CONIC_EQUAL_AREA')
610  gmname = 'albers_conical_equal_area'
611  case ('MERCATOR_1SP', 'MERCATOR_2SP')
612  gmname = 'mercator'
613  case ('POLAR_STEREOGRAPHIC')
614  gmname = 'polar_stereographic'
615  end select
616  return
617  end if
618 
619  ! --- WKT2 path: METHOD["name"] ---
620  istart = index(wkt_upper, 'METHOD[')
621  if (istart == 0) return
622  istart = istart + len('METHOD[')
623  do while (istart <= len(wkt))
624  if (wkt(istart:istart) == '"') exit
625  istart = istart + 1
626  end do
627  if (istart > len(wkt)) return
628  istart = istart + 1
629  iend = index(wkt(istart:), '"')
630  if (iend == 0) return
631  iend = istart + iend - 2
632  if (iend < istart) return
633  proj_name = wkt(istart:iend)
634  call upcase(proj_name)
635  select case (trim(proj_name))
636  case ('TRANSVERSE MERCATOR')
637  gmname = 'transverse_mercator'
638  case ('LAMBERT CONIC CONFORMAL (2SP)', 'LAMBERT CONIC CONFORMAL (1SP)')
639  gmname = 'lambert_conformal_conic'
640  case ('ALBERS EQUAL AREA')
641  gmname = 'albers_conical_equal_area'
642  case ('MERCATOR (VARIANT A)', 'MERCATOR (VARIANT B)')
643  gmname = 'mercator'
644  case ('POLAR STEREOGRAPHIC (VARIANT A)', 'POLAR STEREOGRAPHIC (VARIANT B)')
645  gmname = 'polar_stereographic'
646  end select
647  end function wkt_to_cf_gridmapping
648 
649  !> @brief find the first number following a quoted parameter name
650  !!
651  !! Matches e.g. PARAMETER["central_meridian",-117] (WKT1) or
652  !! PARAMETER["Longitude of natural origin",-117,...] (WKT2).
653  !! Best-effort only -- MF6 has no CRS parsing library. found=.false.
654  !! if name is not present or no number follows it.
655  !<
656  function wkt_find_param(wkt, name, found) result(value)
657  use inputoutputmodule, only: upcase
658  character(len=*), intent(in) :: wkt
659  character(len=*), intent(in) :: name
660  logical(LGP), intent(out) :: found
661  real(dp) :: value
662  character(len=LENBIGLINE) :: wkt_upper
663  character(len=LINELENGTH) :: target_upper
664  integer :: ipos, istart, iend, ios, wkt_len
665 
666  found = .false.
667  value = dzero
668  wkt_len = len_trim(wkt)
669  target_upper = '"'//trim(name)//'"'
670  wkt_upper = wkt
671  call upcase(wkt_upper)
672  call upcase(target_upper)
673  ipos = index(wkt_upper(1:wkt_len), trim(target_upper))
674  if (ipos == 0) return
675 
676  istart = ipos + len_trim(target_upper)
677  do while (istart <= wkt_len)
678  if (wkt(istart:istart) == ',') exit
679  istart = istart + 1
680  end do
681  istart = istart + 1
682  iend = istart
683  do while (iend <= wkt_len)
684  if (wkt(iend:iend) == ',' .or. wkt(iend:iend) == ']') exit
685  iend = iend + 1
686  end do
687  if (iend <= istart) return
688  read (wkt(istart:iend - 1), *, iostat=ios) value
689  if (ios == 0) found = .true.
690  end function wkt_find_param
691 
692  !> @brief find semi-major axis and inverse flattening from a WKT string
693  !!
694  !! Matches SPHEROID["name",A,B,...] (WKT1) or ELLIPSOID["name",A,B,...]
695  !! (WKT2). Best-effort only. found=.false. if neither keyword is
696  !! present or the two numbers cannot be read.
697  !<
698  subroutine wkt_find_ellipsoid(wkt, semi_major_axis, inverse_flattening, found)
699  use inputoutputmodule, only: upcase
700  character(len=*), intent(in) :: wkt
701  real(DP), intent(out) :: semi_major_axis
702  real(DP), intent(out) :: inverse_flattening
703  logical(LGP), intent(out) :: found
704  character(len=LENBIGLINE) :: wkt_upper
705  integer :: ipos, i, iend, ios, wkt_len, nquote
706 
707  found = .false.
708  semi_major_axis = dzero
709  inverse_flattening = dzero
710  wkt_len = len_trim(wkt)
711  wkt_upper = wkt
712  call upcase(wkt_upper)
713 
714  ipos = index(wkt_upper(1:wkt_len), 'SPHEROID[')
715  if (ipos == 0) ipos = index(wkt_upper(1:wkt_len), 'ELLIPSOID[')
716  if (ipos == 0) return
717 
718  ! skip past the quoted ellipsoid name (its closing quote)
719  i = ipos
720  nquote = 0
721  do while (i <= wkt_len)
722  if (wkt(i:i) == '"') nquote = nquote + 1
723  if (nquote == 2) exit
724  i = i + 1
725  end do
726  if (nquote < 2) return
727  i = i + 1
728  if (wkt(i:i) == ',') i = i + 1
729 
730  iend = i
731  do while (iend <= wkt_len)
732  if (wkt(iend:iend) == ',') exit
733  iend = iend + 1
734  end do
735  if (iend <= i) return
736  read (wkt(i:iend - 1), *, iostat=ios) semi_major_axis
737  if (ios /= 0) return
738 
739  i = iend + 1
740  iend = i
741  do while (iend <= wkt_len)
742  if (wkt(iend:iend) == ',' .or. wkt(iend:iend) == ']') exit
743  iend = iend + 1
744  end do
745  if (iend <= i) return
746  read (wkt(i:iend - 1), *, iostat=ios) inverse_flattening
747  if (ios == 0) found = .true.
748  end subroutine wkt_find_ellipsoid
749 
750  !> @brief extract CF transverse_mercator grid_mapping parameters
751  !!
752  !! Best-effort extraction from a WKT1 or WKT2 string -- MF6 has no
753  !! CRS parsing library. Tries WKT1 parameter names first, falling
754  !! back to WKT2 names. found=.false. if any parameter cannot be
755  !! located.
756  !<
757  subroutine wkt_transverse_mercator_params(wkt, longitude_of_central_meridian, &
758  latitude_of_projection_origin, &
759  scale_factor_at_central_meridian, &
760  false_easting, false_northing, &
761  semi_major_axis, inverse_flattening, &
762  found)
763  character(len=*), intent(in) :: wkt
764  real(dp), intent(out) :: longitude_of_central_meridian
765  real(dp), intent(out) :: latitude_of_projection_origin
766  real(dp), intent(out) :: scale_factor_at_central_meridian
767  real(dp), intent(out) :: false_easting
768  real(dp), intent(out) :: false_northing
769  real(dp), intent(out) :: semi_major_axis
770  real(dp), intent(out) :: inverse_flattening
771  logical(LGP), intent(out) :: found
772  logical(LGP) :: f1, f2, f3, f4, f5, f6
773 
774  longitude_of_central_meridian = wkt_find_param(wkt, 'central_meridian', f1)
775  if (.not. f1) longitude_of_central_meridian = &
776  wkt_find_param(wkt, 'Longitude of natural origin', f1)
777 
778  latitude_of_projection_origin = wkt_find_param(wkt, 'latitude_of_origin', f2)
779  if (.not. f2) latitude_of_projection_origin = &
780  wkt_find_param(wkt, 'Latitude of natural origin', f2)
781 
782  scale_factor_at_central_meridian = wkt_find_param(wkt, 'scale_factor', f3)
783  if (.not. f3) scale_factor_at_central_meridian = &
784  wkt_find_param(wkt, 'Scale factor at natural origin', f3)
785 
786  false_easting = wkt_find_param(wkt, 'false_easting', f4)
787  if (.not. f4) false_easting = wkt_find_param(wkt, 'False easting', f4)
788 
789  false_northing = wkt_find_param(wkt, 'false_northing', f5)
790  if (.not. f5) false_northing = wkt_find_param(wkt, 'False northing', f5)
791 
792  call wkt_find_ellipsoid(wkt, semi_major_axis, inverse_flattening, f6)
793 
794  found = f1 .and. f2 .and. f3 .and. f4 .and. f5 .and. f6
795  end subroutine wkt_transverse_mercator_params
796 
797  !> @brief extract CF lambert_conformal_conic grid_mapping parameters
798  !!
799  !! Best-effort extraction from a WKT1 or WKT2 string -- MF6 has no
800  !! CRS parsing library. 2SP (two standard parallels) only: CF's
801  !! lambert_conformal_conic has no scale_factor attribute, so a 1SP
802  !! (scale-factor-based) WKT cannot be exactly represented without
803  !! trigonometric conversion MF6 has no library for. is_2sp=.false.
804  !! if a second standard parallel is not present (i.e. likely 1SP);
805  !! found=.false. if is_2sp but any other parameter cannot be located.
806  !<
807  subroutine wkt_lambert_conformal_conic_params(wkt, standard_parallel_1, &
808  standard_parallel_2, &
809  longitude_of_central_meridian, &
810  latitude_of_projection_origin, &
811  false_easting, false_northing, &
812  semi_major_axis, &
813  inverse_flattening, is_2sp, found)
814  character(len=*), intent(in) :: wkt
815  real(dp), intent(out) :: standard_parallel_1
816  real(dp), intent(out) :: standard_parallel_2
817  real(dp), intent(out) :: longitude_of_central_meridian
818  real(dp), intent(out) :: latitude_of_projection_origin
819  real(dp), intent(out) :: false_easting
820  real(dp), intent(out) :: false_northing
821  real(dp), intent(out) :: semi_major_axis
822  real(dp), intent(out) :: inverse_flattening
823  logical(LGP), intent(out) :: is_2sp
824  logical(LGP), intent(out) :: found
825  logical(LGP) :: f1, f2, f3, f4, f5, f6, f7
826 
827  standard_parallel_2 = wkt_find_param(wkt, 'standard_parallel_2', f2)
828  if (.not. f2) standard_parallel_2 = &
829  wkt_find_param(wkt, 'Latitude of 2nd standard parallel', f2)
830  is_2sp = f2
831  if (.not. is_2sp) then
832  found = .false.
833  return
834  end if
835 
836  standard_parallel_1 = wkt_find_param(wkt, 'standard_parallel_1', f1)
837  if (.not. f1) standard_parallel_1 = &
838  wkt_find_param(wkt, 'Latitude of 1st standard parallel', f1)
839 
840  longitude_of_central_meridian = wkt_find_param(wkt, 'central_meridian', f3)
841  if (.not. f3) longitude_of_central_meridian = &
842  wkt_find_param(wkt, 'Longitude of false origin', f3)
843 
844  latitude_of_projection_origin = wkt_find_param(wkt, 'latitude_of_origin', f4)
845  if (.not. f4) latitude_of_projection_origin = &
846  wkt_find_param(wkt, 'Latitude of false origin', f4)
847 
848  false_easting = wkt_find_param(wkt, 'false_easting', f5)
849  if (.not. f5) false_easting = &
850  wkt_find_param(wkt, 'Easting at false origin', f5)
851 
852  false_northing = wkt_find_param(wkt, 'false_northing', f6)
853  if (.not. f6) false_northing = &
854  wkt_find_param(wkt, 'Northing at false origin', f6)
855 
856  call wkt_find_ellipsoid(wkt, semi_major_axis, inverse_flattening, f7)
857 
858  found = f1 .and. f3 .and. f4 .and. f5 .and. f6 .and. f7
860 
861  !> @brief extract CF albers_conical_equal_area grid_mapping parameters
862  !!
863  !! Best-effort extraction from a WKT1 or WKT2 string -- MF6 has no
864  !! CRS parsing library. found=.false. if any parameter cannot be
865  !! located.
866  !<
867  subroutine wkt_albers_conical_equal_area_params(wkt, standard_parallel_1, &
868  standard_parallel_2, &
869  longitude_of_central_meridian, &
870  latitude_of_projection_origin, &
871  false_easting, &
872  false_northing, &
873  semi_major_axis, &
874  inverse_flattening, found)
875  character(len=*), intent(in) :: wkt
876  real(dp), intent(out) :: standard_parallel_1
877  real(dp), intent(out) :: standard_parallel_2
878  real(dp), intent(out) :: longitude_of_central_meridian
879  real(dp), intent(out) :: latitude_of_projection_origin
880  real(dp), intent(out) :: false_easting
881  real(dp), intent(out) :: false_northing
882  real(dp), intent(out) :: semi_major_axis
883  real(dp), intent(out) :: inverse_flattening
884  logical(LGP), intent(out) :: found
885  logical(LGP) :: f1, f2, f3, f4, f5, f6, f7
886 
887  standard_parallel_1 = wkt_find_param(wkt, 'standard_parallel_1', f1)
888  if (.not. f1) standard_parallel_1 = &
889  wkt_find_param(wkt, 'Latitude of 1st standard parallel', f1)
890 
891  standard_parallel_2 = wkt_find_param(wkt, 'standard_parallel_2', f2)
892  if (.not. f2) standard_parallel_2 = &
893  wkt_find_param(wkt, 'Latitude of 2nd standard parallel', f2)
894 
895  longitude_of_central_meridian = wkt_find_param(wkt, 'longitude_of_center', f3)
896  if (.not. f3) longitude_of_central_meridian = &
897  wkt_find_param(wkt, 'Longitude of false origin', f3)
898 
899  latitude_of_projection_origin = wkt_find_param(wkt, 'latitude_of_center', f4)
900  if (.not. f4) latitude_of_projection_origin = &
901  wkt_find_param(wkt, 'Latitude of false origin', f4)
902 
903  false_easting = wkt_find_param(wkt, 'false_easting', f5)
904  if (.not. f5) false_easting = &
905  wkt_find_param(wkt, 'Easting at false origin', f5)
906 
907  false_northing = wkt_find_param(wkt, 'false_northing', f6)
908  if (.not. f6) false_northing = &
909  wkt_find_param(wkt, 'Northing at false origin', f6)
910 
911  call wkt_find_ellipsoid(wkt, semi_major_axis, inverse_flattening, f7)
912 
913  found = f1 .and. f2 .and. f3 .and. f4 .and. f5 .and. f6 .and. f7
915 
916  !> @brief wrap a WKT2 PROJCRS in a derived CRS encoding grid rotation
917  !!
918  !! Builds a DerivedProjectedCRS wrapping the given WKT2 PROJCRS in an
919  !! EPSG:9624 (affine parametric transformation) deriving conversion
920  !! parameterized from xorigin/yorigin/angrot (degrees). Per ISO 19111,
921  !! a deriving conversion is directed base->derived, so the parameters
922  !! encode the world->local (inverse) rotation; a CRS-aware consumer
923  !! applies the inverse to resolve true position from local coordinates.
924  !! Returns '' if wkt is not recognized as a WKT2 PROJCRS -- a
925  !! best-effort structural check only, as MF6 has no CRS parsing
926  !! library and does not validate the WKT beyond this.
927  !<
928  function wrap_rotated_crs(wkt, xorigin, yorigin, angrot) result(derived_wkt)
929  use inputoutputmodule, only: upcase
930  character(len=*), intent(in) :: wkt
931  real(dp), intent(in) :: xorigin, yorigin, angrot
932  character(len=LENBIGLINE) :: derived_wkt
933  character(len=LENBIGLINE) :: wkt_trim, wkt_upper, base_body, conversion
934  character(len=30) :: a0s, a1s, a2s, b0s, b1s, b2s
935  integer :: nstart, i, depth, closures, iend, wkt_len
936  real(dp) :: ang, a0, a1, a2, b0, b1, b2
937 
938  derived_wkt = ''
939 
940  ! best-effort check that wkt is a WKT2 PROJCRS
941  wkt_trim = trim(adjustl(wkt))
942  wkt_len = len_trim(wkt_trim)
943  wkt_upper = wkt_trim
944  call upcase(wkt_upper)
945  if (wkt_len < 9 .or. wkt_upper(1:8) /= 'PROJCRS[') return
946 
947  ! locate the end of PROJCRS's 2nd top-level child (the mandatory
948  ! BASEGEOGCRS/BASEGEODCRS and CONVERSION elements, in that order),
949  ! by tracking bracket depth from just inside the opening '['
950  nstart = 9
951  depth = 0
952  closures = 0
953  iend = 0
954  do i = nstart, wkt_len
955  if (wkt_trim(i:i) == '[') then
956  depth = depth + 1
957  else if (wkt_trim(i:i) == ']') then
958  depth = depth - 1
959  if (depth == 0) then
960  closures = closures + 1
961  if (closures == 2) then
962  iend = i
963  exit
964  end if
965  end if
966  end if
967  end do
968  if (iend == 0) return
969  base_body = wkt_trim(nstart:iend)
970 
971  ! EPSG:9624 affine parameters, base(world) -> derived(local grid)
972  ang = angrot * dpio180
973  a0 = -(xorigin * cos(ang) + yorigin * sin(ang))
974  a1 = cos(ang)
975  a2 = sin(ang)
976  b0 = xorigin * sin(ang) - yorigin * cos(ang)
977  b1 = -sin(ang)
978  b2 = cos(ang)
979  write (a0s, '(es22.15)') a0
980  write (a1s, '(es22.15)') a1
981  write (a2s, '(es22.15)') a2
982  write (b0s, '(es22.15)') b0
983  write (b1s, '(es22.15)') b1
984  write (b2s, '(es22.15)') b2
985 
986  conversion = 'DERIVINGCONVERSION["MODFLOW 6 grid rotation",'// &
987  'METHOD["Affine parametric transformation",'// &
988  'ID["EPSG",9624]],'// &
989  'PARAMETER["A0",'//trim(adjustl(a0s))// &
990  ',LENGTHUNIT["metre",1]],'// &
991  'PARAMETER["A1",'//trim(adjustl(a1s))// &
992  ',SCALEUNIT["unity",1]],'// &
993  'PARAMETER["A2",'//trim(adjustl(a2s))// &
994  ',SCALEUNIT["unity",1]],'// &
995  'PARAMETER["B0",'//trim(adjustl(b0s))// &
996  ',LENGTHUNIT["metre",1]],'// &
997  'PARAMETER["B1",'//trim(adjustl(b1s))// &
998  ',SCALEUNIT["unity",1]],'// &
999  'PARAMETER["B2",'//trim(adjustl(b2s))// &
1000  ',SCALEUNIT["unity",1]]]'
1001 
1002  derived_wkt = 'DERIVEDPROJCRS["MODFLOW 6 rotated grid CRS",'// &
1003  'BASEPROJCRS['//trim(base_body)//'],'// &
1004  trim(conversion)//','// &
1005  'CS[Cartesian,2],'// &
1006  'AXIS["easting (X)",east,ORDER[1],LENGTHUNIT["metre",1]],'// &
1007  'AXIS["northing (Y)",north,ORDER[2],LENGTHUNIT["metre",1]]]'
1008  end function wrap_rotated_crs
1009 
1010  !> @brief destroy model netcdf export object
1011  !<
1012  subroutine export_destroy(this)
1014  use simvariablesmodule, only: idm_context
1015  class(ncmodelexporttype), intent(inout) :: this
1016  ! override in derived class
1017  deallocate (this%deflate)
1018  deallocate (this%shuffle)
1019  deallocate (this%input_attr)
1020  deallocate (this%chunk_time)
1021  ! Deallocate idm memory
1022  if (this%ncf_mempath /= '') then
1023  call memorystore_remove(this%modelname, 'NCF', idm_context)
1024  end if
1025  end subroutine export_destroy
1026 
1027 end module ncmodelexportmodule
subroutine init()
Definition: GridSorting.f90:25
abstract interfaces for model netcdf export type
Definition: NCModel.f90:123
This module contains simulation constants.
Definition: Constants.f90:9
integer(i4b), parameter linelength
maximum length of a standard line
Definition: Constants.f90:45
integer(i4b), parameter lencomponentname
maximum length of a component name
Definition: Constants.f90:18
@ mvalidate
validation mode - do not run time steps
Definition: Constants.f90:206
integer(i4b), parameter lenmodelname
maximum length of the model name
Definition: Constants.f90:22
integer(i4b), parameter lenbigline
maximum length of a big line
Definition: Constants.f90:15
@ disu
DISV6 discretization.
Definition: Constants.f90:158
@ dis
DIS6 discretization.
Definition: Constants.f90:156
@ disv
DISU6 discretization.
Definition: Constants.f90:157
integer(i4b), parameter lenvarname
maximum length of a variable name
Definition: Constants.f90:17
real(dp), parameter dpio180
real constant
Definition: Constants.f90:130
real(dp), parameter dzero
real constant zero
Definition: Constants.f90:65
integer(i4b), parameter lenmempath
maximum length of the memory path
Definition: Constants.f90:27
logical function, public idm_multi_package(component, subcomponent)
Input definition module.
This module contains the InputLoadTypeModule.
subroutine, public lowcase(word)
Convert to lower case.
subroutine, public upcase(word)
Convert to upper case.
This module defines variable data types.
Definition: kind.f90:8
Load context for IDM generic dynamic loaders.
Definition: LoadContext.f90:10
character(len=lenvarname) function, public rsv_name(mf6varname)
create read state variable name
character(len=lenmemseparator), parameter mempathseparator
used to build up the memory address for the stored variables
character(len=lenmempath) function create_mem_path(component, subcomponent, context)
returns the path to the memory object
subroutine, public memorystore_remove(component, subcomponent, context)
This module contains the ModflowInputModule.
Definition: ModflowInput.f90:9
This module contains the NCModelExportModule.
Definition: NCModel.f90:8
character(len=lenbigline) function, public wrap_rotated_crs(wkt, xorigin, yorigin, angrot)
wrap a WKT2 PROJCRS in a derived CRS encoding grid rotation
Definition: NCModel.f90:929
class(exportpackagetype) function, pointer export_get(this, idx)
retrieve dynamic export object from package list
Definition: NCModel.f90:404
character(len=linelength) function, public wkt_to_cf_gridmapping(wkt)
map a WKT string to a CF-1.13 grid_mapping_name
Definition: NCModel.f90:574
character(len=linelength) function, public export_varname(pkgname, tagname, mempath, layer, iaux)
build netcdf variable name
Definition: NCModel.f90:455
integer(i4b) function istp(this)
step index for timeseries data
Definition: NCModel.f90:440
subroutine epkg_init(this, mf6_input, mshape, naux, param_names, nparam)
initialize dynamic package export object
Definition: NCModel.f90:152
@, public netcdf_structured
netcdf structrured export
Definition: NCModel.f90:37
subroutine export_destroy(this)
destroy model netcdf export object
Definition: NCModel.f90:1013
subroutine export_init(this, modelname, modeltype, modelfname, nc_fname, disenum, nctype, iout)
initialization of model netcdf export
Definition: NCModel.f90:283
subroutine, public wkt_lambert_conformal_conic_params(wkt, standard_parallel_1, standard_parallel_2, longitude_of_central_meridian, latitude_of_projection_origin, false_easting, false_northing, semi_major_axis, inverse_flattening, is_2sp, found)
extract CF lambert_conformal_conic grid_mapping parameters
Definition: NCModel.f90:814
subroutine, public wkt_transverse_mercator_params(wkt, longitude_of_central_meridian, latitude_of_projection_origin, scale_factor_at_central_meridian, false_easting, false_northing, semi_major_axis, inverse_flattening, found)
extract CF transverse_mercator grid_mapping parameters
Definition: NCModel.f90:763
real(dp) function wkt_find_param(wkt, name, found)
find the first number following a quoted parameter name
Definition: NCModel.f90:657
@, public netcdf_mesh2d
netcdf ugrid layered mesh export
Definition: NCModel.f90:38
@, public netcdf_undef
undefined netcdf export type
Definition: NCModel.f90:36
subroutine set(this, modelname, modeltype, modelfname, nctype, disenum)
set netcdf file scoped attributes
Definition: NCModel.f90:207
character(len=linelength) function input_attribute(this, pkgname, idt)
build modflow_input attribute string
Definition: NCModel.f90:422
subroutine epkg_destroy(this)
destroy dynamic package export object
Definition: NCModel.f90:199
subroutine wkt_find_ellipsoid(wkt, semi_major_axis, inverse_flattening, found)
find semi-major axis and inverse flattening from a WKT string
Definition: NCModel.f90:699
character(len=linelength) function, public export_longname(longname, pkgname, tagname, mempath, layer, iaux, component_type, subcomponent_type)
build netcdf variable longname
Definition: NCModel.f90:496
subroutine export_input(this)
netcdf dynamic package period export
Definition: NCModel.f90:549
subroutine, public wkt_albers_conical_equal_area_params(wkt, standard_parallel_1, standard_parallel_2, longitude_of_central_meridian, latitude_of_projection_origin, false_easting, false_northing, semi_major_axis, inverse_flattening, found)
extract CF albers_conical_equal_area grid_mapping parameters
Definition: NCModel.f90:875
This module contains simulation methods.
Definition: Sim.f90:10
subroutine, public store_error(msg, terminate)
Store an error message.
Definition: Sim.f90:92
subroutine, public store_error_filename(filename, terminate)
Store the erroring file name.
Definition: Sim.f90:204
This module contains simulation variables.
Definition: SimVariables.f90:9
character(len=maxcharlen) errmsg
error message string
character(len=linelength) idm_context
integer(i4b) isim_mode
simulation mode
This module contains the SourceCommonModule.
Definition: SourceCommon.f90:7
logical(lgp) function, public filein_fname(filename, tagname, input_mempath, input_fname)
enforce and set a single input filename provided via FILEIN keyword
integer(i4b), dimension(:), pointer, public, contiguous nstp
number of time steps in each stress period
Definition: tdis.f90:42
character(len=lendatetime), pointer, public datetime0
starting date and time for the simulation
Definition: tdis.f90:44
integer(i4b), pointer, public kstp
current time step number
Definition: tdis.f90:27
integer(i4b), pointer, public kper
current stress period number
Definition: tdis.f90:26
integer(i4b), pointer, public nper
number of stress period
Definition: tdis.f90:24
This module contains version information.
Definition: version.f90:7
character(len= *), parameter fullversion
Definition: version.f90:24
This class is used to store a single deferred-length character string. It was designed to work in an ...
Definition: CharString.f90:23
Input parameter definition. Describes an input parameter.
type for storing a dynamic package load list
A generic heterogeneous doubly-linked list.
Definition: List.f90:14
Pointer type for read state variable.
Definition: LoadContext.f90:41
derived type for storing input definition for a file
abstract type for model netcdf export type
Definition: NCModel.f90:111
netcdf export attribute annotations
Definition: NCModel.f90:59
base class for an export model
Definition: NCModel.f90:75