0.6.2-dev0
FORCES
FORtran lib for Comp. Env. Sys.
Loading...
Searching...
No Matches
mo_poly.f90
Go to the documentation of this file.
1!> \file mo_poly.f90
2!> \brief \copybrief mo_poly
3!> \details \copydetails mo_poly
4
5
6
7!> \brief Polygon calculations.
8!> \details
9!! This module determines wether a 2D point lies inside, outside, or
10!! on the vertice/edge of a 2D polygon
11!! and is part of the UFZ CHS Fortran library.
12!> \changelog
13!! - Juliane Mai, July 2012
14!! - written
15!! - Maren Goehler, July 2012
16!! - area & center of mass
17!> \author Juliane Mai
18!> \date Jul 2012
19!> \copyright Copyright 2005-\today, the CHS Developers, Sabine Attinger: All rights reserved.
20!! FORCES is released under the LGPLv3+ license \license_note
21module mo_poly
22
23 use mo_kind, only: i4, sp, dp
24 use mo_utils, only: eq, ge, le, ne
25
26 implicit none
27
28 public :: areapoly
29
30 !> \copydoc mo_poly::areapoly_sp
31 interface areapoly
32 module procedure areapoly_sp
33 module procedure areapoly_dp
34 end interface
35
36 public :: center_of_mass
37
38 !> \copydoc mo_poly::center_of_mass_sp
40 module procedure center_of_mass_sp
41 module procedure center_of_mass_dp
42 end interface
43
44 public :: inpoly
45
46 !> \copydoc mo_poly::inpoly_sp
47 interface inpoly
48 module procedure inpoly_sp
49 module procedure inpoly_dp
50 end interface
51
52 public :: orientpoly
53
54 !> \copydoc mo_poly::orientpoly_sp
55 interface orientpoly
56 module procedure orientpoly_sp
57 module procedure orientpoly_dp
58 end interface
59
60 public :: mod_pole
61
62 !> \copydoc mo_poly::mod_pole_sp
63 interface mod_pole
64 module procedure mod_pole_sp
65 module procedure mod_pole_dp
66 end interface
67
68 public :: mod_shift
69
70 !> \copydoc mo_poly::mod_shift_sp
71 interface mod_shift
72 module procedure mod_shift_sp
73 module procedure mod_shift_dp
74 end interface
75
76
77 ! ------------------------------------------------------------------
78
79 private
80
81 ! ------------------------------------------------------------------
82
83contains
84
85 ! ------------------------------------------------------------------
86
87 !> \brief Area of polygon
88 !> \details Function for computing the area of a polygon (2D, convex or not).
89 !! Function for computing the area of a polygon
90 !! The polygon can be convex or not.
91 !!
92 !! The method is only applicable for 2D polygons and points.
93 !!
94 !! \b Example
95 !!
96 !! \code{.f90}
97 !! polygon(:,1) = (/ 1.0_dp,2.0_dp,2.0_dp,1.0_dp /)
98 !! polygon(:,2) = (/ 1.0_dp,1.0_dp,2.0_dp,2.0_dp /)
99 !!
100 !! area = areapoly( polygon )
101 !!
102 !! ! --> area = 1
103 !! \endcode
104 !!
105 !! See also example in test directory.
106 !!
107 !! \b Literature
108 !!
109 !! 1. http://de.wikipedia.org/wiki/Geometrischer_Schwerpunkt
110 !!
111 !> \return Area of polygon
112 function areapoly_sp(coord) result(areapoly)
113 !> coordinates of the polygon in question
114 real(sp), dimension(:,:), intent(in) :: coord
115 real(sp) :: areapoly
116
117 ! local variables
118 integer(i4) :: i,k ! loop
119 integer(i4) :: nedges ! number of coordinates
120 real(sp) :: xsum ! for summing up
121 real(sp) :: ysum ! for summing up
122
123 xsum = 0.0_sp
124 ysum = 0.0_sp
125 nedges = size(coord,1)
126
127 do i = 1, nedges
128 if (i == nedges) then
129 k = 1_i4
130 else
131 k = i + 1_i4
132 end if
133 xsum = xsum + ( coord(i,1) * coord(k,2) )
134 ysum = ysum + ( coord(i,2) * coord(k,1) )
135 end do
136
137 areapoly = 0.5_sp * (xsum - ysum)
138
139 end function areapoly_sp
140
141 ! ------------------------------------------------------------------
142 !> \brief Center of mass of polygon.
143 !> \details Function for computing the center of mass of a polygon (2D, convex or not).
144 !!
145 !! \f[ A = \sum_{i}(x_{i}y_{i+1}-x_{i+1}y_{i}) \f]
146 !! \f[ x_s = \frac{1}{6A} \sum_i(x_i+x_{i+1})(x_iy_{i+1}-x_{i+1}y_i) \f]
147 !! \f[ y_s = \frac{1}{6A} \sum_i(y_i+y_{i+1})(x_iy_{i+1}-x_{i+1}y_i) \f]
148 !!
149 !! \b Example
150 !!
151 !! \code{.f90}
152 !! polygon(:,1) = (/ 1.0_dp,2.0_dp,2.0_dp,1.0_dp /)
153 !! polygon(:,2) = (/ 1.0_dp,1.0_dp,2.0_dp,2.0_dp /)
154 !!
155 !! com = center_of_mass( polygon )
156 !!
157 !! ! --> com = (/1.5_dp, 1.5_dp/)
158 !! \endcode
159 !!
160 !! See also example in test directory
161 !!
162 !! \b Literature
163 !!
164 !! 1. http://de.wikipedia.org/wiki/Geometrischer_Schwerpunkt
165 !!
166 !> \return Center of mass of polygon.
167
168 function center_of_mass_sp(coord) result(center_of_mass)
169 !> coordinates of polygon in question
170 real(sp), dimension(:,:), intent(in) :: coord
171 real(sp), dimension(2) :: center_of_mass
172
173 ! local variables
174 integer(i4) :: i,k ! loop
175 integer(i4) :: nedges ! number of coordinates
176 real(sp) :: area ! area of the polygon
177 real(sp) :: xsum ! for summing up
178 real(sp) :: ysum ! for summing up
179
180 xsum = 0.0_sp
181 ysum = 0.0_sp
182 nedges = size(coord,1)
183
184 area = areapoly_sp(coord)
185
186 do i = 1, nedges
187 if (i == nedges ) then
188 k = 1_i4
189 else
190 k = i + 1_i4
191 end if
192 ! multiply x coord by the y coord of next vertex
193 xsum = xsum + ((coord(i,1) + coord(k,1)) * &
194 ((coord(i,1) * coord(k,2) - coord(k,1) * coord(i,2))))
195
196 ysum = ysum + ((coord(i,2) + coord(k,2)) * &
197 ((coord(i,1) * coord(k,2) - coord(k,1) * coord(i,2))))
198 end do
199
200 center_of_mass(1) = 1.0_sp / (6.0_sp * area) * xsum
201 center_of_mass(2) = 1.0_sp / (6.0_sp * area) * ysum
202
203 end function center_of_mass_sp
204
205 ! ------------------------------------------------------------------
206 !> \brief Determination point of polygon.
207 !> \details Determines whether a 2D point is inside, outside or on vertex of a polygon (2D, convex or not).
208 !!
209 !! The original version of the source code (pnpoly) was implemented by
210 !! W. Randolph Franklin. It was insufficiently assigning vertex/edge points.
211 !!
212 !! \b Example
213 !!
214 !! \code{.f90}
215 !! polygon(:,1) = (/ 1.0_dp,2.0_dp,2.0_dp,1.0_dp /)
216 !! polygon(:,2) = (/ 1.0_dp,1.0_dp,2.0_dp,2.0_dp /)
217 !!
218 !! point = (/ 1.5, 1.5 /)
219 !!
220 !! call inpoly( point, polygon, inside )
221 !!
222 !! ! --> inside = 1 ... point is inside the polygon
223 !! \endcode
224 !!
225 !! See also example in test directory
226 !!
227 !! \b Literature
228 !!
229 !! 1. https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
230 !!
231 !> \return Whether point is inside (=1), outside (=-1) or on a vertex/edge of the polygon (=0)
232 subroutine inpoly_sp(p,coord,erg)
233 !> point in question
234 real(sp), dimension(2), intent(in) :: p
235 !> coordinates of the polygon
236 real(sp), dimension(:, :), intent(in) :: coord
237 !> result:
238 !! inside: erg = 1
239 !! outside: erg = -1
240 !! on vertex/edge: erg = 0
241 integer(i4), intent(out) :: erg
242
243 ! local variables
244 real(sp), dimension(size(coord,1)) :: x, y
245 real(sp) :: lx, ly
246 logical :: mx,my,nx,ny, test1, test2
247 integer(i4) :: n, i, j
248
249 n = size(coord,1)
250
251 do i=1,n
252 x(i)=coord(i,1)-p(1)
253 y(i)=coord(i,2)-p(2)
254 ! check if point is equal to any coord
255 if ( eq(x(i),0.0_sp) .and. eq(y(i),0.0_sp) ) then
256 erg=0_i4
257 return
258 end if
259 end do
260
261 erg=-1_i4
262
263 do i=1,n
264 j=1+mod(i,n)
265 ! vertical vertex
266 if ( eq(coord(i,1),coord(j,1)) .and. eq(coord(i,1),p(1)) ) then
267 ly = (p(2)-coord(j,2)) / (coord(i,2)-coord(j,2))
268 if ( ge(ly,0.0_sp) .and. le(ly,1.0_sp) ) then
269 erg=0_i4
270 return
271 end if
272 end if
273 ! horizontal vertex
274 if ( eq(coord(i,2),coord(j,2)) .and. eq(coord(i,2),p(2)) ) then
275 lx = (p(1)-coord(j,1)) / (coord(i,1)-coord(j,1))
276 if ( ge(lx,0.0_sp ) .and. le(lx,1.0_sp) ) then
277 erg=0_i4
278 return
279 end if
280 end if
281 !
282 mx = ge(x(i),0.0_sp)
283 nx = ge(x(j),0.0_sp)
284 my = ge(y(i),0.0_sp)
285 ny = ge(y(j),0.0_sp)
286
287 test1 = .not.((my.or.ny).and.(mx.or.nx)).or.(mx.and.nx)
288 test2 = .not.(my.and.ny.and.(mx.or.nx).and..not.(mx.and.nx))
289
290 if (.not. test1) then
291 if (test2) then
292 if ((y(i)*x(j)-x(i)*y(j))/(x(j)-x(i)) < 0.0_sp) then
293 cycle
294 else
295 if ((y(i)*x(j)-x(i)*y(j))/(x(j)-x(i)) > 0.0_sp) then
296 erg = -erg
297 cycle
298 else
299 erg = 0_i4
300 return
301 end if
302 end if
303 else
304 erg=-erg
305 end if
306 end if
307
308 end do
309
310 end subroutine inpoly_sp
311
312 ! ------------------------------------------------------------------
313 !> \brief Check orientation of polygon
314 !> \details Function for checking the orientation of a polygon (2D, convex or not).
315 !> \return Integer indicating orientation (counter-clockwise: -1, clockwise: 1, none: 0)
316 function orientpoly_sp(coord) result(orientpoly)
317 !> coordinates of the polygon in question
318 real(sp), dimension(:,:), intent(in) :: coord
319 integer(i4) :: orientpoly
320 !> result:
321 !! clockwise: orientpoly = 1
322 !! counter-clockwise: orientpoly = -1
323 !! flat line or similar irregular shape: orientpoly = 0
324 ! local variables
325 integer(i4) :: n
326 real(sp) :: sum_edges
327
328 ! calculate sum over the edges, (x2 - x1)(y2 + y1) as in
329 ! https://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order
330 n = size(coord, 1)
331 ! use a vectorized version of sum over all (x2 -x1)*(y2-y1)
332 sum_edges = sum((coord(2:n, 1) - coord(1:n-1, 1)) * (coord(2:n, 2) + coord(1:n-1, 2)))
333 sum_edges = sum_edges + (coord(1, 1) - coord(n, 1)) * (coord(1, 2) + coord(n, 2))
334 if (eq(sum_edges, 0._sp)) then
335 orientpoly = 0_i4
336 else if (sum_edges < 0._sp) then
337 orientpoly = -1_i4
338 else
339 orientpoly = 1_i4
340 end if
341
342 end function orientpoly_sp
343
344 ! ------------------------------------------------------------------
345 !> \brief Modify polygon so it covers pole correctly
346 !> \details Modifies a polygon (2D, convex or not) to include pole when passed to inpoly
347 !! this function is intended to modify a given polygon, so it can be represented in a Cartesian grid
348 !! the use case is a polygon (e.g. 120,80, -120,80, 0,80 covering the north pole) that is not represented on
349 !! Cartesian lat-lon grid as polygon.
350 !! The script inserts additional coordinates, so the pole is covered (e.g. 180,80, 180,90, -180,90, -180,80)
351 !! See test cases for examples.
352 !> \return modified coordinates
353 function mod_pole_sp(coord, meridian_arg) result(coord_mod)
354 !> coordinates of the polygon in question
355 real(sp), dimension(:,:), intent(in) :: coord
356 !> meridian that represents discontinuity, defaults to 180.0
357 real(sp), intent(in), optional :: meridian_arg
358 real(sp), dimension(:,:), allocatable :: coord_mod
359
360 ! local variables
361 real(sp) :: meridian
362 real(sp) :: break, a
363 integer(i4) :: i, j, k, n
364
365 if (present(meridian_arg)) then
366 meridian = meridian_arg
367 else
368 meridian = 180._sp
369 end if
370
371 n = size(coord, 1)
372 ! determine location where meridian is crossed
373 ! find the maximum and minimum longitudes
374 i = maxloc(coord(:, 1), 1)
375 j = minloc(coord(:, 1), 1)
376 ! determine size of new coords array
377 k = n + 2
378 if (ne(coord(i, 1), meridian)) then
379 k = k + 1
380 end if
381 if (ne(coord(j, 1), meridian * (-1._sp))) then
382 k = k + 1
383 end if
384 allocate(coord_mod(k, 2))
385 ! polygon covers a pole
386 if (mod(i,n)+1 == j) then
387 ! the coord pair after i is j, so longitudes are ascending, so north pole is contained
388 coord_mod(1:i, :) = coord(1:i, :)
389 k = i
390 ! if the maxval is not meridian, we need to add point at intersection of meridian and points i and j
391 if (ne(coord(i, 1), meridian)) then
392 a = meridian - coord(i, 1)
393 break = coord(i, 2) + a / (a + abs(meridian + coord(j, 1))) * (coord(j, 2) - coord(i, 2))
394 coord_mod(k+1, :) = [meridian, break]
395 k = k + 1
396 end if
397 ! add the points meridian,90 and meridian * -1, 90
398 coord_mod(k+1:k+2, 1) = [meridian, meridian * (-1._sp)]
399 coord_mod(k+1:k+2, 2) = [90._sp, 90._sp]
400 k = k + 2
401 ! if the minval is not meridian * -1, we need to add point at intersection of meridian and points i and j
402 if (ne(coord(j, 1), meridian * (-1._sp))) then
403 a = meridian - coord(i, 1)
404 break = coord(i, 2) + a / (a + abs(meridian + coord(j, 1))) * (coord(j, 2) - coord(i, 2))
405 coord_mod(k+1, :) = [meridian * (-1._sp), break]
406 k = k + 1
407 end if
408 ! add the remaining coordinates
409 if (j > 1) coord_mod(k+1:k+1+n-j, :) = coord(j:n, :)
410 else if (mod(j,n)+1 == i) then
411 ! the coord pair after j is i, so longitudes are descending, so south pole is contained
412 coord_mod(1:j, :) = coord(1:j, :)
413 k = j
414 ! if the minval is not meridian * -1, we need to add point at intersection of meridian and points i and j
415 if (ne(coord(j, 1), meridian * (-1._sp))) then
416 a = abs(meridian + coord(j, 1))
417 break = coord(j, 2) + a / (a + meridian - coord(i, 1)) * (coord(i, 2) - coord(j, 2))
418 coord_mod(k+1, :) = [meridian * (-1._sp), break]
419 k = k + 1
420 end if
421 ! add the points meridian * -1, -90 and meridian, -90
422 coord_mod(k+1:k+2, 1) = [meridian * (-1._sp), meridian]
423 coord_mod(k+1:k+2, 2) = [-90._sp, -90._sp]
424 k = k + 2
425 ! if the maxval is not meridian, we need to add point at intersection of meridian and points i and j
426 if (ne(coord(i, 1), meridian)) then
427 a = abs(meridian + coord(j, 1))
428 break = coord(j, 2) + a / (a + meridian - coord(i, 1)) * (coord(i, 2) - coord(j, 2))
429 coord_mod(k+1, :) = [meridian, break]
430 k = k + 1
431 end if
432 ! add the remaining coordinates
433 if (i > 1) coord_mod(k+1:k+1+n-i, :) = coord(i:n, :)
434 ! else: if there are multiple locations of minval or maxval, this edge case is not covered...
435 end if
436
437 end function mod_pole_sp
438
439 ! ------------------------------------------------------------------
440 !> \brief Shifts the (longitude) value 180 degrees
441 !> \details Modify a coordinate value
442 !> \return Shifted value
443 elemental function mod_shift_sp(x_coord, meridian_arg) result(shifted)
444 !> coordinates of the polygon in question
445 real(sp), intent(in) :: x_coord
446 !> meridian that represents discontinuity, defaults to 180.0
447 real(sp), intent(in), optional :: meridian_arg
448 real(sp) :: shifted
449 real(sp) :: meridian
450
451 ! shift values
452 if (present(meridian_arg)) then
453 meridian = meridian_arg
454 else
455 meridian = 180._sp
456 end if
457 shifted = sign(abs(x_coord) - meridian, x_coord * (-1._sp))
458
459 end function mod_shift_sp
460
461 ! ------------------------------------------------------------------
462 !> \brief Area of polygon
463 !> \details Function for computing the area of a polygon (2D, convex or not).
464 !! Function for computing the area of a polygon
465 !! The polygon can be convex or not.
466 !!
467 !! The method is only applicable for 2D polygons and points.
468 !!
469 !! \b Example
470 !!
471 !! \code{.f90}
472 !! polygon(:,1) = (/ 1.0_dp,2.0_dp,2.0_dp,1.0_dp /)
473 !! polygon(:,2) = (/ 1.0_dp,1.0_dp,2.0_dp,2.0_dp /)
474 !!
475 !! area = areapoly( polygon )
476 !!
477 !! ! --> area = 1
478 !! \endcode
479 !!
480 !! See also example in test directory.
481 !!
482 !! \b Literature
483 !!
484 !! 1. http://de.wikipedia.org/wiki/Geometrischer_Schwerpunkt
485 !!
486 !> \return Area of polygon
487 function areapoly_dp(coord) result(areapoly)
488 !> coordinates of the polygon in question
489 real(dp), dimension(:,:), intent(in) :: coord
490 real(dp) :: areapoly
491
492 ! local variables
493 integer(i4) :: i,k ! loop
494 integer(i4) :: nedges ! number of coordinates
495 real(dp) :: xsum ! for summing up
496 real(dp) :: ysum ! for summing up
497
498 xsum = 0.0_dp
499 ysum = 0.0_dp
500 nedges = size(coord,1)
501
502 do i = 1, nedges
503 if (i == nedges) then
504 k = 1_i4
505 else
506 k = i + 1_i4
507 end if
508 xsum = xsum + ( coord(i,1) * coord(k,2) )
509 ysum = ysum + ( coord(i,2) * coord(k,1) )
510 end do
511
512 areapoly = 0.5_dp * (xsum - ysum)
513
514 end function areapoly_dp
515
516 ! ------------------------------------------------------------------
517 !> \brief Center of mass of polygon.
518 !> \details Function for computing the center of mass of a polygon (2D, convex or not).
519 !!
520 !! \f[ A = \sum_{i}(x_{i}y_{i+1}-x_{i+1}y_{i}) \f]
521 !! \f[ x_s = \frac{1}{6A} \sum_i(x_i+x_{i+1})(x_iy_{i+1}-x_{i+1}y_i) \f]
522 !! \f[ y_s = \frac{1}{6A} \sum_i(y_i+y_{i+1})(x_iy_{i+1}-x_{i+1}y_i) \f]
523 !!
524 !! \b Example
525 !!
526 !! \code{.f90}
527 !! polygon(:,1) = (/ 1.0_dp,2.0_dp,2.0_dp,1.0_dp /)
528 !! polygon(:,2) = (/ 1.0_dp,1.0_dp,2.0_dp,2.0_dp /)
529 !!
530 !! com = center_of_mass( polygon )
531 !!
532 !! ! --> com = (/1.5_dp, 1.5_dp/)
533 !! \endcode
534 !!
535 !! See also example in test directory
536 !!
537 !! \b Literature
538 !!
539 !! 1. http://de.wikipedia.org/wiki/Geometrischer_Schwerpunkt
540 !!
541 !> \return Center of mass of polygon.
542
543 function center_of_mass_dp(coord) result(center_of_mass)
544 !> coordinates of polygon in question
545 real(dp), dimension(:,:), intent(in) :: coord
546 real(dp), dimension(2) :: center_of_mass
547
548 ! local variables
549 integer(i4) :: i,k ! loop
550 integer(i4) :: nedges ! number of coordinates
551 real(dp) :: area ! area of the polygon
552 real(dp) :: xsum ! for summing up
553 real(dp) :: ysum ! for summing up
554
555 xsum = 0.0_dp
556 ysum = 0.0_dp
557 nedges = size(coord,1)
558
559 area = areapoly_dp(coord)
560
561 do i = 1, nedges
562 if (i == nedges ) then
563 k = 1_i4
564 else
565 k = i + 1_i4
566 end if
567 ! multiply x coord by the y coord of next vertex
568 xsum = xsum + ((coord(i,1) + coord(k,1)) * &
569 ((coord(i,1) * coord(k,2) - coord(k,1) * coord(i,2))))
570
571 ysum = ysum + ((coord(i,2) + coord(k,2)) * &
572 ((coord(i,1) * coord(k,2) - coord(k,1) * coord(i,2))))
573 end do
574
575 center_of_mass(1) = 1.0_dp / (6.0_dp * area) * xsum
576 center_of_mass(2) = 1.0_dp / (6.0_dp * area) * ysum
577
578 end function center_of_mass_dp
579
580 ! ------------------------------------------------------------------
581 !> \brief Determination point of polygon.
582 !> \details Determines whether a 2D point is inside, outside or on vertex of a polygon (2D, convex or not).
583 !!
584 !! The original version of the source code (pnpoly) was implemented by
585 !! W. Randolph Franklin. It was insufficiently assigning vertex/edge points.
586 !!
587 !! \b Example
588 !!
589 !! \code{.f90}
590 !! polygon(:,1) = (/ 1.0_dp,2.0_dp,2.0_dp,1.0_dp /)
591 !! polygon(:,2) = (/ 1.0_dp,1.0_dp,2.0_dp,2.0_dp /)
592 !!
593 !! point = (/ 1.5, 1.5 /)
594 !!
595 !! call inpoly( point, polygon, inside )
596 !!
597 !! ! --> inside = 1 ... point is inside the polygon
598 !! \endcode
599 !!
600 !! See also example in test directory
601 !!
602 !! \b Literature
603 !!
604 !! 1. https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
605 !!
606 !> \return Whether point is inside (=1), outside (=-1) or on a vertex/edge of the polygon (=0)
607 subroutine inpoly_dp(p,coord,erg)
608 !> point in question
609 real(dp), dimension(2), intent(in) :: p
610 !> coordinates of the polygon
611 real(dp), dimension(:, :), intent(in) :: coord
612 !> result:
613 !! inside: erg = 1
614 !! outside: erg = -1
615 !! on vertex/edge: erg = 0
616 integer(i4), intent(out) :: erg
617
618 ! local variables
619 real(dp), dimension(size(coord,1)) :: x, y
620 real(dp) :: lx, ly
621 logical :: mx,my,nx,ny, test1, test2
622 integer(i4) :: n, i, j
623
624 n = size(coord,1)
625
626 do i=1,n
627 x(i)=coord(i,1)-p(1)
628 y(i)=coord(i,2)-p(2)
629 ! check if point is equal to any coord
630 if ( eq(x(i),0.0_dp) .and. eq(y(i),0.0_dp) ) then
631 erg=0_i4
632 return
633 end if
634 end do
635
636 erg=-1_i4
637
638 do i=1,n
639 j=1+mod(i,n)
640 ! vertical vertex
641 if ( eq(coord(i,1),coord(j,1)) .and. eq(coord(i,1),p(1)) ) then
642 ly = (p(2)-coord(j,2)) / (coord(i,2)-coord(j,2))
643 if ( ge(ly,0.0_dp) .and. le(ly,1.0_dp) ) then
644 erg=0_i4
645 return
646 end if
647 end if
648 ! horizontal vertex
649 if ( eq(coord(i,2),coord(j,2)) .and. eq(coord(i,2),p(2)) ) then
650 lx = (p(1)-coord(j,1)) / (coord(i,1)-coord(j,1))
651 if ( ge(lx,0.0_dp ) .and. le(lx,1.0_dp) ) then
652 erg=0_i4
653 return
654 end if
655 end if
656 !
657 mx = ge(x(i),0.0_dp)
658 nx = ge(x(j),0.0_dp)
659 my = ge(y(i),0.0_dp)
660 ny = ge(y(j),0.0_dp)
661
662 test1 = .not.((my.or.ny).and.(mx.or.nx)).or.(mx.and.nx)
663 test2 = .not.(my.and.ny.and.(mx.or.nx).and..not.(mx.and.nx))
664
665 if (.not. test1) then
666 if (test2) then
667 if ((y(i)*x(j)-x(i)*y(j))/(x(j)-x(i)) < 0.0_dp) then
668 cycle
669 else
670 if ((y(i)*x(j)-x(i)*y(j))/(x(j)-x(i)) > 0.0_dp) then
671 erg = -erg
672 cycle
673 else
674 erg = 0_i4
675 return
676 end if
677 end if
678 else
679 erg=-erg
680 end if
681 end if
682
683 end do
684
685 end subroutine inpoly_dp
686
687 ! ------------------------------------------------------------------
688 !> \brief Check orientation of polygon
689 !> \details Function for checking the orientation of a polygon (2D, convex or not).
690 !> \return Integer indicating orientation (counter-clockwise: -1, clockwise: 1, none: 0)
691 function orientpoly_dp(coord) result(orientpoly)
692 !> coordinates of the polygon in question
693 real(dp), dimension(:,:), intent(in) :: coord
694 integer(i4) :: orientpoly
695 !> result:
696 !! clockwise: orientpoly = 1
697 !! counter-clockwise: orientpoly = -1
698 !! flat line or similar irregular shape: orientpoly = 0
699 ! local variables
700 integer(i4) :: n
701 real(dp) :: sum_edges
702
703 ! calculate sum over the edges, (x2 - x1)(y2 + y1) as in
704 ! https://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order
705 n = size(coord, 1)
706 ! use a vectorized version of sum over all (x2 -x1)*(y2-y1)
707 sum_edges = sum((coord(2:n, 1) - coord(1:n-1, 1)) * (coord(2:n, 2) + coord(1:n-1, 2)))
708 sum_edges = sum_edges + (coord(1, 1) - coord(n, 1)) * (coord(1, 2) + coord(n, 2))
709 if (eq(sum_edges, 0._dp)) then
710 orientpoly = 0_i4
711 else if (sum_edges < 0._dp) then
712 orientpoly = -1_i4
713 else
714 orientpoly = 1_i4
715 end if
716
717 end function orientpoly_dp
718
719 ! ------------------------------------------------------------------
720 !> \brief Modify polygon so it covers pole correctly
721 !> \details Modifies a polygon (2D, convex or not) to include pole when passed to inpoly
722 !! this function is intended to modify a given polygon, so it can be represented in a Cartesian grid
723 !! the use case is a polygon (e.g. 120,80, -120,80, 0,80 covering the north pole) that is not represented on
724 !! Cartesian lat-lon grid as polygon.
725 !! The script inserts additional coordinates, so the pole is covered (e.g. 180,80, 180,90, -180,90, -180,80)
726 !! See test cases for examples.
727 !> \return modified coordinates
728 function mod_pole_dp(coord, meridian_arg) result(coord_mod)
729 !> coordinates of the polygon in question
730 real(dp), dimension(:,:), intent(in) :: coord
731 !> meridian that represents discontinuity, defaults to 180.0
732 real(dp), intent(in), optional :: meridian_arg
733 real(dp), dimension(:,:), allocatable :: coord_mod
734
735 ! local variables
736 real(dp) :: meridian
737 real(dp) :: break, a
738 integer(i4) :: i, j, k, n
739
740 if (present(meridian_arg)) then
741 meridian = meridian_arg
742 else
743 meridian = 180._dp
744 end if
745
746 n = size(coord, 1)
747 ! determine location where meridian is crossed
748 ! find the maximum and minimum longitudes
749 i = maxloc(coord(:, 1), 1)
750 j = minloc(coord(:, 1), 1)
751 ! determine size of new coords array
752 k = n + 2
753 if (ne(coord(i, 1), meridian)) then
754 k = k + 1
755 end if
756 if (ne(coord(j, 1), meridian * (-1._dp))) then
757 k = k + 1
758 end if
759 allocate(coord_mod(k, 2))
760 ! polygon covers a pole
761 if (mod(i,n)+1 == j) then
762 ! the coord pair after i is j, so longitudes are ascending, so north pole is contained
763 coord_mod(1:i, :) = coord(1:i, :)
764 k = i
765 ! if the maxval is not meridian, we need to add point at intersection of meridian and points i and j
766 if (ne(coord(i, 1), meridian)) then
767 a = meridian - coord(i, 1)
768 break = coord(i, 2) + a / (a + abs(meridian + coord(j, 1))) * (coord(j, 2) - coord(i, 2))
769 coord_mod(k+1, :) = [meridian, break]
770 k = k + 1
771 end if
772 ! add the points meridian,90 and meridian * -1, 90
773 coord_mod(k+1:k+2, 1) = [meridian, meridian * (-1._dp)]
774 coord_mod(k+1:k+2, 2) = [90._dp, 90._dp]
775 k = k + 2
776 ! if the minval is not meridian * -1, we need to add point at intersection of meridian and points i and j
777 if (ne(coord(j, 1), meridian * (-1._dp))) then
778 a = meridian - coord(i, 1)
779 break = coord(i, 2) + a / (a + abs(meridian + coord(j, 1))) * (coord(j, 2) - coord(i, 2))
780 coord_mod(k+1, :) = [meridian * (-1._dp), break]
781 k = k + 1
782 end if
783 ! add the remaining coordinates
784 if (j > 1) coord_mod(k+1:k+1+n-j, :) = coord(j:n, :)
785 else if (mod(j,n)+1 == i) then
786 ! the coord pair after j is i, so longitudes are descending, so south pole is contained
787 coord_mod(1:j, :) = coord(1:j, :)
788 k = j
789 ! if the minval is not meridian * -1, we need to add point at intersection of meridian and points i and j
790 if (ne(coord(j, 1), meridian * (-1._dp))) then
791 a = abs(meridian + coord(j, 1))
792 break = coord(j, 2) + a / (a + meridian - coord(i, 1)) * (coord(i, 2) - coord(j, 2))
793 coord_mod(k+1, :) = [meridian * (-1._dp), break]
794 k = k + 1
795 end if
796 ! add the points meridian * -1, -90 and meridian, -90
797 coord_mod(k+1:k+2, 1) = [meridian * (-1._dp), meridian]
798 coord_mod(k+1:k+2, 2) = [-90._dp, -90._dp]
799 k = k + 2
800 ! if the maxval is not meridian, we need to add point at intersection of meridian and points i and j
801 if (ne(coord(i, 1), meridian)) then
802 a = abs(meridian + coord(j, 1))
803 break = coord(j, 2) + a / (a + meridian - coord(i, 1)) * (coord(i, 2) - coord(j, 2))
804 coord_mod(k+1, :) = [meridian, break]
805 k = k + 1
806 end if
807 ! add the remaining coordinates
808 if (i > 1) coord_mod(k+1:k+1+n-i, :) = coord(i:n, :)
809 ! else: if there are multiple locations of minval or maxval, this edge case is not covered...
810 end if
811
812 end function mod_pole_dp
813
814 ! ------------------------------------------------------------------
815 !> \brief Shifts the (longitude) value 180 degrees
816 !> \details Modify a coordinate value
817 !> \return Shifted value
818 elemental function mod_shift_dp(x_coord, meridian_arg) result(shifted)
819 !> coordinates of the polygon in question
820 real(dp), intent(in) :: x_coord
821 !> meridian that represents discontinuity, defaults to 180.0
822 real(dp), intent(in), optional :: meridian_arg
823 real(dp) :: shifted
824 real(dp) :: meridian
825
826 ! shift values
827 if (present(meridian_arg)) then
828 meridian = meridian_arg
829 else
830 meridian = 180._dp
831 end if
832 shifted = sign(abs(x_coord) - meridian, x_coord * (-1._dp))
833
834 end function mod_shift_dp
835
836 ! ------------------------------------------------------------------
837
838end module mo_poly
Area of polygon.
Definition mo_poly.f90:31
Center of mass of polygon.
Definition mo_poly.f90:39
Determination point of polygon.
Definition mo_poly.f90:47
Modify polygon so it covers pole correctly.
Definition mo_poly.f90:63
Shifts the (longitude) value 180 degrees.
Definition mo_poly.f90:71
Check orientation of polygon.
Definition mo_poly.f90:55
Comparison of real values.
Definition mo_utils.F90:284
Comparison of real values: a >= b.
Definition mo_utils.F90:294
Comparison of real values: a <= b.
Definition mo_utils.F90:299
Comparison of real values for inequality.
Definition mo_utils.F90:289
Define number representations.
Definition mo_kind.F90:17
integer, parameter sp
Single Precision Real Kind.
Definition mo_kind.F90:44
integer, parameter i4
4 Byte Integer Kind
Definition mo_kind.F90:40
integer, parameter dp
Double Precision Real Kind.
Definition mo_kind.F90:46
Polygon calculations.
Definition mo_poly.f90:21
elemental real(sp) function mod_shift_sp(x_coord, meridian_arg)
Shifts the (longitude) value 180 degrees.
Definition mo_poly.f90:444
real(dp) function areapoly_dp(coord)
Area of polygon.
Definition mo_poly.f90:488
real(sp) function, dimension(:,:), allocatable mod_pole_sp(coord, meridian_arg)
Modify polygon so it covers pole correctly.
Definition mo_poly.f90:354
subroutine inpoly_dp(p, coord, erg)
Determination point of polygon.
Definition mo_poly.f90:608
integer(i4) function orientpoly_sp(coord)
Check orientation of polygon.
Definition mo_poly.f90:317
subroutine inpoly_sp(p, coord, erg)
Determination point of polygon.
Definition mo_poly.f90:233
integer(i4) function orientpoly_dp(coord)
Check orientation of polygon.
Definition mo_poly.f90:692
elemental real(dp) function mod_shift_dp(x_coord, meridian_arg)
Shifts the (longitude) value 180 degrees.
Definition mo_poly.f90:819
real(dp) function, dimension(2) center_of_mass_dp(coord)
Center of mass of polygon.
Definition mo_poly.f90:544
real(sp) function, dimension(2) center_of_mass_sp(coord)
Center of mass of polygon.
Definition mo_poly.f90:169
real(sp) function areapoly_sp(coord)
Area of polygon.
Definition mo_poly.f90:113
real(dp) function, dimension(:,:), allocatable mod_pole_dp(coord, meridian_arg)
Modify polygon so it covers pole correctly.
Definition mo_poly.f90:729
General utilities for the CHS library.
Definition mo_utils.F90:20