Skip to content

Commit

Permalink
feat(GeomUtil): add geometry module with point_in_polygon utility (MO…
Browse files Browse the repository at this point in the history
  • Loading branch information
wpbonelli authored Oct 29, 2023
1 parent f84b1c7 commit 88ec384
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 34 deletions.
90 changes: 90 additions & 0 deletions autotest/TestGeomUtil.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
module TestGeomUtil
use KindModule, only: I4B, DP
use testdrive, only: error_type, unittest_type, new_unittest, check, test_failed
use GeomUtilModule, only: point_in_polygon
use ConstantsModule, only: LINELENGTH
implicit none
private
public :: collect_geomutil

contains

subroutine collect_geomutil(testsuite)
type(unittest_type), allocatable, intent(out) :: testsuite(:)
testsuite = [ &
new_unittest("point_in_polygon_rect", &
test_point_in_polygon_rect) &
]
end subroutine collect_geomutil

subroutine test_point_in_polygon_rect(error)
type(error_type), allocatable, intent(out) :: error
real(DP), allocatable :: poly(:, :)

! allocate and define polygon
allocate (poly(4, 2))
! vertices in clockwise order
poly(1, :) = (/0.0_DP, 0.0_DP/)
poly(2, :) = (/0.0_DP, 1.0_DP/)
poly(3, :) = (/1.0_DP, 1.0_DP/)
poly(4, :) = (/1.0_DP, 0.0_DP/)

! points inside polygon
call check(error, point_in_polygon(0.99_DP, 0.01_DP, poly))
call check(error, point_in_polygon(0.5_DP, 0.5_DP, poly))
call check(error, point_in_polygon(0.0001_DP, 0.9999_DP, poly))
if (allocated(error)) return

! points outside polygon
call check(error, (.not. point_in_polygon(0.5_DP, 1.00001_DP, poly)))
call check(error, (.not. point_in_polygon(-0.5_DP, 34.0_DP, poly)))
if (allocated(error)) return

! points on vertices
call check(error, point_in_polygon(0.0_DP, 0.0_DP, poly))
call check(error, point_in_polygon(1.0_DP, 0.0_DP, poly))
call check(error, point_in_polygon(0.0_DP, 1.0_DP, poly))
call check(error, point_in_polygon(1.0_DP, 1.0_DP, poly))
if (allocated(error)) return

! points on faces
call check(error, point_in_polygon(0.0_DP, 0.5_DP, poly))
call check(error, point_in_polygon(0.5_DP, 0.0_DP, poly))
call check(error, point_in_polygon(1.0_DP, 0.5_DP, poly))
call check(error, point_in_polygon(0.5_DP, 1.0_DP, poly))
if (allocated(error)) return

! vertices counter-clockwise
poly(1, :) = (/0.0_DP, 0.0_DP/)
poly(2, :) = (/1.0_DP, 0.0_DP/)
poly(3, :) = (/1.0_DP, 1.0_DP/)
poly(4, :) = (/0.0_DP, 1.0_DP/)

! points inside polygon
call check(error, point_in_polygon(0.99_DP, 0.01_DP, poly))
call check(error, point_in_polygon(0.5_DP, 0.5_DP, poly))
call check(error, point_in_polygon(0.0001_DP, 0.9999_DP, poly))
if (allocated(error)) return

! points outside polygon
call check(error, (.not. point_in_polygon(0.5_DP, 1.00001_DP, poly)))
call check(error, (.not. point_in_polygon(-0.5_DP, 34.0_DP, poly)))
if (allocated(error)) return

! points on vertices
call check(error, point_in_polygon(0.0_DP, 0.0_DP, poly))
call check(error, point_in_polygon(1.0_DP, 0.0_DP, poly))
call check(error, point_in_polygon(0.0_DP, 1.0_DP, poly))
call check(error, point_in_polygon(1.0_DP, 1.0_DP, poly))
if (allocated(error)) return

! points on faces
call check(error, point_in_polygon(0.0_DP, 0.5_DP, poly))
call check(error, point_in_polygon(0.5_DP, 0.0_DP, poly))
call check(error, point_in_polygon(1.0_DP, 0.5_DP, poly))
call check(error, point_in_polygon(0.5_DP, 1.0_DP, poly))
if (allocated(error)) return

end subroutine test_point_in_polygon_rect

end module TestGeomUtil
1 change: 1 addition & 0 deletions autotest/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ test_drive = dependency('test-drive', required : false)
if test_drive.found()
tests = [
'ArrayHandlers',
'GeomUtil'
]

test_srcs = files(
Expand Down
4 changes: 3 additions & 1 deletion autotest/tester.f90
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ program tester
use testdrive, only: run_testsuite, new_testsuite, testsuite_type, &
& select_suite, run_selected, get_argument
use TestArrayHandlers, only: collect_arrayhandlers
use TestGeomUtil, only: collect_geomutil
implicit none
integer :: stat, is
character(len=:), allocatable :: suite_name, test_name
Expand All @@ -11,7 +12,8 @@ program tester

stat = 0
testsuites = [ &
new_testsuite("ArrayHandlers", collect_arrayhandlers) &
new_testsuite("ArrayHandlers", collect_arrayhandlers), &
new_testsuite("GeomUtil", collect_geomutil) &
]

call get_argument(1, suite_name)
Expand Down
61 changes: 32 additions & 29 deletions make/makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,37 @@ include ./makedefaults

# Define the source file directories
SOURCEDIR1=../src
SOURCEDIR2=../src/Model
SOURCEDIR3=../src/Model/TransportModel
SOURCEDIR4=../src/Model/GroundWaterFlow
SOURCEDIR5=../src/Model/Geometry
SOURCEDIR6=../src/Model/ModelUtilities
SOURCEDIR7=../src/Model/GroundWaterTransport
SOURCEDIR8=../src/Model/Connection
SOURCEDIR9=../src/Distributed
SOURCEDIR10=../src/Utilities
SOURCEDIR11=../src/Utilities/Idm
SOURCEDIR2=../src/Exchange
SOURCEDIR3=../src/Distributed
SOURCEDIR4=../src/Solution
SOURCEDIR5=../src/Solution/LinearMethods
SOURCEDIR6=../src/Solution/ParticleTracker
SOURCEDIR7=../src/Solution/PETSc
SOURCEDIR8=../src/Timing
SOURCEDIR9=../src/Utilities
SOURCEDIR10=../src/Utilities/Idm
SOURCEDIR11=../src/Utilities/Idm/selector
SOURCEDIR12=../src/Utilities/Idm/mf6blockfile
SOURCEDIR13=../src/Utilities/Idm/selector
SOURCEDIR14=../src/Utilities/Vector
SOURCEDIR15=../src/Utilities/Matrix
SOURCEDIR16=../src/Utilities/Observation
SOURCEDIR17=../src/Utilities/ArrayRead
SOURCEDIR18=../src/Utilities/OutputControl
SOURCEDIR19=../src/Utilities/Libraries
SOURCEDIR20=../src/Utilities/Libraries/blas
SOURCEDIR21=../src/Utilities/Libraries/rcm
SOURCEDIR13=../src/Utilities/TimeSeries
SOURCEDIR14=../src/Utilities/Memory
SOURCEDIR15=../src/Utilities/OutputControl
SOURCEDIR16=../src/Utilities/ArrayRead
SOURCEDIR17=../src/Utilities/Libraries
SOURCEDIR18=../src/Utilities/Libraries/rcm
SOURCEDIR19=../src/Utilities/Libraries/blas
SOURCEDIR20=../src/Utilities/Libraries/sparskit2
SOURCEDIR21=../src/Utilities/Libraries/daglib
SOURCEDIR22=../src/Utilities/Libraries/sparsekit
SOURCEDIR23=../src/Utilities/Libraries/sparskit2
SOURCEDIR24=../src/Utilities/Libraries/daglib
SOURCEDIR25=../src/Utilities/Memory
SOURCEDIR26=../src/Utilities/TimeSeries
SOURCEDIR27=../src/Timing
SOURCEDIR28=../src/Solution
SOURCEDIR29=../src/Solution/PETSc
SOURCEDIR30=../src/Solution/LinearMethods
SOURCEDIR31=../src/Exchange
SOURCEDIR23=../src/Utilities/Vector
SOURCEDIR24=../src/Utilities/Matrix
SOURCEDIR25=../src/Utilities/Observation
SOURCEDIR26=../src/Model
SOURCEDIR27=../src/Model/Connection
SOURCEDIR28=../src/Model/GroundWaterTransport
SOURCEDIR29=../src/Model/ModelUtilities
SOURCEDIR30=../src/Model/GroundWaterFlow
SOURCEDIR31=../src/Model/TransportModel
SOURCEDIR32=../src/Model/Geometry

VPATH = \
${SOURCEDIR1} \
Expand Down Expand Up @@ -67,7 +68,8 @@ ${SOURCEDIR27} \
${SOURCEDIR28} \
${SOURCEDIR29} \
${SOURCEDIR30} \
${SOURCEDIR31}
${SOURCEDIR31} \
${SOURCEDIR32}

.SUFFIXES: .f90 .F90 .o

Expand Down Expand Up @@ -321,6 +323,7 @@ $(OBJDIR)/sparsekit.o \
$(OBJDIR)/rcm.o \
$(OBJDIR)/blas1_d.o \
$(OBJDIR)/Iunit.o \
$(OBJDIR)/GeomUtil.o \
$(OBJDIR)/RectangularGeometry.o \
$(OBJDIR)/CircularGeometry.o

Expand Down
1 change: 1 addition & 0 deletions msvs/mf6core.vfproj
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@
<Tool Name="VFFortranCompilerTool" Preprocess="preprocessYes"/></FileConfiguration></File>
<File RelativePath="..\src\Utilities\DevFeature.f90"/>
<File RelativePath="..\src\Utilities\genericutils.f90"/>
<File RelativePath="..\src\Utilities\GeomUtil.f90"/>
<File RelativePath="..\src\Utilities\HashTable.f90"/>
<File RelativePath="..\src\Utilities\HeadFileReader.f90"/>
<File RelativePath="..\src\Utilities\InputOutput.f90"/>
Expand Down
1 change: 1 addition & 0 deletions msvs/mf6lib.vfproj
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@
<File RelativePath="..\src\Utilities\compilerversion.fpp"/>
<File RelativePath="..\src\Utilities\Constants.f90"/>
<File RelativePath="..\src\Utilities\genericutils.f90"/>
<File RelativePath="..\src\Utilities\GeomUtil.f90"/>
<File RelativePath="..\src\Utilities\HashTable.f90"/>
<File RelativePath="..\src\Utilities\InputOutput.f90"/>
<File RelativePath="..\src\Utilities\Iunit.f90"/>
Expand Down
65 changes: 65 additions & 0 deletions src/Utilities/GeomUtil.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
module GeomUtilModule
use KindModule, only: I4B, DP
implicit none
private
public :: between, point_in_polygon
contains

!> @brief Check if a value is between two other values (inclusive).
logical function between(x, a, b)
real(DP), intent(in) :: x, a, b
between = (x >= a .and. x <= b .or. x <= a .and. x >= b)
end function between

!> @brief Check if a point is within a polygon.
!! Vertices and edge points are considered in.
!! Reference: https://stackoverflow.com/a/63436180/6514033
logical function point_in_polygon(x, y, poly)
real(DP), intent(in) :: x
real(DP), intent(in) :: y
real(DP), allocatable, intent(in) :: poly(:, :)
integer(I4B) :: i, ii, num_verts
real(DP) :: xa, xb, ya, yb, c = 0.0_DP

point_in_polygon = .false.
num_verts = size(poly, 1)
xa = poly(1, 1)
ya = poly(1, 2)

do i = 0, num_verts + 1
ii = mod(i, num_verts) + 1
xb = poly(ii, 1)
yb = poly(ii, 2)

! boundary cases
if ((x == xa .and. y == ya) .or. (x == xb .and. y == yb)) then
! vertex point
point_in_polygon = .true.
else if (ya == yb .and. y == ya .and. between(x, xa, xb)) then
! horizontal edge
point_in_polygon = .true.
! if within vertical range, cast a ray
else if (between(y, ya, yb)) then
if (y == ya .and. yb >= ya .or. y == yb .and. ya >= yb) then
xa = xb
ya = yb
cycle
end if
! cross product
c = (xa - x) * (yb - y) - (xb - x) * (ya - y)
! boundary case
if (c == 0) then
point_in_polygon = .true.
! standard intersection
else if ((ya < yb) .eqv. (c > 0)) then
point_in_polygon = .not. point_in_polygon
end if
end if

if (point_in_polygon) exit
xa = xb
ya = yb
end do
end function point_in_polygon

end module GeomUtilModule
1 change: 1 addition & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ modflow_sources = files(
'Utilities' / 'defmacro.F90',
'Utilities' / 'DevFeature.f90',
'Utilities' / 'genericutils.f90',
'Utilities' / 'GeomUtil.f90',
'Utilities' / 'HashTable.f90',
'Utilities' / 'HeadFileReader.f90',
'Utilities' / 'InputOutput.f90',
Expand Down
8 changes: 4 additions & 4 deletions utils/mf5to6/make/makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ include ./makedefaults

# Define the source file directories
SOURCEDIR1=../src
SOURCEDIR2=../src/MF2005
SOURCEDIR3=../src/NWT
SOURCEDIR4=../src/LGR
SOURCEDIR5=../src/Preproc
SOURCEDIR2=../src/NWT
SOURCEDIR3=../src/LGR
SOURCEDIR4=../src/Preproc
SOURCEDIR5=../src/MF2005
SOURCEDIR6=../../../src/Utilities/Memory
SOURCEDIR7=../../../src/Utilities/TimeSeries
SOURCEDIR8=../../../src/Utilities
Expand Down

0 comments on commit 88ec384

Please sign in to comment.