MODFLOW 6  version 6.7.0.dev0
USGS Modular Hydrologic Model
GeomUtil.f90
Go to the documentation of this file.
2  use kindmodule, only: i4b, dp, lgp
3  use errorutilmodule, only: pstop
4  use constantsmodule, only: dzero, dsame, done, dtwo, dhalf, &
6 
7  implicit none
8  private
9  public :: between, point_in_polygon, &
13 contains
14 
15  !> @brief Check if a value is between two other values (inclusive).
16  logical function between(x, a, b)
17  real(dp), intent(in) :: x, a, b
18  between = ((x >= a .and. x <= b) .or. (x <= a .and. x >= b))
19  end function between
20 
21  !> @brief Check if a point is within a polygon.
22  !!
23  !! Vertices and edge points are considered in the polygon.
24  !! Adapted from https://stackoverflow.com/a/63436180/6514033,
25  !<
26  logical function point_in_polygon(x, y, poly)
27  ! dummy
28  real(dp), intent(in) :: x !< x point coordinate
29  real(dp), intent(in) :: y !< y point coordinate
30  real(dp), allocatable, intent(in) :: poly(:, :) !< polygon vertices (column-major indexing)
31  ! local
32  integer(I4B) :: i, ii, num_verts
33  real(dp) :: xa, xb, ya, yb, c = 0.0_dp
34 
35  point_in_polygon = .false.
36  num_verts = size(poly, 2)
37  xa = poly(1, num_verts)
38  ya = poly(2, num_verts)
39 
40  do i = 0, num_verts - 1
41  ii = mod(i, num_verts) + 1
42  xb = poly(1, ii)
43  yb = poly(2, ii)
44 
45  if ((x == xa .and. y == ya) .or. &
46  (x == xb .and. y == yb)) then
47  ! on vertex
48  point_in_polygon = .true.
49  exit
50  else if (ya == yb .and. &
51  y == ya .and. &
52  between(x, xa, xb)) then
53  ! on horizontal edge
54  point_in_polygon = .true.
55  exit
56  else if (between(y, ya, yb)) then
57  if ((y == ya .and. yb >= ya) .or. &
58  (y == yb .and. ya >= yb)) then
59  xa = xb
60  ya = yb
61  cycle
62  end if
63  ! cross product
64  c = (xa - x) * (yb - y) - (xb - x) * (ya - y)
65  if (c == 0.0_dp) then
66  ! on edge
67  point_in_polygon = .true.
68  exit
69  else if ((ya < yb) .eqv. (c > 0)) then
70  ! ray intersection
72  end if
73  end if
74 
75  xa = xb
76  ya = yb
77  end do
78  end function point_in_polygon
79 
80  !> @brief Get node number, given layer, row, and column indices
81  !! for a structured grid. If any argument is invalid return -1.
82  function get_node(ilay, irow, icol, nlay, nrow, ncol)
83  integer(I4B), intent(in) :: ilay, irow, icol, nlay, nrow, ncol
84  integer(I4B) :: get_node
85 
86  if (nlay > 0 .and. nrow > 0 .and. ncol > 0 .and. &
87  ilay > 0 .and. ilay <= nlay .and. &
88  irow > 0 .and. irow <= nrow .and. &
89  icol > 0 .and. icol <= ncol) then
90  get_node = &
91  icol + ncol * (irow - 1) + (ilay - 1) * nrow * ncol
92  else
93  get_node = -1
94  end if
95  end function get_node
96 
97  !> @brief Get row, column and layer indices from node number and grid
98  !! dimensions. If nodenumber is invalid, irow, icol, and ilay are -1.
99  subroutine get_ijk(nodenumber, nrow, ncol, nlay, irow, icol, ilay)
100  ! -- dummy variables
101  integer(I4B), intent(in) :: nodenumber
102  integer(I4B), intent(in) :: nrow
103  integer(I4B), intent(in) :: ncol
104  integer(I4B), intent(in) :: nlay
105  integer(I4B), intent(out) :: irow
106  integer(I4B), intent(out) :: icol
107  integer(I4B), intent(out) :: ilay
108  ! -- local variables
109  integer(I4B) :: nodes
110  integer(I4B) :: ij
111 
112  nodes = nlay * nrow * ncol
113  if (nodenumber < 1 .or. nodenumber > nodes) then
114  irow = -1
115  icol = -1
116  ilay = -1
117  else
118  ilay = (nodenumber - 1) / (ncol * nrow) + 1
119  ij = nodenumber - (ilay - 1) * ncol * nrow
120  irow = (ij - 1) / ncol + 1
121  icol = ij - (irow - 1) * ncol
122  end if
123  end subroutine get_ijk
124 
125  !> @brief Get layer index and within-layer node index from node number
126  !! and grid dimensions. If nodenumber is invalid, icpl and ilay are -1.
127  subroutine get_jk(nodenumber, ncpl, nlay, icpl, ilay)
128  ! -- dummy variables
129  integer(I4B), intent(in) :: nodenumber
130  integer(I4B), intent(in) :: ncpl
131  integer(I4B), intent(in) :: nlay
132  integer(I4B), intent(out) :: icpl
133  integer(I4B), intent(out) :: ilay
134  ! -- local variables
135  integer(I4B) :: nodes
136 
137  nodes = ncpl * nlay
138  if (nodenumber < 1 .or. nodenumber > nodes) then
139  icpl = -1
140  ilay = -1
141  else
142  ilay = (nodenumber - 1) / ncpl + 1
143  icpl = nodenumber - (ilay - 1) * ncpl
144  end if
145  end subroutine get_jk
146 
147  !> @brief Skew a 2D vector along the x-axis.
148  pure function skew(v, s, invert) result(res)
149  ! -- dummy
150  real(dp), intent(in) :: v(2) !< vector
151  real(dp), intent(in) :: s(3) !< skew matrix entries (top left, top right, bottom right)
152  logical(LGP), intent(in), optional :: invert
153  real(dp) :: res(2)
154  ! -- local
155  logical(LGP) :: linvert
156  real(dp) :: sxx, sxy, syy
157 
158  ! -- process optional arguments
159  if (present(invert)) then
160  linvert = invert
161  else
162  linvert = .false.
163  end if
164 
165  sxx = s(1)
166  sxy = s(2)
167  syy = s(3)
168  if (.not. linvert) then
169  res(1) = sxx * v(1) + sxy * v(2)
170  res(2) = syy * v(2)
171  else
172  res(2) = v(2) / syy
173  res(1) = (v(1) - sxy * res(2)) / sxx
174  end if
175  end function skew
176 
177  !> @brief Apply a 3D translation and optional 2D rotation to coordinates.
178  subroutine transform(xin, yin, zin, &
179  xout, yout, zout, &
180  xorigin, yorigin, zorigin, &
181  sinrot, cosrot, &
182  invert)
183  ! -- dummy
184  real(dp) :: xin, yin, zin !< input coordinates
185  real(dp) :: xout, yout, zout !< output coordinates
186  real(dp), optional :: xorigin, yorigin, zorigin !< origin coordinates
187  real(dp), optional :: sinrot, cosrot !< sine and cosine of rotation
188  logical(LGP), optional :: invert !< whether to invert
189  ! -- local
190  logical(LGP) :: ltranslate, lrotate, linvert
191  real(dp) :: x, y
192  real(dp) :: lxorigin, lyorigin, lzorigin
193  real(dp) :: lsinrot, lcosrot
194 
195  ! -- Process option arguments and set defaults and flags
196  call defaults(lxorigin, lyorigin, lzorigin, &
197  lsinrot, lcosrot, linvert, &
198  ltranslate, lrotate, &
199  xorigin, yorigin, zorigin, &
200  sinrot, cosrot, invert)
201 
202  ! -- Apply transformation or its inverse
203  if (.not. linvert) then
204  ! -- Apply transformation to coordinates
205  if (ltranslate) then
206  xout = xin - lxorigin
207  yout = yin - lyorigin
208  zout = zin - lzorigin
209  else
210  xout = lxorigin
211  yout = lyorigin
212  zout = lzorigin
213  end if
214  if (lrotate) then
215  x = xout
216  y = yout
217  xout = x * lcosrot + y * lsinrot
218  yout = -x * lsinrot + y * lcosrot
219  end if
220  else
221  ! -- Apply inverse of transformation to coordinates
222  if (lrotate) then
223  x = xin * lcosrot - yin * lsinrot
224  y = xin * lsinrot + yin * lcosrot
225  else
226  x = xin
227  y = yin
228  end if
229  if (ltranslate) then
230  xout = x + lxorigin
231  yout = y + lyorigin
232  zout = zin + lzorigin
233  end if
234  end if
235  end subroutine transform
236 
237  !> @brief Apply a 3D translation and 2D rotation to an existing transformation.
238  subroutine compose(xorigin, yorigin, zorigin, &
239  sinrot, cosrot, &
240  xorigin_new, yorigin_new, zorigin_new, &
241  sinrot_new, cosrot_new, &
242  invert)
243  ! -- dummy
244  real(dp) :: xorigin, yorigin, zorigin !< origin coordinates (original)
245  real(dp) :: sinrot, cosrot !< sine and cosine of rotation (original)
246  real(dp), optional :: xorigin_new, yorigin_new, zorigin_new !< origin coordinates (new)
247  real(dp), optional :: sinrot_new, cosrot_new !< sine and cosine of rotation (new)
248  logical(LGP), optional :: invert !< whether to invert
249  ! -- local
250  logical(LGP) :: ltranslate, lrotate, linvert
251  real(dp) :: xorigin_add, yorigin_add, zorigin_add
252  real(dp) :: sinrot_add, cosrot_add
253  real(dp) :: x0, y0, z0, s0, c0
254 
255  ! -- Process option arguments and set defaults and flags
256  call defaults(xorigin_add, yorigin_add, zorigin_add, &
257  sinrot_add, cosrot_add, linvert, &
258  ltranslate, lrotate, &
259  xorigin_new, yorigin_new, zorigin_new, &
260  sinrot_new, cosrot_new, invert)
261 
262  ! -- Copy existing transformation into working copy
263  x0 = xorigin
264  y0 = yorigin
265  z0 = zorigin
266  s0 = sinrot
267  c0 = cosrot
268 
269  ! -- Modify transformation
270  if (.not. linvert) then
271  ! -- Apply additional transformation to existing transformation
272  if (ltranslate) then
273  ! -- Calculate modified origin, XOrigin + R^T XOrigin_add, where
274  ! -- XOrigin and XOrigin_add are the existing and additional origin
275  ! -- vectors, respectively, and R^T is the transpose of the existing
276  ! -- rotation matrix
277  call transform(xorigin_add, yorigin_add, zorigin_add, &
278  xorigin, yorigin, zorigin, &
279  x0, y0, z0, s0, c0, .true.)
280  end if
281  if (lrotate) then
282  ! -- Calculate modified rotation matrix (represented by sinrot
283  ! -- and cosrot) as R_add R, where R and R_add are the existing
284  ! -- and additional rotation matrices, respectively
285  sinrot = cosrot_add * s0 + sinrot_add * c0
286  cosrot = cosrot_add * c0 - sinrot_add * s0
287  end if
288  else
289  ! -- Apply inverse of additional transformation to existing transformation
290  !
291  ! -- Calculate modified origin, R^T (XOrigin + R_add XOrigin_add), where
292  ! -- XOrigin and XOrigin_add are the existing and additional origin
293  ! -- vectors, respectively, R^T is the transpose of the existing rotation
294  ! -- matrix, and R_add is the additional rotation matrix
295  if (ltranslate) then
296  call transform(-xorigin_add, -yorigin_add, zorigin_add, &
297  x0, y0, z0, xorigin, yorigin, zorigin, &
298  -sinrot_add, cosrot_add, .true.)
299  end if
300  xorigin = c0 * x0 - s0 * y0
301  yorigin = s0 * x0 + c0 * y0
302  zorigin = z0
303  if (lrotate) then
304  ! -- Calculate modified rotation matrix (represented by sinrot
305  ! -- and cosrot) as R_add^T R, where R and R_add^T are the existing
306  ! -- rotation matrix and the transpose of the additional rotation
307  ! -- matrix, respectively
308  sinrot = cosrot_add * s0 - sinrot_add * c0
309  cosrot = cosrot_add * c0 + sinrot_add * s0
310  end if
311  end if
312  end subroutine compose
313 
314  !> @brief Process arguments and set defaults. Internal use only.
315  subroutine defaults(xorigin, yorigin, zorigin, &
316  sinrot, cosrot, &
317  invert, translate, rotate, &
318  xorigin_opt, yorigin_opt, zorigin_opt, &
319  sinrot_opt, cosrot_opt, invert_opt)
320  ! -- dummy
321  real(DP) :: xorigin, yorigin, zorigin
322  real(DP) :: sinrot, cosrot
323  logical(LGP) :: invert, translate, rotate
324  real(DP), optional :: xorigin_opt, yorigin_opt, zorigin_opt
325  real(DP), optional :: sinrot_opt, cosrot_opt
326  logical(LGP), optional :: invert_opt
327 
328  translate = .false.
329  xorigin = dzero
330  if (present(xorigin_opt)) then
331  xorigin = xorigin_opt
332  translate = .true.
333  end if
334  yorigin = dzero
335  if (present(yorigin_opt)) then
336  yorigin = yorigin_opt
337  translate = .true.
338  end if
339  zorigin = dzero
340  if (present(zorigin_opt)) then
341  zorigin = zorigin_opt
342  translate = .true.
343  end if
344  rotate = .false.
345  sinrot = dzero
346  cosrot = done
347  if (present(sinrot_opt)) then
348  sinrot = sinrot_opt
349  if (present(cosrot_opt)) then
350  cosrot = cosrot_opt
351  else
352  ! -- If sinrot_opt is specified but cosrot_opt is not,
353  ! -- default to corresponding non-negative cosrot_add
354  cosrot = dsqrt(done - sinrot * sinrot)
355  end if
356  rotate = .true.
357  else if (present(cosrot_opt)) then
358  cosrot = cosrot_opt
359  ! -- cosrot_opt is specified but sinrot_opt is not, so
360  ! -- default to corresponding non-negative sinrot_add
361  sinrot = dsqrt(done - cosrot * cosrot)
362  rotate = .true.
363  end if
364  invert = .false.
365  if (present(invert_opt)) invert = invert_opt
366  end subroutine defaults
367 
368  !> @brief Calculate polygon area, with vertices given in CW or CCW order.
369  function area(xv, yv, cw) result(a)
370  ! dummy
371  real(dp), dimension(:), intent(in) :: xv
372  real(dp), dimension(:), intent(in) :: yv
373  logical(LGP), intent(in), optional :: cw
374  ! result
375  real(dp) :: a
376  integer(I4B) :: s
377 
378  if (present(cw)) then
379  if (cw) then
380  s = 1
381  else
382  s = -1
383  end if
384  else
385  s = 1
386  end if
387 
388  a = -dhalf * sum(xv(:) * cshift(yv(:), s) - cshift(xv(:), s) * yv(:))
389 
390  end function area
391 
392  !> @brief Find the lateral face shared by two cells.
393  !!
394  !! Find the lateral (x-y plane) face shared by the given cells.
395  !! The iface return argument will be 0 if they share no such face,
396  !! otherwise the index of the shared face in cell 1's vertex array,
397  !! where face N connects vertex N to vertex N + 1 going clockwise.
398  !!
399  !! Note: assumes the cells are convex and share at most 2 vertices
400  !! and that both vertex arrays are oriented clockwise.
401  !<
402  subroutine shared_face(iverts1, iverts2, iface)
403  integer(I4B), dimension(:) :: iverts1
404  integer(I4B), dimension(:) :: iverts2
405  integer(I4B), intent(out) :: iface
406  integer(I4B) :: nv1
407  integer(I4B) :: nv2
408  integer(I4B) :: il1, iil1
409  integer(I4B) :: il2, iil2
410  logical(LGP) :: found
411  logical(LGP) :: wrapped
412 
413  iface = 0
414  found = .false.
415  nv1 = size(iverts1)
416  nv2 = size(iverts2)
417  wrapped = iverts1(1) == iverts1(nv1)
418 
419  ! Find a vertex shared by the cells, then check the adjacent faces.
420  ! If the cells share a face, it must be one of these. When looking
421  ! forward in the 1st cell's vertices, look backwards in the 2nd's,
422  ! and vice versa, since a clockwise face in cell 1 must correspond
423  ! to a counter-clockwise face in cell 2.
424  outerloop: do il1 = 1, nv1 - 1
425  do il2 = 1, nv2 - 1
426  if (iverts1(il1) == iverts2(il2)) then
427 
428  iil1 = il1 + 1
429  if (il2 == 1) then
430  iil2 = nv2
431  if (wrapped) iil2 = iil2 - 1
432  else
433  iil2 = il2 - 1
434  end if
435  if (iverts1(iil1) == iverts2(iil2)) then
436  found = .true.
437  iface = il1
438  exit outerloop
439  end if
440 
441  iil2 = il2 + 1
442  if (il1 == 1) then
443  iil1 = nv1
444  if (wrapped) iil1 = iil1 - 1
445  else
446  iil1 = il1 - 1
447  end if
448  if (iverts1(iil1) == iverts2(iil2)) then
449  found = .true.
450  iface = iil1
451  exit outerloop
452  end if
453 
454  end if
455  end do
456  if (found) exit
457  end do outerloop
458  end subroutine shared_face
459 
460  !> @brief Clamp barycentric coordinates to the interior of a triangle,
461  !! with optional padding some minimum distance from any face.
462  !!
463  !! This routine requires 0 <= tol <= 1/3 and 1 = alpha + beta + gamma.
464  !<
465  subroutine clamp_bary(alpha, beta, gamma, pad)
466  ! dummy
467  real(dp), intent(inout) :: alpha
468  real(dp), intent(inout) :: beta
469  real(dp), intent(out) :: gamma
470  real(dp), intent(in), optional :: pad
471  ! local
472  real(dp) :: lolimit
473  real(dp) :: hilimit
474  real(dp) :: delta
475  real(dp) :: lpad
476 
477  if (present(pad)) then
478  lpad = pad
479  if (pad < dzero .or. pad > donethird) &
480  call pstop(1, "pad must be between 0 and 1/3, inclusive")
481  else
482  lpad = dzero
483  end if
484 
485  gamma = done - alpha - beta
486  lolimit = lpad
487  hilimit = done - dtwo * lpad
488  ! Check alpha coordinate against lower limit
489  if (alpha < lolimit) then
490  ! Alpha is too low, so nudge alpha to lower limit; this is a move
491  ! parallel to the "alpha axis," which also changes gamma
492  alpha = lolimit
493  gamma = done - alpha - beta
494  ! Check beta coordinate against lower limit (which in this
495  ! case is equivalent to checking gamma coordinate against
496  ! upper limit)
497  if (beta < lolimit) then
498  ! Beta is too low (gamma is too high), so nudge beta to lower limit;
499  ! this is a move parallel to the "beta axis," which also changes gamma
500  beta = lolimit
501  gamma = hilimit
502  ! Check beta coordinate against upper limit (which in this
503  ! case is equivalent to checking gamma coordinate against
504  ! lower limit)
505  else if (beta > hilimit) then
506  ! Beta is too high (gamma is too low), so nudge beta to lower limit;
507  ! this is a move parallel to the "beta axis," which also changes gamma
508  beta = hilimit
509  gamma = lolimit
510  end if
511  end if
512  ! Check beta coordinate against lower limit. (If alpha coordinate
513  ! was nudged to lower limit, beta and gamma coordinates have also
514  ! been adjusted as necessary to place particle within subcell, and
515  ! subsequent checks on beta and gamma will evaluate to false, and
516  ! no further adjustments will be made.)
517  if (beta < lolimit) then
518  ! Beta is too low, so nudge beta to lower limit; this is a move
519  ! parallel to the "beta axis," which also changes gamma
520  beta = lolimit
521  gamma = done - alpha - beta
522  ! Check alpha coordinate against lower limit (which in this
523  ! case is equivalent to checking gamma coordinate against
524  ! upper limit)
525  if (alpha < lolimit) then
526  ! Alpha is too low (gamma is too high), so nudge alpha to lower limit;
527  ! this is a move parallel to the "alpha axis," which also changes gamma
528  alpha = lolimit
529  gamma = hilimit
530  ! Check alpha coordinate against upper limit (which in this
531  ! case is equivalent to checking gamma coordinate against
532  ! lower limit)
533  else if (alpha > hilimit) then
534  ! Alpha is too high (gamma is too low), so nudge alpha to lower limit;
535  ! this is a move parallel to the "alpha axis," which also changes gamma
536  alpha = hilimit
537  gamma = lolimit
538  end if
539  end if
540  ! Check gamma coordinate against lower limit.(If alpha and/or beta
541  ! coordinate was nudged to lower limit, gamma coordinate has also
542  ! been adjusted as necessary to place particle within subcell, and
543  ! subsequent check on gamma will evaluate to false, and no further
544  ! adjustment will be made.)
545  if (gamma < lolimit) then
546  ! Gamma is too low, so nudge gamma to lower limit; this is a move
547  ! parallel to the "gamma axis," which also changes alpha and beta
548  delta = dhalf * (lolimit - gamma)
549  gamma = lpad
550  alpha = alpha - delta
551  beta = beta - delta
552  end if
553  end subroutine clamp_bary
554 
555 end module geomutilmodule
This module contains simulation constants.
Definition: Constants.f90:9
real(dp), parameter dsame
real constant for values that are considered the same based on machine precision
Definition: Constants.f90:122
real(dp), parameter dep3
real constant 1000
Definition: Constants.f90:88
real(dp), parameter donethird
real constant 1/3
Definition: Constants.f90:67
real(dp), parameter dhalf
real constant 1/2
Definition: Constants.f90:68
real(dp), parameter dzero
real constant zero
Definition: Constants.f90:65
real(dp), parameter dtwo
real constant 2
Definition: Constants.f90:79
real(dp), parameter done
real constant 1
Definition: Constants.f90:76
subroutine pstop(status, message)
Stop the program, optionally specifying an error status code.
Definition: ErrorUtil.f90:24
subroutine, public shared_face(iverts1, iverts2, iface)
Find the lateral face shared by two cells.
Definition: GeomUtil.f90:403
subroutine, public transform(xin, yin, zin, xout, yout, zout, xorigin, yorigin, zorigin, sinrot, cosrot, invert)
Apply a 3D translation and optional 2D rotation to coordinates.
Definition: GeomUtil.f90:183
integer(i4b) function, public get_node(ilay, irow, icol, nlay, nrow, ncol)
Get node number, given layer, row, and column indices for a structured grid. If any argument is inval...
Definition: GeomUtil.f90:83
subroutine defaults(xorigin, yorigin, zorigin, sinrot, cosrot, invert, translate, rotate, xorigin_opt, yorigin_opt, zorigin_opt, sinrot_opt, cosrot_opt, invert_opt)
Process arguments and set defaults. Internal use only.
Definition: GeomUtil.f90:320
logical function, public between(x, a, b)
Check if a value is between two other values (inclusive).
Definition: GeomUtil.f90:17
subroutine, public clamp_bary(alpha, beta, gamma, pad)
Clamp barycentric coordinates to the interior of a triangle, with optional padding some minimum dista...
Definition: GeomUtil.f90:466
real(dp) function, public area(xv, yv, cw)
Calculate polygon area, with vertices given in CW or CCW order.
Definition: GeomUtil.f90:370
subroutine, public compose(xorigin, yorigin, zorigin, sinrot, cosrot, xorigin_new, yorigin_new, zorigin_new, sinrot_new, cosrot_new, invert)
Apply a 3D translation and 2D rotation to an existing transformation.
Definition: GeomUtil.f90:243
logical function, public point_in_polygon(x, y, poly)
Check if a point is within a polygon.
Definition: GeomUtil.f90:27
subroutine, public get_ijk(nodenumber, nrow, ncol, nlay, irow, icol, ilay)
Get row, column and layer indices from node number and grid dimensions. If nodenumber is invalid,...
Definition: GeomUtil.f90:100
pure real(dp) function, dimension(2), public skew(v, s, invert)
Skew a 2D vector along the x-axis.
Definition: GeomUtil.f90:149
subroutine, public get_jk(nodenumber, ncpl, nlay, icpl, ilay)
Get layer index and within-layer node index from node number and grid dimensions. If nodenumber is in...
Definition: GeomUtil.f90:128
This module defines variable data types.
Definition: kind.f90:8