module Data.Geo.Jord.Polygon
(
Polygon
, vertices
, edges
, concave
, Error(..)
, simple
, circle
, arc
, contains
, triangulate
) where
import Data.List (find)
import Data.Maybe (isJust, mapMaybe)
import Data.Geo.Jord.Angle (Angle)
import qualified Data.Geo.Jord.Angle as Angle
import Data.Geo.Jord.Ellipsoid (meanRadius)
import Data.Geo.Jord.Geodetic (HorizontalPosition)
import qualified Data.Geo.Jord.Geodetic as Geodetic
import Data.Geo.Jord.GreatCircle (MinorArc)
import qualified Data.Geo.Jord.GreatCircle as GreatCircle
import Data.Geo.Jord.Length (Length)
import qualified Data.Geo.Jord.Length as Length
import qualified Data.Geo.Jord.Math3d as Math3d
import Data.Geo.Jord.Model (Spherical, surface)
import Data.Geo.Jord.Triangle (Triangle)
import qualified Data.Geo.Jord.Triangle as Triangle
data Polygon a =
Polygon
{ vertices :: [HorizontalPosition a]
, edges :: [MinorArc a]
, concave :: Bool
}
deriving (Eq, Show)
data Error
= NotEnoughVertices
| InvalidEdge
| InvalidRadius
| EmptyArcRange
| SeflIntersectingEdge
deriving (Eq, Show)
simple :: (Spherical a) => [HorizontalPosition a] -> Either Error (Polygon a)
simple vs
| null vs = Left NotEnoughVertices
| head vs == last vs = simple (init vs)
| length vs < 3 = Left NotEnoughVertices
| otherwise = simple' vs
circle :: (Spherical a) => HorizontalPosition a -> Length -> Int -> Either Error (Polygon a)
circle c r nb
| r <= Length.zero = Left InvalidRadius
| nb < 3 = Left NotEnoughVertices
| otherwise = Right (discretiseArc c r as)
where
n = fromIntegral nb :: Double
as = take nb (iterate (\x -> x + pi * 2.0 / n) 0.0)
arc :: (Spherical a)
=> HorizontalPosition a
-> Length
-> Angle
-> Angle
-> Int
-> Either Error (Polygon a)
arc c r sa ea nb
| r <= Length.zero = Left InvalidRadius
| nb < 3 = Left NotEnoughVertices
| range == Angle.zero = Left EmptyArcRange
| otherwise = Right (discretiseArc c r as)
where
range = Angle.clockwiseDifference sa ea
n = fromIntegral nb :: Double
inc = Angle.toRadians range / (n - 1.0)
r0 = Angle.toRadians sa
as = take nb (iterate (+ inc) r0)
contains :: (Spherical a) => Polygon a -> HorizontalPosition a -> Bool
contains poly p = GreatCircle.enclosedBy p (vertices poly)
triangulate :: (Spherical a) => Polygon a -> [Triangle a]
triangulate p
| length vs == 3 = [triangle vs]
| otherwise = earClipping vs []
where
vs = vertices p
triangle :: (Spherical a) => [HorizontalPosition a] -> Triangle a
triangle vs = Triangle.unsafeMake (head vs) (vs !! 1) (vs !! 2)
earClipping :: (Spherical a) => [HorizontalPosition a] -> [Triangle a] -> [Triangle a]
earClipping vs ts
| length vs == 3 = reverse (triangle vs : ts)
| otherwise =
case findEar vs of
Nothing -> []
(Just (p, e, n)) -> earClipping vs' ts'
where ts' = Triangle.unsafeMake p e n : ts
vs' = filter (/= e) vs
findEar ::
(Spherical a)
=> [HorizontalPosition a]
-> Maybe (HorizontalPosition a, HorizontalPosition a, HorizontalPosition a)
findEar ps = find (`isEar` rs) convex
where
rs = reflices ps
t3 = tuple3 ps
convex = filter (\(_, v, _) -> v `notElem` rs) t3
isEar ::
(Spherical a)
=> (HorizontalPosition a, HorizontalPosition a, HorizontalPosition a)
-> [HorizontalPosition a]
-> Bool
isEar (p, c, n) = all (\r -> not (GreatCircle.enclosedBy r vs))
where
vs = [p, c, n]
reflices :: (Spherical a) => [HorizontalPosition a] -> [HorizontalPosition a]
reflices ps = fmap (\(_, c, _) -> c) rs
where
t3 = tuple3 ps
rs = filter (\(p, c, n) -> GreatCircle.side p c n == GreatCircle.LeftOf) t3
selfIntersects :: (Spherical a) => [MinorArc a] -> Bool
selfIntersects ps
| length ps < 4 = False
| otherwise = any intersects pairs
where
(_, pairs) = makePairs' (ps, [])
intersects :: (Spherical a) => (MinorArc a, [MinorArc a]) -> Bool
intersects (ma, mas) = any (isJust . GreatCircle.intersection ma) mas
makePairs' ::
(Spherical a)
=> ([MinorArc a], [(MinorArc a, [MinorArc a])])
-> ([MinorArc a], [(MinorArc a, [MinorArc a])])
makePairs' (xs, ps)
| length xs < 3 = (xs, ps)
| otherwise = makePairs' (nxs, np : ps)
where
nxs = tail xs
versus =
if null ps
then init . tail . tail $ xs
else tail . tail $ xs
np = (head xs, versus)
simple' :: (Spherical a) => [HorizontalPosition a] -> Either Error (Polygon a)
simple' vs
| length es /= length vs = Left InvalidEdge
| si = Left SeflIntersectingEdge
| otherwise = Right (Polygon os es isConcave)
where
zs = tuple3 vs
clockwise = sum (fmap (\(a, b, c) -> Angle.toRadians (GreatCircle.turn a b c)) zs) < 0.0
os =
if clockwise
then vs
else reverse vs
es = mkEdges os
zzs = tuple3 os
isConcave =
length vs > 3 && any (\(a, b, c) -> GreatCircle.side a b c == GreatCircle.LeftOf) zzs
si = isConcave && selfIntersects es
tuple3 ::
(Spherical a)
=> [HorizontalPosition a]
-> [(HorizontalPosition a, HorizontalPosition a, HorizontalPosition a)]
tuple3 ps = zip3 l1 l2 l3
where
l1 = last ps : init ps
l2 = ps
l3 = tail ps ++ [head ps]
mkEdges :: (Spherical a) => [HorizontalPosition a] -> [MinorArc a]
mkEdges ps = mapMaybe (uncurry GreatCircle.minorArc) (zip ps (tail ps ++ [head ps]))
discretiseArc :: (Spherical a) => HorizontalPosition a -> Length -> [Double] -> Polygon a
discretiseArc c r as = Polygon ps (mkEdges ps) False
where
m = Geodetic.model c
lat = Geodetic.latitude c
lon = Geodetic.longitude c
erm = Length.toMetres . meanRadius . surface $ m
rm = erm * sin (Length.toMetres r / erm)
z = sqrt (erm * erm - rm * rm)
rya = pi / 2.0 - Angle.toRadians lat
cy = cos rya
sy = sin rya
ry = [Math3d.vec3 cy 0 sy, Math3d.vec3 0 1 0, Math3d.vec3 (-sy) 0 cy]
rza = Angle.toRadians lon
cz = cos rza
sz = sin rza
rz = [Math3d.vec3 cz (-sz) 0, Math3d.vec3 sz cz 0, Math3d.vec3 0 0 1]
anp = fmap (\a -> Math3d.vec3 ((-rm) * cos a) (rm * sin a) z) as
rot = fmap (\v -> Math3d.multM (Math3d.multM v ry) rz) anp
ps = fmap (`Geodetic.nvectorPos'` m) rot