packages feed

hgeometry 0.4.0.0 → 0.5.0.0

raw patch · 82 files changed

+8913/−1183 lines, 82 filesdep +Framesdep +directorydep +hspecdep −validationdep ~basedep ~bifunctorsdep ~bytestringnew-component:exe:hgeometry-examples

Dependencies added: Frames, directory, hspec, optparse-applicative, semigroupoids, template-haskell, time

Dependencies removed: validation

Dependency ranges changed: base, bifunctors, bytestring, containers, data-clist, fixed-vector, hexpat, lens, linear, semigroups, singletons, text, vector, vinyl

Files

README.md view
@@ -22,13 +22,18 @@  Please note that aspect (2), implementing good algorithms, is much work in progress. HGeometry currently has only very basic types, and implements only-two algorithms: an (optimal) $O(n \log n)$ time algorithm for convex hull, and-an $O(n)$ expected time algorithm for smallest enclosing disk (both in $R^2$).+a few algorithms: -Current work is on implementing $O(n \log n + k)$ time red-blue line segment-intersection. This would also allow for efficient polygon intersection and map-overlay.+* two (optimal) $O(n \log n)$ time algorithms for convex hull in+  $\mathbb{R}^2$: the typical Graham scan, and a divide and conqueror algorithm,+* an $O(n)$ expected time algorithm for smallest enclosing disk in $\mathbb{R}^$2,+* the well-known Douglas Peucker polyline line simplification algorithm,+* an $O(n \log n)$ time algorithm for computing the Delaunay triangulation+(using divide and conqueror).+* an $O(n \log n)$ time algorithm for computing the Euclidean Minimum Spanning+Tree (EMST), based on computing the Delaunay Triangulation. + A Note on the Ext (:+) data type --------------------------------- @@ -59,6 +64,6 @@ Reading and Writing Ipe files ----------------------------- -Appart from geometric types, HGeometry provides some interface for reading and-writing Ipe (http://ipe7.sourceforge.net). However, this is all very work in+Apart from geometric types, HGeometry provides some interface for reading and+writing Ipe (http://ipe.otfried.org). However, this is all very work in progress. Hence, the API is experimental and may change at any time!
doctests.hs view
@@ -16,20 +16,41 @@            , "PatternSynonyms"           , "ViewPatterns"+          , "TupleSections"+          , "MultiParamTypeClasses"+          , "LambdaCase"            , "StandaloneDeriving"           , "GeneralizedNewtypeDeriving"+          , "DeriveFunctor"+          , "DeriveFoldable"+          , "DeriveTraversable"+          , "AutoDeriveTypeable"           , "FlexibleInstances"           , "FlexibleContexts"           ] -files = geomModules+files = mconcat [ geomModules+                , dataModules+                ] -geomModules = map ("src/Data/Geometry/" <>) [ "Point.hs"-                                            , "Vector.hs"-                                            , "Line.hs"-                                            , "LineSegment.hs"-                                            , "PolyLine.hs"-                                            , "Ball.hs"-                                            , "Box.hs"-                                            ]+prefixWith s = map (\s' -> "src/" <> s <> s')+++dataModules = prefixWith "Data/" [ "Range.hs"+                                 , "CircularList/Util.hs"+                                 , "Permutation.hs"+                                 , "CircularSeq.hs"+                                 , "PlanarGraph.hs"+                                 ]++geomModules = prefixWith "Data/Geometry/" [ "Point.hs"+                                          , "Vector.hs"+                                          , "Line.hs"+                                          , "Line/Internal.hs"                                                                        , "Interval.hs"+                                          , "LineSegment.hs"+                                          , "PolyLine.hs"+                                          , "Polygon.hs"+                                          , "Ball.hs"+                                          , "Box.hs"+                                          ]
+ examples/BAPC2012/Gunslinger.hs view
@@ -0,0 +1,146 @@+module BAPC2012.Gunslinger where++import           Algorithms.Geometry.ConvexHull.GrahamScan+import           Control.Lens+import qualified Data.CircularSeq as C+import qualified Data.Foldable as F+import qualified Data.List.NonEmpty as NonEmpty+import           Data.Ext+import           Data.Fixed+import           Data.Geometry.Point+import           Data.Geometry.Polygon+import qualified Data.List     as L+import           Data.Maybe+import           Linear.Affine(distanceA)++{-++Credits: http://2012.bapc.eu/++Problem Statement+=================++The quickly shooting, maiden saving, gunslinging cowboy Luke finds himself in+the den of his archenemy: the Dalton gang. He is trying to escape but is in+constant danger of being shot. Fortunately his excellent marksmanship and quick+reflexes give him the upper hand in a firefight: the gang members are all too+scared to move, let alone draw their guns. That is, as long as Luke can see+them. If he cannot see one of the thugs, then that Dalton will immediately fire+upon Luke and kill the cowboy without fear of retaliation. Luke’s amazing+eyesight allows him to cover a field of view of 180 degrees all the time. While+doing so he can move around freely, even walking backward if necessary.  Luke’s+goal is to walk to the escape hatch in the den while turning in such a way that+he will not be shot. He does not want to shoot any of the Daltons because that+will surely result in a big fire fight. The Daltons all have varying heights,+but you may assume that all the people and the hatch are of infinitesimal size.++Input+-----++On the first line one positive number: the number of test cases, at most+100. After that per test case:++* one line with two space-separated integers xL and yL: Luke’s starting position.+* one line with two space-separated integers xE and yE : the position of the escape hatch.+* one line with an integer n (1 <= n <= 1 000): the number of Dalton gang members.+* n lines with two space-separated integers xi and yi: the position of the i-th Dalton.++All x and y are in the range −10000 <= x,y <= 10000. Luke, the escape hatch and+all Daltons all have distinct positions.++Output+-----++Per test case:++* one line with the length of the shortest path Luke can take to the escape+hatch without dying, rounded to three decimal places, or “IMPOSSIBLE” if no+such path exists.  The test cases are such that an absolute error of at most+10^−6 in the final answer does not influence the result of the rounding.+++Solution+========++Compute the convex hull of Luke, the hatch, and the daltons. If luke and the+hatch are on the convex hull, Luke can safely reach it. The length of the+shortest path is the lenght of walking along the convex hull (in one of the two+directions). If luke or the hatch is not on the Convex Hull, escaping is+impossible.++Running time: O(n log n)++-}++data Answer = Possible Double | Impossible deriving (Eq,Ord)++instance Show Answer where+  show Impossible   = "IMPOSSIBLE"+  show (Possible l) = showFixed False  . roundToMili $ l+    where+      roundToMili :: Real a => a -> Milli+      roundToMili = realToFrac+++data Kind = Luke | Hatch | Dalton deriving (Show,Eq)++++data Input = Input { _luke  :: Point 2 Int+                   , _hatch :: Point 2 Int+                   , _daltons :: [Point 2 Int]+                   } deriving (Show,Eq)++-- TODO: THis only works if there are no colinear points. I.e. if Luke or the+-- hatch lie on the hull, but in the interior of some edge, they are not+-- contained in the hull.+escape                :: Input -> Answer+escape (Input l h ds) = case convexHull . NonEmpty.fromList $+                               (l :+ Luke) : (h :+ Hatch) : map (:+ Dalton) ds of+    -- all positions are distinct, so the hull has at least two elements+    ConvexHull poly -> case C.findRotateTo (\p -> p^.extra == Luke) $+                              poly^.outerBoundary of+      Nothing -> Impossible+      Just h  -> (distanceToHatch $ C.leftElements h)+                 `min`+                 (distanceToHatch $ C.rightElements h)+++toHatch    :: [p :+ Kind] -> Maybe [p :+ Kind]+toHatch xs = let (ys,rest) = L.break (\p -> p^.extra == Hatch) xs+             in case rest of+               []    -> Nothing+               (h:_) -> Just $ ys ++ [h]+++distanceAlong     :: [Point 2 Int :+ k] -> Double+distanceAlong xs' = sum $ zipWith distanceA xs (tail xs)+  where+    xs = map (fmap fromIntegral . (^.core)) xs'+++-- Distance from luke, at the head of the list, to the hatch while walking+-- along the points in the list.+distanceToHatch :: Foldable f => f (Point 2 Int :+ Kind) -> Answer+distanceToHatch = maybe Impossible (Possible . distanceAlong) . toHatch . F.toList+++readPoint   :: String -> Point 2 Int+readPoint s = let [x,y] = map read . words $ s in point2 x y+++readInput                 :: [String] -> [Input]+readInput []              = []+readInput (ls:hs:ns:rest) = let n            = read ns+                                (daltons,ys) = L.splitAt n rest+                            in Input (readPoint ls)+                                     (readPoint hs)+                                     (map readPoint daltons)+                               : readInput ys+++gunslinger :: String -> String+gunslinger = unlines . map (show . escape) . readInput . tail . lines++main :: IO ()+main = interact gunslinger
+ examples/BAPC2014/Armybase.lhs view
@@ -0,0 +1,300 @@+---+title: Army Base with Ternary Search+author: Frank Staals+@EXPECTED_RESULTS@: CORRECT+---++Army Base with Ternary Search+=============================++$O(n^2 \log n)$ solution.++++> {-# LANGUAGE GeneralizedNewtypeDeriving #-}+> module BAPC2014.Armybase where++> import Control.Applicative+> import Control.Lens((^.))+> import Data.Monoid+> import Data.Ix+> import Data.Ext++> import Data.Geometry.Triangle+> import Data.Geometry.Point+> import Data.Geometry.Polygon+> import Algorithms.Geometry.ConvexHull.GrahamScan+> import qualified Data.Array   as A+> import qualified Data.Foldable as F+> import qualified Data.List     as L+> import qualified Data.List.NonEmpty as NonEmpty++Preliminaries+-------------+++> type PointSet = [Point 2 Int]+++A value of type Half should still be halved. I.e. 'Half 2x = x'++> newtype Half = Half Int+>                        deriving (Show,Eq,Ord)++> instance Num Half where+>   (Half a) + (Half b) = Half $ a + b+>   (Half a) - (Half b) = Half $ a - b+>   (Half a) * (Half b) = Half $ a * b+>   abs (Half a) = Half $ abs a+>   signum (Half a) = Half $ 2 * signum a+>   fromInteger a = Half $ 2 * fromInteger a++> showValue :: Half -> String+> showValue (Half i) = concat [ show $ i `div` 2+>                             , if odd i then ".5" else ""+>                             ]+>   where+>     odd x = x `mod` 2 /= 0+++> type Area = Half++++> triangArea :: Triangle p Int -> Half+> triangArea = Half . doubleArea+++++TODO: Discuss degenerate cases here+++Let $V(Q)$ denote the vertices of polygon $Q$, and let $\mathcal{CH}(P)$ denote+the convex hull of the set of points $P$.+++<div class="lemma">+There is an maximum area quadrangle $Q^*$, such that $V(Q^*) \subseteq+V(\mathcal{CH}(P))$.+</div>++<div class="proof">+TODO+</div>+++Main Algorithm+---------------++> maxBaseArea    :: PointSet -> Area+> maxBaseArea [] = 0+> maxBaseArea p  = case convexHull . NonEmpty.fromList $ map ext p of+>     ch@(ConvexHull h) -> case F.toList $ h ^. outerBoundary of+>                            [_,_]   -> 0+>                            [a,b,c] -> triangArea $ Triangle a b c+>                            _       -> maxAreaQuadrangle ch+++<div class="observation">+Left an Right chains are independent+</div>++Main idea: Pick two non-adjacent vertices $p$ and $q$ of the convex hull as the+diagonals of our quadrangle. This splits the problem into two independent+subproblems, in both of which we have to find the largest triangle that has $p$+and $q$ as vertices.++> maxAreaQuadrangle :: ConvexHull () Int -> Area+> maxAreaQuadrangle = maximum' . map (uncurry3 maxAreaQuadrangleWith) . allChains+>   where+>     uncurry3 f (a,b,c) = f a b c+>     maximum' = L.foldl1' max -- saves memory :)++the function `allChains` finds all alowed pairs $p$ and $q$, and the chains of+vertices (along the convex hull) connecting $p$ to $q$ and $q$ to $p$.++> type Chain = Array Int (Point 2 Int)++> allChains                 :: ConvexHull () Int+>                           -> [(Point 2 Int, Point 2 Int, (Chain,Chain))]+> allChains (ConvexHull ch) =+>     [ (chA ! i, chA ! j, chains chA i j) | i <- [1..n-2], j <- rest i ]+>   where+>     n   = F.length $ ch^.outerBoundary+>     chA = listArray (1,n) . map (^.core) . F.toList $ ch^.outerBoundary+>     rest i = [i+2.. if i == 1 then n - 1 else n ]++we make sure that we only select non-neighbouring pairs. Hence the i+2 in rest+i. Then also the last valid start-point p is the one with index n-2.++To make sure `allChains` runs in $O(n^2)$ time we build an Array representing+our convex hull vertices. All individiual chains are then views of this underlying array:++> chains          :: Array Int a -> Int -> Int -> (Array Int a, Array Int a)+> chains a pi qi = (ixSubMap (1,qi-pi-1) fu a, ixSubMap (1,r + pi - 1) fv a)+>   where+>     n    = rangeSize . bounds $ a+>     r    = n - qi+>     fu i = pi + i+>     fv i = if i <= r then qi + i+>                      else i - r++We then use `maxAreaQuadrangleWith` to find the largest quadrangle given its+diagonals $p$ and $q$ (and the chains connecting $p$ and $q$).++> maxAreaQuadrangleWith             :: Point 2 Int -> Point 2 Int -> (Chain,Chain) -> Area+> maxAreaQuadrangleWith p q (us,vs) = let pqu = findLargestTriang p q (Unimodal us)+>                                         pqv = findLargestTriang q p (Unimodal vs)+>                                     in (pqu `seq` triangArea pqu)+>                                      + (pqv `seq` triangArea pqv)+++To efficiently find the largest triangle we use that the area is a unimodal+function along the convex hull. We prove this in the following lemmas.++<div class="lemma">+Let $p$ and $q$ be points on the convex hull $\mathcal{CH}(P)$, and let+$\mathcal{C}$ denote the portion of $\partial$\mathcal{CH}(P)$ between $p$ and+$q$. The area $a(v)$ of the triangle $\Delta pqv$, with $v \in \mathcal{C}$+depends only on the (Euclidean) distance $d(v)$ between $v$ and the line+segment $\overline{pq}$. More specifically, we have $a(v) = cd(v)$, for some+constant $c$.+</div>++<div class="proof">+TODO+</div>++<div class="observation">+Let $p$ and $q$ be points on the convex hull $\mathcal{CH}(P)$, let+$\mathcal{C}$ denote the portion of $\partial$\mathcal{CH}(P)$ between $p$ and+$q$, and let $v(t)$, with $t \in [0,1]$ denote the position along+$\mathcal{C}$. The function $d(t)$ expressing the (Euclidean) distance between+$v(t)$ and line segment $\overline{pq}$ is unimodal.+</div>++<div class="lemma">+Let $p$ and $q$ be points on the convex hull $\mathcal{CH}(P)$, let+$\mathcal{C}$ denote the portion of $\partial$\mathcal{CH}(P)$ between $p$ and+$q$, and let $v(t)$, with $t \in [0,1]$ denote the position along+$\mathcal{C}$. The function $a(t)$ expressing the area of the triangle $\Delta pqv(t)$ is unimodal.+</div>++<div class="proof">+Directly from previous lemma and observation.+</div>++This means we can find the triangle $\Delta pqv_i$ in $O(\log n)$ time using a+ternary search.+++> newtype Unimodal s a = Unimodal { unU :: s a }+>                      deriving (Show,Eq,Ord,Read,Functor)+++> findLargestTriang        :: Point 2 Int -> Point 2 Int+>                          -> Unimodal (Array Int) (Point 2 Int) -> Triangle () Int+> findLargestTriang p q us = triang . ternarySearchArray area' $ us+>   where+>     triang v = Triangle (ext p) (ext q) (ext v)+>     area' = triangArea . triang+++Ternary Search+--------------++> ternarySearchArray            :: (Ix i, Integral i, Ord b)+>                               => (a -> b) -> Unimodal (Array i) a -> a+> ternarySearchArray f (Unimodal a)+>   | rangeSize (bounds a) == 0 = error "empty array"+>   | otherwise                 = let (l,u) = bounds a+>                                     i     = ternarySearch (\i -> f $ a ! i) (pred l) (succ u)+>                                 in a ! i++Given a function $f$, a lowerbound $\ell$, and a n upperbound $u$ find the+value $i \in (\ell,u)$ such that $f i$ is maximal.++> ternarySearch          :: (Integral r, Ord a) => (r -> a) -> r -> r -> r+> ternarySearch f l u++>   | u - l < 2  = error "ternarySearch: l and u too close"+>   | u - l == 2 = l + 1+>   | otherwise  = let t = (u - l) `div` 3+>                      n = l + t+>                      m = l + 2*t+>                  in if f n > f m then ternarySearch f l m+>                                  else ternarySearch f n u+++Input & Output+--------------++> readPointSet :: [String] -> PointSet+> readPointSet = map readPoint+>   where+>     readPoint s = let [x,y] = map read . words $ s in point2 x y++> readInput           :: [String] -> [PointSet]+> readInput []        = []+> readInput (ns:rest) = let n       = read ns+>                           (xs,ys) = L.splitAt n rest+>                       in readPointSet xs : readInput ys+++> armybase :: String -> String+> armybase = unlines . map (showValue . maxBaseArea) . readInput . tail . lines+++> main :: IO ()+> main = interact armybase+++> show' (p,q,(a,b)) = (p,q,elems a, elems b)++Array Stuff+-----------++> data Array i a = Array { bounds       :: (i,i)+>                        , accessTransf :: i -> i+>                        , arrayData    :: A.Array i a+>                        }++> instance Ix i => Functor (Array i) where+>   fmap f (Array b g a) = Array b g (fmap f a)++> instance (Show i, Show a, A.Ix i) => Show (Array i a) where+>   show a@(Array bs g _) = concat [ "Array "+>                                  , show bs+>                                  , " "+>                                  , show $ assocs a+>                                  ]+++> (!) :: Ix i => Array i a -> i -> a+> (Array _ g a) ! i = a A.! (g i)++> elems                   :: Ix i => Array i a -> [a]+> elems a = [ a ! i | i <- A.range $ bounds a ]++> listArray   :: Ix i => (i,i) -> [a] -> Array i a+> listArray b = Array b id . A.listArray b++> assocs :: A.Ix i => Array i a -> [(i,a)]+> assocs (Array _ g a) = (\(k,v) -> (g k, v)) <$> A.assocs a++> ixSubMap :: Ix i => (i,i) -> (i -> i) -> Array i a -> Array i a+> ixSubMap bs f (Array obs g a) = Array bs (g . f) a++++Testing stuff+-------------++-- > testPs :: PointSet+-- > testPs = [Point (0,0), Point (2,2), Point (4,1), Point (5,0), Point (3,-1)]++-- > test2 = [Point (0,0), Point (-2,-2), Point (3,-2), Point (0,1), Point (0,3)]+++-- > testPs3 = [Point (-16,0), Point (16,16), Point (16,-16), Point (-16,16), Point (-16,-16)]
+ examples/Demo/Delaunay.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE ScopedTypeVariables #-}+module Demo.Delaunay where++import           Algorithms.Geometry.EuclideanMST.EuclideanMST+import           Algorithms.Geometry.DelaunayTriangulation.DivideAndConqueror+import           Algorithms.Geometry.DelaunayTriangulation.Types+import           Control.Applicative+import           Control.Lens+import           Data.Data+import           Data.Ext+import           Data.Geometry+import           Data.Geometry.Ipe+import qualified Data.List.NonEmpty as NonEmpty+import           Data.Traversable+import           Options.Applicative+++data Options = Options { _inPath    :: FilePath+                       , _outFile   :: FilePath+                       }+               deriving Data++options :: ParserInfo Options+options = info (helper <*> parser)+               (  progDesc "Compute the Delaunay Triangulation of the points in the input file."+               <> header   "Delaunay"+               )+  where+    parser = Options+          <$> strOption (help "Input file (in ipe7 xml format)"+                         <> short 'i'+                        )+          <*> strOption (help "Output File (in ipe7 xml format)"+                         <> short 'o'+                        )++mainWith                          :: Options -> IO ()+mainWith (Options inFile outFile) = do+    ePage <- readSinglePageFile inFile+    case ePage of+      Left err                         -> print err+      Right (page :: IpePage Rational) -> case page^..content.traverse._IpeUse of+        []         -> putStrLn "No points found"+        syms@(_:_) -> do+           let pts  = syms&traverse.core %~ (^.symbolPoint)+               pts' = NonEmpty.fromList pts+               dt   = delaunayTriangulation $ pts'+               emst = euclideanMST pts'+               out  = [asIpe drawTriangulation dt, asIpe drawTree' emst]+           -- print $ length $ edges' dt+           -- print $ toPlaneGraph (Proxy :: Proxy DT) dt+           writeIpeFile outFile . singlePageFromContent $ out+++data DT++--xs = [(1,(1,1)) , (2,(2,2)) ]
+ examples/Demo/DrawGPX.hs view
@@ -0,0 +1,183 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeOperators #-}+module Demo.DrawGPX where++import           Algorithms.Geometry.PolyLineSimplification.DouglasPeucker+import           Control.Applicative+import           Control.Lens+import           Data.Data+import           Data.Ext+import qualified Data.Foldable as F+import           Data.Geometry+import           Data.Geometry.PolyLine+import           Data.Geometry.Ipe+import           Data.Geometry.Vector+import           Data.List (isSuffixOf)+import           Data.Maybe+import qualified Data.Sequence as S+import qualified Data.Text as T+import           Data.Time.Calendar+import           Data.Time.Clock+import           Demo.GPXParser+import           Options.Applicative+import           System.Directory+import           Text.Printf (printf)+++--------------------------------------------------------------------------------++data Options = Options { _inPath  :: FilePath+                       , _outPath :: FilePath+                       }+               deriving Data++options :: ParserInfo Options+options = info (helper <*> parser)+               (  progDesc "Draws gpx trajectories in ipe"+               <> header   "DrawGPX"+               )+  where+    parser = Options+          <$> strOption (help "Input Directory"+                         <> short 'i'+                         <> metavar "INDIR"+                        )+          <*> strOption (help "Output File"+                         <> short 'o'+                         <> metavar "TARGET"+                        )++--------------------------------------------------------------------------------++mainWith                          :: Options -> IO ()+mainWith (Options inPath outPath) = do+    let inPath' = inPath ++ "/"+    files <- map (inPath' ++) . filter (isSuffixOf ".gpx")+         <$> getDirectoryContents inPath'+    tks   <- concatMap (_tracks . combineTracks) <$> mapM readGPXFile files+    let polies  = mapMaybe asPolyLine tks+        polies' = map (douglasPeucker 0.01 . scaleUniformlyBy 100) polies+        pg = singlePageFromContent $ map (asIpeObject' mempty) polies'+    -- print pg+    writeIpeFile outPath pg+++colors :: [T.Text]+colors = map (T.unwords . map (T.pack . printf "%.4f" . (/ 256.0))) colors'+  where+    colors' :: [[Double]]+    -- colors' = [ [84,48,5]+    --           , [140,81,10]+    --           , [191,129,45]+    --           , [223,194,125]+    --           , [246,232,195]+    --           , [245,245,245]+    --           , [199,234,229]+    --           , [128,205,193]+    --           , [53,151,143]+    --           , [1,102,94]+    --           , [0,60,48]+    --           , [0,0,0]+    --           ]+    colors' = [ [166,206,227]+              , [31,120,180]+              , [178,223,138]+              , [51,160,44]+              , [251,154,153]+              , [227,26,28]+              , [253,191,111]+              , [255,127,0]+              , [202,178,214]+              , [106,61,154]+              , [255,255,153]+              , [177,89,40]+              ]++-- readCoords    :: FilePath -> IO (PolyLine 2 () Double)+-- readCoords fp = fromPoints .+--                 map ((\[x,y] -> point2 x y :+ ()) . map read . words) . lines+--              <$> readFile fp++-- readCoords'    :: FilePath -> IO [PolyLine 2 () Double]+-- readCoords' fp = mapMaybe (fmap fromPoints . g . f)  .  group' . lines <$> readFile fp+--   where+--     f = map ((\[x,y] -> point2 x y :+ ()) . map read . words)+--     g xs@(_:_:_) = Just xs+--     g _          = Nothing++-- group' lst = case break (== "NL") lst of+--                ([],[]) -> []+--                ([],"NL":r) -> group' r+--                (pr,"NL":r) -> pr:group' r+--                (pr,[])     -> [pr]++++-- maps = mapM (\f -> readCoords $ "/Users/frank/tmp/bikerides/maps/" ++ f)+--        [ "nld_coords.txt"+--        , "bel_coords.txt"+--        , "dnk_coords.txt"+--        , "fra_coords.txt"+--        ]++-- --   do+-- --     nld <- readCoords+-- --     writePolyLineFile "/tmp/nld.ipe" $  map (flip stroke "black") [nld]+++++++asPolyLine :: Track -> Maybe (PolyLine 2 UTCTime Double)+asPolyLine = fmap fromPoints . f . map toPt . _trackPoints+  where+    f xs@(_:_:_) = Just xs+    f _          = Nothing++toPt :: TrackPoint -> Point 2 Double :+ Time+toPt (TP (pos :+ t)) = point2 (pos^.longitude) (pos^.latitude) :+ t++ssFactor = 1++worldWidth  = 1000+worldHeight = 1000+world = (worldWidth,worldHeight)++-- colors = [ "red" , "purple" , "blue" , "green" , "orange"  ]++strokeByMonth p = stroke p c+  where+    dt = p^.points.to F.toList.to head.extra+    (_,m,_) = toGregorian $ utctDay dt+    c = colors !! (m -1)++stroke p c = (p&points.traverse.extra .~ (),[("stroke",c)])+++groupsOf      :: Int -> [a] -> [[a]]+groupsOf _ [] = []+groupsOf k xs = let (ys,xss) = splitAt k xs in ys : groupsOf k xss++subsample   :: Int -> [a] -> [a]+subsample k = map head . groupsOf k++subsampleTrack   :: Int -> Track -> Track+subsampleTrack k = over trackPoints (subsample k)+++-- | Given the width and height of the map and a Position, compute a Mercato Projection of+-- the position. See+-- <http://en.wikipedia.org/wiki/Mercator_projection#Derivation_of_the_Mercator_projection+-- WikiPedia> for more info.+mercatoProject                    :: (Double,Double)+                                  -> Position+                                  -> Point 2 Double+mercatoProject (width,height) pos = point2 x y+  where+    x    =                (width / 360)    * pos^.longitude+    y    = (height / 2) - (width / (2*pi)) * (log . tan $ (pi / 4) + (latR / 2))+    latR = -1 * pos^.latitude * pi / 180
+ examples/Demo/GPXParser.hs view
@@ -0,0 +1,112 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeOperators #-}+module Demo.GPXParser where+++import qualified Data.ByteString.Lazy as B++import           Control.Applicative+import           Control.Monad+import           Data.Maybe+import           Data.Time.Clock+import           Data.Time.Format+import           Text.XML.Expat.Tree++import           Control.Lens++import           Data.Geometry.Point+import           Data.Ext++import           Debug.Trace++--------------------------------------------------------------------------------++type Time = UTCTime++newtype Position = Position {_unP :: Point 2 Double}  deriving (Show,Eq)+makeLenses ''Position++latitude :: Lens' Position Double+latitude = unP.xCoord++longitude :: Lens' Position Double+longitude = unP.yCoord+++newtype TrackPoint = TP {_unTP :: Position :+ Time} deriving (Show,Eq)+makeLenses ''TrackPoint++newtype Track = Track { _trackPoints :: [TrackPoint] } deriving (Show,Eq)+makeLenses ''Track++newtype Activity = Activity { _tracks :: [Track]} deriving (Show,Eq)+makeLenses ''Activity+++combineTracks (Activity ts) = Activity [Track $ concatMap _trackPoints ts]+++readGPXFile    :: FilePath -> IO Activity+readGPXFile fp = (r . fst . parse defaultParseOptions) <$> B.readFile fp+  where+    -- l m = error . show $ m+    r = fromJust . parseGPX++class ReadGPX t where+  parseGPX :: Node String String -> Maybe t++instance ReadGPX Activity where+  parseGPX x = case selectPath ["gpx"] x of+    [x@(Element _ _ chs)] -> Just . Activity . mapMaybe parseGPX . chsWith "trk" $ x+++    --                      concatMap (selectPath [""Track"]) $ chs+    -- _                 -> Nothing++++instance ReadGPX Track where+  parseGPX x@(Element "trk" _ _) = Just . Track . mapMaybe parseGPX . concatMap (chsWith "trkpt") . chsWith "trkseg" $ x+++++instance ReadGPX TrackPoint where+  parseGPX x@(Element "trkpt" ats _) = (\p t -> TP $ p :+ t) <$> pos <*> time+    where+      pos  = (\l l' -> Position $ point2 l l') <$> lat <*> lon+      time = fmap (readTime' . extract) . listToMaybe . chsWith "time" $ x++      lat = read <$> lookup "lat" ats+      lon = read <$> lookup "lon" ats+++extract = (\(Text s) -> s) . head . eChildren++readTime' :: String -> UTCTime+readTime' = readTime defaultTimeLocale "%0C%y-%m-%dT%TZ"++-- instance ReadGPX Position where+--   parseGPX x@(Element "Position" _ _) = (\l l' -> Position $ point2 l l') <$> lat <*> lon++--     where+--       f n = listToMaybe . map (read . extract) . (chsWith n) $ x+--       lat = f "LatitudeDegrees"+--       lon = f "LongitudeDegrees"++selectPath :: [String] -> Node String String -> [Node String String]+selectPath []  _ = []+selectPath [n] x | hasName n x = [x]+                 | otherwise   = []+selectPath (n:p) x | hasName n x = concatMap (selectPath p) $ eChildren x+                   | otherwise   = []++chsWith   :: String -> Node String String -> [Node String String]+chsWith n = filter (hasName n) . eChildren++hasName   :: String -> Node String String -> Bool+hasName _ (Text _) = False+hasName n (Element n' _ _) = n == n'
+ examples/Demo/MinDisk.hs view
@@ -0,0 +1,78 @@+{-# LANGUAGE ScopedTypeVariables #-}+module Demo.MinDisk where++import           Algorithms.Geometry.SmallestEnclosingBall.RandomizedIncrementalConstruction+import           Algorithms.Geometry.SmallestEnclosingBall.Types++import           Control.Applicative+import           Control.Lens+import           Data.Data+import           Data.Ext+import qualified Data.Foldable as F+import qualified Data.Traversable as Tr+import           Data.Geometry+import           Data.Geometry.Ball+import           Data.Geometry.Line+import           Data.Geometry.PolyLine+import           Data.Monoid++--import           Data.Geometry.Ipe++import           Data.Geometry.Ipe+import           Data.Geometry.Ipe.Types+import           Data.Maybe+import           Data.Seq2+import           Data.Vinyl+import           System.Environment(getArgs)+import           System.Random+import           Options.Applicative++--------------------------------------------------------------------------------++newtype Options = Options { inPath  :: FilePath }+                deriving Data++options :: ParserInfo Options+options = info (helper <*> parser)+               (  progDesc "Given an ipe file with a set of points, computes the smallest enclosing disk of the points."+               <> header   "MinDisk - Computes the smallest enclosing disk of a set of points"+               )+  where+    parser = Options+          <$> strOption (help "Input File (in ipe7 xml format)")++--------------------------------------------------------------------------------++diskResult :: Floating r => IpeOut (DiskResult p r) (IpeObject r)+diskResult = IpeOut f+  where+    f (DiskResult d pts) = asIpeGroup (asIpeObject d mempty : (F.toList . fmap g $ pts))+    g p = asIpeObject (p^.core) mempty+++mainWith              :: Options -> IO ()+mainWith (Options fp) = do+    ep <- readSinglePageFile fp+    gen <- getStdGen+    case ep of+      Left err                       -> print err+      Right (ipeP :: IpePage Double) ->+        case map ext $ ipeP^..content.Tr.traverse._IpeUse.core.symbolPoint of+          pts@(_:_:_) -> do+                           let res = smallestEnclosingDisk gen pts+                           printAsIpeSelection . asIpe diskResult $ res+          _           -> putStrLn "Not enough points!"+++      -- polies = ipeP^..content.Tr.traverse._IpePath.core._asPolyLine++      -- print pls+      -- mapM_ (print . (^.enclosingDisk) . minDisk' gen) polies+      -- mapM_ (printAsIpeSelection . asIpe diskResult . minDisk' gen) polies+++minDisk' :: RandomGen g => g -> PolyLine 2 () Double -> DiskResult () Double+minDisk' = minDisk++minDisk    :: (Ord r, Fractional r, RandomGen g) => g -> PolyLine 2 () r -> DiskResult () r+minDisk gen pl = smallestEnclosingDisk gen (F.toList $ pl^.points)
+ examples/Demo/WriteEnsemble.hs view
@@ -0,0 +1,96 @@+module Demo.WriteEnsemble where++import Control.Lens+import Data.Data+import Data.Ext+import Control.Applicative+import Data.Fixed+import qualified Data.Text as T+import qualified Data.Text.IO as TIO+import Data.Geometry.Ipe+import Data.Geometry+import Data.Geometry.PolyLine(fromPoints)+import System.Environment+import System.Directory+import Data.List(isSuffixOf)+import Data.Monoid+import Data.Time.Calendar+import Options.Applicative+++--------------------------------------------------------------------------------++data Options = Options { kind    :: String+                       , inPath  :: FilePath+                       , outPath :: FilePath+                       }+               deriving Data+++options :: ParserInfo Options+options = info (helper <*> parser)+               (  progDesc "Converts ensembles to ipe files"+               <> header   "ensemblewriter - writes a weather ensembles to a ipe files"+               )+  where+    parser = Options+          <$> strOption (help "Kind of input data in the input files" )+          <*> strOption (help "Input Directory")+          <*> strOption (help "Output Directory")++--------------------------------------------------------------------------------++-- read a bunch of text files, each defining a time-series (ensemble), produce+-- an ipe file where each time-series is represented by a polyline.++main :: IO ()+main = execParser options >>= mainWith++mainWith                               :: Options -> IO ()+mainWith (Options kind inPath outPath) = do+    inFiles <- filter (".dat" `isSuffixOf`) <$> getDirectoryContents inPath+    let f = case kind of+          "precip" -> asPrecipPt+          _        -> asTempPt+    polies <- mapM (fmap (asPts f) . readFile' . ((inPath ++ "/") ++)) inFiles+    let polies' = map (fromPoints . take 100) . trim $ polies+    writeIpeFile outPath . singlePageFromContent . map (flip asIpeObject mempty) $ polies'++readFile'    :: String -> IO T.Text+readFile' fp = putStrLn fp >> TIO.readFile fp++maxStartDay :: [[core :+ Day]] -> Day+maxStartDay = maximum . map ((^.extra) . head)++-- | Find the last starting day in the file, and trim all the lists s.t. they+-- all start at or after this day.+trim    :: [[Point 2 Milli :+ Day]] -> [[Point 2 Milli :+ Day]]+trim xs = let m      = maxStartDay xs+              startD = fromIntegral $ toModifiedJulianDay m+          in map ( map (\p -> p&core.xCoord %~ subtract startD)+                 . dropWhile (\x -> x^.extra < m)+                 ) xs+++-- force'   :: Show r => IO (PolyLine 2 () r) -> IO (PolyLine 2 () r)+-- force' mkP = mkP >>= \p -> (putStrLn $ show p) >> return p++read' :: Read a => T.Text -> a+read' = read . T.unpack++asPts   :: ([T.Text] -> b) -> T.Text -> [b]+asPts f = map (f . T.words) . filter (\l -> T.head l /= '#') . T.lines++-- | read a line of the form: yyyy mm dd value+asTempPt    :: [T.Text] -> Point 2 Milli :+ Day+asTempPt ts = let [y,m,d] = map read' $ Prelude.init ts+                  v       = read' $ last ts+                  day     = fromGregorian y (fromInteger m) (fromInteger d)+              in point2 (fromIntegral $ (toModifiedJulianDay day)) (10 * v) :+ day++-- | read a line of the form: yyyymmdd value+asPrecipPt       :: [T.Text] -> Point 2 Milli :+ Day+asPrecipPt [t,v] = let (y,t') = T.splitAt 4 t+                       (m,d)  = T.splitAt 2 t'+                       day    = fromGregorian (read' y) (read' m) (read' d)+                   in point2 (fromIntegral $ (toModifiedJulianDay day)) (10 * read' v) :+ day
+ examples/Main.hs view
@@ -0,0 +1,74 @@+module Main where++import Data.Monoid+import Control.Applicative+import Options.Applicative+import Data.Data++--------------------------------------------------------------------------------++import qualified Demo.DrawGPX as DrawGPX+import qualified Demo.WriteEnsemble as EnsembleWriter+import qualified Demo.MinDisk as MinDisk+import qualified Demo.Delaunay as Delaunay+++++--------------------------------------------------------------------------------++data Options = BAPC           BAPCOptions+             | DrawGPX        DrawGPX.Options+             | EnsembleWriter EnsembleWriter.Options+             | MinDisk        MinDisk.Options+             | Delaunay       Delaunay.Options+             deriving Data++parser :: Parser Options+parser = subparser (+       command' DrawGPX        DrawGPX.options+    <> command' EnsembleWriter EnsembleWriter.options+    <> command' MinDisk        MinDisk.options+    <> command' Delaunay       Delaunay.options+    )+++mainWith       :: Options -> IO ()+mainWith opts' = case opts' of+  BAPC _              -> putStrLn "not yet"+  DrawGPX opts        -> DrawGPX.mainWith opts+  EnsembleWriter opts -> EnsembleWriter.mainWith opts+  MinDisk opts        -> MinDisk.mainWith opts+  Delaunay opts       -> Delaunay.mainWith opts+++--------------------------------------------------------------------------------++options :: ParserInfo Options+options = info (helper <*> parser)+               (  progDesc "Example programs for HGeometry. Use -h to get a list of programs."+               <> header   "hgeometry-examples - Examples for HGeometry"+               )++--------------------------------------------------------------------------------++main :: IO ()+main = execParser options >>= mainWith+++command'          :: Data o => (a -> o) -> ParserInfo a -> Mod CommandFields o+command' constr p = command (show . toConstr $ constr undefined) (constr <$> p)+++noOpts :: InfoMod () -> ParserInfo ()+noOpts = info (pure ())++++++++data BAPCOptions = BAPCOptions { year :: Int+                               }+                   deriving Data
hgeometry.cabal view
@@ -2,30 +2,46 @@ -- documentation, see http://haskell.org/cabal/users-guide/  name:                hgeometry-version:             0.4.0.0-synopsis:            Data types for geometric objects, geometric algorithms, and data  structures.+version:             0.5.0.0+synopsis:            Geometric Algorithms, Data structures, and Data types. description:   HGeometry provides some basic geometry types, and geometric algorithms and   data structures for them. The main two focusses are: (1) Strong type safety,   and (2) implementations of geometric algorithms and data structures with good   asymptotic running time guarantees. -homepage:            http://fstaals.net/software/hgeometry+homepage:            https://fstaals.net/software/hgeometry license:             BSD3 license-file:        LICENSE author:              Frank Staals-maintainer:          f.staals@uu.nl+maintainer:          frank@fstaals.net -- copyright: +tested-with:         GHC >= 7.8.4+ category:            Geometry build-type:          Simple+ extra-source-files:  README.md+                     resources/basic.isy+                     test/Data/Geometry/pointInPolygon.ipe+                     test/Data/Geometry/Polygon/Convex/convexTests.ipe+                     test/Algorithms/Geometry/SmallestEnclosingDisk/manual.ipe+ cabal-version:       >=1.10 source-repository head   type:     git-  location: http://github.com/noinia/hgeometry+  location: https://github.com/noinia/hgeometry ++flag examples+  description: Build demonstration programs+  default:     False+  manual:      True+ library+  ghc-options: -Wall -fno-warn-unticked-promoted-constructors+   exposed-modules:  Data.Geometry                     Data.Geometry.Properties                     Data.Geometry.Vector@@ -33,11 +49,13 @@                     Data.Geometry.Transformation                     Data.Geometry.Vector.VectorFixed                     Data.Geometry.Interval+                    Data.Geometry.Boundary                      Data.Geometry.Point                     Data.Geometry.Line                     Data.Geometry.Line.Internal                     Data.Geometry.LineSegment+                    Data.Geometry.SubLine                     Data.Geometry.HalfLine                     Data.Geometry.PolyLine @@ -45,42 +63,70 @@                       -- Data.Geometry.Plane-+                    Data.Geometry.Slab                     Data.Geometry.Box+                    Data.Geometry.Box.Internal                     Data.Geometry.Ball                     Data.Geometry.Polygon+                    Data.Geometry.Polygon.Convex +                    Data.Geometry.Duality +                    Algorithms.Util+                     Algorithms.Geometry.ConvexHull.GrahamScan+                    Algorithms.Geometry.ConvexHull.Types+                    Algorithms.Geometry.ConvexHull.DivideAndConqueror++                    Algorithms.Geometry.SmallestEnclosingBall.Types                     Algorithms.Geometry.SmallestEnclosingBall.RandomizedIncrementalConstruction+                    Algorithms.Geometry.SmallestEnclosingBall.Naive -                    -- Data.TypeLevel.Common-                    -- Data.TypeLevel.Filter+                    Algorithms.Geometry.DelaunayTriangulation.Types+                    Algorithms.Geometry.DelaunayTriangulation.DivideAndConqueror+                    Algorithms.Geometry.DelaunayTriangulation.Naive -                    -- Data.Type.Nat-                    -- Data.Type.List+                    Algorithms.Geometry.PolyLineSimplification.DouglasPeucker -                    -- Data.Vinyl.Universe.Geometry-                    -- Data.Vinyl.Show-                    -- Data.Vinyl.Extra+                    Algorithms.Geometry.EuclideanMST.EuclideanMST ---                    Data.Geometry.Vector -                   Data.Geometry.Ipe-                   Data.Geometry.Ipe.Attributes-                   Data.Geometry.Ipe.Types-                   Data.Geometry.Ipe.Writer-                   Data.Geometry.Ipe.Reader-                   Data.Geometry.Ipe.PathParser+                    Algorithms.Graph.DFS+                    Algorithms.Graph.MST  -                   Data.Ext+                    Data.UnBounded+                    Data.Range -                   Data.Seq2-                   System.Random.Shuffle-                   Control.Monad.State.Persistent +                    Data.Geometry.Ipe+                    Data.Geometry.Ipe.Literal+                    Data.Geometry.Ipe.Attributes+                    Data.Geometry.Ipe.Types+                    Data.Geometry.Ipe.Writer+                    Data.Geometry.Ipe.Reader+                    Data.Geometry.Ipe.PathParser+                    Data.Geometry.Ipe.IpeOut+                    Data.Geometry.Ipe.FromIpe +++                    Data.Ext+                    Data.Seq2+                    Data.CircularSeq+                    Data.Sequence.Util+                    Data.BinaryTree+                    Data.CircularList.Util++                    Data.Permutation+                    Data.PlanarGraph+                    Data.PlaneGraph+++                    System.Random.Shuffle+                    Control.Monad.State.Persistent++   other-modules:                    Data.Geometry.Ipe.ParserPrimitives @@ -93,31 +139,38 @@                , vinyl            >= 0.5       &&     < 0.6                , linear           >= 1.10                , lens             >= 4.2-               , singletons       >= 1.0       &&     < 1.1-               , bifunctors       >= 4.0+               , singletons       >= 1.0       &&     < 2.0+               , bifunctors       >= 5+               , semigroupoids    >= 5+               , Frames           >= 0.1.0.0 -               , text             >= 0.11+               , text             >= 1.1.1.0                , bytestring       >= 0.10                 , semigroups       >= 0.15                , bifunctors       >= 4.1-               , validation       >= 0.4+               -- , validation       >= 0.4 -               , containers       >= 0.5                , parsec           >= 3-               -- , tranformers      >= 0.3+               -- , tranformers      > 0.3                 , vector           >= 0.10-               , fixed-vector     >= 0.6.4.0   &&     < 0.7+               , fixed-vector     >= 0.6.4.0                , data-clist       >= 0.0.7.2-               -- , HList            >= 0.3       &&     < 0.4 -               , hexpat           >= 0.20.7+               , hexpat           >= 0.20.9                , mtl                , random+               , template-haskell  +               , time+               , directory+               , optparse-applicative++   hs-source-dirs: src+                  -- examples/demo    default-language:    Haskell2010 @@ -131,17 +184,82 @@                     , RankNTypes                      , PatternSynonyms+                    , TupleSections+                    , LambdaCase                     , ViewPatterns                      , StandaloneDeriving                     , GeneralizedNewtypeDeriving+                    , DeriveFunctor+                    , DeriveFoldable+                    , DeriveTraversable++                    , AutoDeriveTypeable+                     , FlexibleInstances                     , FlexibleContexts+                    , MultiParamTypeClasses +executable hgeometry-examples+  if !flag(examples)+    buildable: False+  main-is: Main.hs +  if flag(examples)+    build-depends: base+                 , hgeometry+                 , lens+                 , containers+                 , vinyl+                 , Frames+                 , semigroups+                 , optparse-applicative+                 , text+                 , hexpat+                 , bytestring+                 , directory+                 , time+                 , random  +  hs-source-dirs: examples +  other-modules: Demo.DrawGPX+                 Demo.WriteEnsemble+                 Demo.MinDisk+                 Demo.Delaunay++                 Demo.GPXParser++  default-language: Haskell2010++  default-extensions: TypeFamilies+                    , GADTs+                    , KindSignatures+                    , DataKinds+                    , TypeOperators+                    , ConstraintKinds+                    , PolyKinds+                    , RankNTypes++                    , PatternSynonyms+                    , ViewPatterns++                    , StandaloneDeriving+                    , GeneralizedNewtypeDeriving+                    , DeriveFunctor+                    , DeriveFoldable+                    , DeriveTraversable++                    , DeriveDataTypeable+                    , AutoDeriveTypeable++                    , FlexibleInstances+                    , FlexibleContexts+                    , MultiParamTypeClasses+++ test-suite doctests   type:          exitcode-stdio-1.0   ghc-options:   -threaded@@ -150,9 +268,65 @@    default-language:    Haskell2010 -  -- Extensions are enabled in doctests.hs-  --  default-extensions:+test-suite hspec+  type:                 exitcode-stdio-1.0+  default-language:     Haskell2010+  hs-source-dirs:       test+  main-is:              Spec.hs +  other-modules: Data.RangeSpec+                 Data.Geometry.Ipe.ReaderSpec+                 Data.Geometry.PolygonSpec+                 Data.Geometry.PointSpec+                 Data.Geometry.Polygon.Convex.ConvexSpec++                 Algorithms.Geometry.SmallestEnclosingDisk.RISpec+                 Algorithms.Geometry.DelaunayTriangulation.DTSpec++                 Util+++  build-depends:        base+                      , hspec >= 2.1+                      , hgeometry+                      , Frames+                      , lens+                      , data-clist+                      , linear+                      , bytestring+                      , vinyl+                      , semigroups+                      , vector+                      , containers+                      , random++  default-extensions: TypeFamilies+                    , GADTs+                    , KindSignatures+                    , DataKinds+                    , TypeOperators+                    , ConstraintKinds+                    , PolyKinds+                    , RankNTypes++                    , PatternSynonyms+                    , ViewPatterns+                    , LambdaCase+++                    , StandaloneDeriving+                    , GeneralizedNewtypeDeriving+                    , DeriveFunctor+                    , DeriveFoldable+                    , DeriveTraversable++                    , AutoDeriveTypeable++                    , FlexibleInstances+                    , FlexibleContexts+                    , MultiParamTypeClasses+                    , OverloadedStrings+ test-suite bapc_examples   type:          exitcode-stdio-1.0   ghc-options:   -O2@@ -165,7 +339,10 @@                , lens                , data-clist                , linear+               , semigroups +  other-modules: BAPC2012.Gunslinger+                 BAPC2014.Armybase    default-language:    Haskell2010 
+ resources/basic.isy view
@@ -0,0 +1,141 @@+<?xml version="1.0"?>+<!DOCTYPE ipestyle SYSTEM "ipe.dtd">+<ipestyle name="basic">+<color name="red" value="1 0 0"/>+<color name="green" value="0 1 0"/>+<color name="blue" value="0 0 1"/>+<color name="yellow" value="1 1 0"/>+<color name="orange" value="1 0.647 0"/>+<color name="gold" value="1 0.843 0"/>+<color name="purple" value="0.627 0.125 0.941"/>+<color name="gray" value="0.745 0.745 0.745"/>+<color name="brown" value="0.647 0.165 0.165"/>+<color name="navy" value="0 0 0.502"/>+<color name="pink" value="1 0.753 0.796"/>+<color name="seagreen" value="0.18 0.545 0.341"/>+<color name="turquoise" value="0.251 0.878 0.816"/>+<color name="violet" value="0.933 0.51 0.933"/>+<color name="darkblue" value="0 0 0.545"/>+<color name="darkcyan" value="0 0.545 0.545"/>+<color name="darkgray" value="0.663 0.663 0.663"/>+<color name="darkgreen" value="0 0.392 0"/>+<color name="darkmagenta" value="0.545 0 0.545"/>+<color name="darkorange" value="1 0.549 0"/>+<color name="darkred" value="0.545 0 0"/>+<color name="lightblue" value="0.678 0.847 0.902"/>+<color name="lightcyan" value="0.878 1 1"/>+<color name="lightgray" value="0.827 0.827 0.827"/>+<color name="lightgreen" value="0.565 0.933 0.565"/>+<color name="lightyellow" value="1 1 0.878"/>+<dashstyle name="dashed" value="[4] 0"/>+<dashstyle name="dotted" value="[1 3] 0"/>+<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>+<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>+<pen name="heavier" value="0.8"/>+<pen name="fat" value="1.2"/>+<pen name="ultrafat" value="2"/>+<textsize name="large" value="\large"/>+<textsize name="Large" value="\Large"/>+<textsize name="LARGE" value="\LARGE"/>+<textsize name="huge" value="\huge"/>+<textsize name="Huge" value="\Huge"/>+<textsize name="small" value="\small"/>+<textsize name="footnote" value="\footnotesize"/>+<textsize name="tiny" value="\tiny"/>+<symbolsize name="small" value="2"/>+<symbolsize name="tiny" value="1.1"/>+<symbolsize name="large" value="5"/>+<arrowsize name="small" value="5"/>+<arrowsize name="tiny" value="3"/>+<arrowsize name="large" value="10"/>+<gridsize name="4 pts" value="4"/>+<gridsize name="8 pts (~3 mm)" value="8"/>+<gridsize name="16 pts (~6 mm)" value="16"/>+<gridsize name="32 pts (~12 mm)" value="32"/>+<gridsize name="10 pts (~3.5 mm)" value="10"/>+<gridsize name="20 pts (~7 mm)" value="20"/>+<gridsize name="14 pts (~5 mm)" value="14"/>+<gridsize name="28 pts (~10 mm)" value="28"/>+<gridsize name="56 pts (~20 mm)" value="56"/>+<anglesize name="90 deg" value="90"/>+<anglesize name="60 deg" value="60"/>+<anglesize name="45 deg" value="45"/>+<anglesize name="30 deg" value="30"/>+<anglesize name="22.5 deg" value="22.5"/>+<symbol name="mark/circle(sx)" transformations="translations">+<path fill="sym-stroke">+0.6 0 0 0.6 0 0 e 0.4 0 0 0.4 0 0 e+</path></symbol>+<symbol name="mark/disk(sx)" transformations="translations">+<path fill="sym-stroke">+0.6 0 0 0.6 0 0 e+</path></symbol>+<symbol name="mark/fdisk(sfx)" transformations="translations">+<group><path fill="sym-fill">+0.5 0 0 0.5 0 0 e+</path><path fill="sym-stroke" fillrule="eofill">+0.6 0 0 0.6 0 0 e 0.4 0 0 0.4 0 0 e+</path></group></symbol>+<symbol name="mark/box(sx)" transformations="translations">+<path fill="sym-stroke" fillrule="eofill">+-0.6 -0.6 m 0.6 -0.6 l 0.6 0.6 l -0.6 0.6 l h+-0.4 -0.4 m 0.4 -0.4 l 0.4 0.4 l -0.4 0.4 l h</path></symbol>+<symbol name="mark/square(sx)" transformations="translations">+<path fill="sym-stroke">+-0.6 -0.6 m 0.6 -0.6 l 0.6 0.6 l -0.6 0.6 l h</path></symbol>+<symbol name="mark/fsquare(sfx)" transformations="translations">+<group><path fill="sym-fill">+-0.5 -0.5 m 0.5 -0.5 l 0.5 0.5 l -0.5 0.5 l h</path>+<path fill="sym-stroke" fillrule="eofill">+-0.6 -0.6 m 0.6 -0.6 l 0.6 0.6 l -0.6 0.6 l h+-0.4 -0.4 m 0.4 -0.4 l 0.4 0.4 l -0.4 0.4 l h</path></group></symbol>+<symbol name="mark/cross(sx)" transformations="translations">+<group><path fill="sym-stroke">+-0.43 -0.57 m 0.57 0.43 l 0.43 0.57 l -0.57 -0.43 l h</path>+<path fill="sym-stroke">+-0.43 0.57 m 0.57 -0.43 l 0.43 -0.57 l -0.57 0.43 l h</path>+</group></symbol>+<symbol name="arrow/arc(spx)">+<path pen="sym-pen" stroke="sym-stroke" fill="sym-stroke">+0 0 m -1.0 0.333 l -1.0 -0.333 l h</path></symbol>+<symbol name="arrow/farc(spx)">+<path pen="sym-pen" stroke="sym-stroke" fill="white">+0 0 m -1.0 0.333 l -1.0 -0.333 l h</path></symbol>+<symbol name="arrow/ptarc(spx)">+<path pen="sym-pen" stroke="sym-stroke" fill="sym-stroke">+0 0 m -1.0 0.333 l -0.8 0 l -1.0 -0.333 l h</path></symbol>+<symbol name="arrow/fptarc(spx)">+<path pen="sym-pen" stroke="sym-stroke" fill="white">+0 0 m -1.0 0.333 l -0.8 0 l -1.0 -0.333 l h</path></symbol>+<symbol name="arrow/fnormal(spx)">+<path pen="sym-pen" stroke="sym-stroke" fill="white">+0 0 m -1.0 0.333 l -1.0 -0.333 l h</path></symbol>+<symbol name="arrow/pointed(spx)">+<path pen="sym-pen" stroke="sym-stroke" fill="sym-stroke">+0 0 m -1.0 0.333 l -0.8 0 l -1.0 -0.333 l h</path></symbol>+<symbol name="arrow/fpointed(spx)">+<path pen="sym-pen" stroke="sym-stroke" fill="white">+0 0 m -1.0 0.333 l -0.8 0 l -1.0 -0.333 l h</path></symbol>+<symbol name="arrow/linear(spx)">+<path pen="sym-pen" stroke="sym-stroke">+-1.0 0.333 m 0 0 l -1.0 -0.333 l</path></symbol>+<symbol name="arrow/fdouble(spx)">+<path pen="sym-pen" stroke="sym-stroke" fill="white">+0 0 m -1.0 0.333 l -1.0 -0.333 l h+-1 0 m -2.0 0.333 l -2.0 -0.333 l h+</path></symbol>+<symbol name="arrow/double(spx)">+<path pen="sym-pen" stroke="sym-stroke" fill="sym-stroke">+0 0 m -1.0 0.333 l -1.0 -0.333 l h+-1 0 m -2.0 0.333 l -2.0 -0.333 l h+</path></symbol>+<tiling name="falling" angle="-60" width="1" step="4"/>+<tiling name="rising" angle="30" width="1" step="4"/>+<textstyle name="center" begin="\begin{center}"+end="\end{center}"/>+<textstyle name="itemize" begin="\begin{itemize}"+end="\end{itemize}"/>+<textstyle name="item" begin="\begin{itemize}\item{}"+end="\end{itemize}"/>+</ipestyle>+
+ src/Algorithms/Geometry/ConvexHull/DivideAndConqueror.hs view
@@ -0,0 +1,30 @@+module Algorithms.Geometry.ConvexHull.DivideAndConqueror( convexHull+                                                        , module Types+                                                        ) where++import           Data.Semigroup.Foldable+import           Data.Semigroup+import           Algorithms.Geometry.ConvexHull.Types as Types+import           Control.Lens((^.))+import           Data.BinaryTree+import           Data.Ext+import           Data.Function(on)+import           Data.Geometry.Point+import           Data.Geometry.Polygon+import qualified Data.Geometry.Polygon.Convex as Convex+import qualified Data.List.NonEmpty as NonEmpty++-- | O(n log n) time ConvexHull using divide and conqueror.+convexHull :: (Ord r, Num r)+           => NonEmpty.NonEmpty (Point 2 r :+ p) -> ConvexHull p r+convexHull = unMerge+           . foldMap1 (Merge . ConvexHull . fromPoints . (:[]) . _unElem)+           . asBalancedBinLeafTree+           . NonEmpty.sortBy (compare `on` (^.core))++newtype Merge r p = Merge { unMerge :: ConvexHull p r }++instance (Num r, Ord r) => Semigroup (Merge r p) where+  (Merge lp) <> (Merge rp) = Merge . ConvexHull $ ch+    where+      (ch,_,_) = Convex.merge (lp^.extractHull) (rp^.extractHull)
src/Algorithms/Geometry/ConvexHull/GrahamScan.hs view
@@ -1,65 +1,54 @@-module Algorithms.Geometry.ConvexHull.GrahamScan( ConvexHull(..)-                                                , DegenerateCH-                                                , convexHull+module Algorithms.Geometry.ConvexHull.GrahamScan( convexHull                                                 , upperHull                                                 , lowerHull+                                                , module Types                                                 ) where +import           Algorithms.Geometry.ConvexHull.Types as Types import           Control.Lens((^.)) import           Data.Ext import           Data.Geometry.Point import           Data.Geometry.Polygon-import qualified Data.List as L+import qualified Data.List.NonEmpty as NonEmpty import           Data.Monoid----- | Two dimensional convex hulls-newtype ConvexHull p r = ConvexHull { _hull :: (SimplePolygon p r) }---                       deriving (Show,Eq)--type DegenerateCH p r = Maybe (Point 2 r :+ p)+import           Data.List.NonEmpty(NonEmpty(..))  --- | O(n log n) time ConvexHull using Graham-Scan-convexHull     :: (Ord r, Num r)-               => [Point 2 r :+ p] -> Either (DegenerateCH p r) (ConvexHull p r)-convexHull []  = Left Nothing-convexHull [p] = Left (Just p)-convexHull ps  = let ps' = L.sortBy incXdecY ps-                     uh  = tail . hull' $         ps'-                     lh  = tail . hull' $ reverse ps'-                 in Right . ConvexHull . fromPoints $ lh ++ uh+-- | O(n log n) time ConvexHull using Graham-Scan. The resulting polygon is+-- given in clockwise order.+convexHull            :: (Ord r, Num r)+                      => NonEmpty (Point 2 r :+ p) -> ConvexHull p r+convexHull (p :| []) = ConvexHull . fromPoints $ [p]+convexHull ps        = let ps' = NonEmpty.toList . NonEmpty.sortBy incXdecY $ ps+                           uh  = NonEmpty.tail . hull' $         ps'+                           lh  = NonEmpty.tail . hull' $ reverse ps'+                       in ConvexHull . fromPoints . reverse $ lh ++ uh -upperHull  :: (Ord r, Num r)-           => [Point 2 r :+ p] -> Either (DegenerateCH p r) [Point 2 r :+ p]+upperHull  :: (Ord r, Num r) => NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p) upperHull = hull id  -lowerHull  :: (Ord r, Num r)-           => [Point 2 r :+ p] -> Either (DegenerateCH p r) [Point 2 r :+ p]+lowerHull :: (Ord r, Num r) => NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p) lowerHull = hull reverse   -- | Helper function so that that can compute both the upper or the lower hull, depending -- on the function f-hull       :: (Ord r, Num r)-           => ([Point 2 r :+ p] -> [Point 2 r :+ p])-           -> [Point 2 r :+ p]-           -> Either (DegenerateCH p r) [Point 2 r :+ p]-hull _ []  = Left Nothing-hull _ [p] = Left (Just p)-hull f ps  = Right . hull' . f . L.sortBy incXdecY $ ps--+hull               :: (Ord r, Num r)+                   => ([Point 2 r :+ p] -> [Point 2 r :+ p])+                   -> NonEmpty (Point 2 r :+ p) -> NonEmpty (Point 2 r :+ p)+hull f h@(_ :| []) = h+hull f pts         = hull' .  f+                   . NonEmpty.toList . NonEmpty.sortBy incXdecY $ pts -incXdecY  :: Ord a => Ext t (Point 2 a) -> Ext t1 (Point 2 a) -> Ordering+incXdecY  :: Ord r => (Point 2 r) :+ p -> (Point 2 r) :+ q -> Ordering incXdecY (Point2 px py :+ _) (Point2 qx qy :+ _) =   compare px qx <> compare qy py   -- | Precondition: The list of input points is sorted-hull'          :: (Ord r, Num r) => [Point 2 r :+ p] -> [Point 2 r :+ p]-hull' (a:b:ps) = hull'' [b,a] ps+hull'          :: (Ord r, Num r) => [Point 2 r :+ p] -> NonEmpty (Point 2 r :+ p)+hull' (a:b:ps) = NonEmpty.fromList $ hull'' [b,a] ps   where     hull'' h  []    = h     hull'' h (p:ps) = hull'' (cleanMiddle (p:h)) ps
+ src/Algorithms/Geometry/ConvexHull/Types.hs view
@@ -0,0 +1,14 @@+{-# LANGUAGE TemplateHaskell #-}+module Algorithms.Geometry.ConvexHull.Types where++import           Control.Lens+import           Data.Ext+import           Data.Geometry.Point+import           Data.Geometry.Polygon++++-- | Two dimensional convex hulls+newtype ConvexHull p r = ConvexHull { _extractHull :: (SimplePolygon p r) }+                       deriving (Show,Eq)+makeLenses ''ConvexHull
+ src/Algorithms/Geometry/DelaunayTriangulation/DivideAndConqueror.hs view
@@ -0,0 +1,297 @@+{-# LANGUAGE ScopedTypeVariables #-}+module Algorithms.Geometry.DelaunayTriangulation.DivideAndConqueror where++import           Algorithms.Geometry.ConvexHull.GrahamScan as GS+import           Algorithms.Geometry.DelaunayTriangulation.Types+import           Control.Applicative+import           Control.Lens+import           Control.Monad.Reader+import           Control.Monad.State+import           Data.BinaryTree+import qualified Data.CircularList as CL+import qualified Data.CircularSeq as CS+import qualified Data.CircularList.Util as CU+import           Data.Ext+import qualified Data.Foldable as F+import           Data.Function (on)+import           Data.Geometry+import           Data.Geometry.Ball (disk, insideBall)+import           Data.Geometry.Interval+import           Data.Geometry.Polygon+import qualified Data.Geometry.Polygon.Convex as Convex+import           Data.Geometry.Polygon.Convex (ConvexPolygon)+import qualified Data.IntMap.Strict as IM+import qualified Data.List as L+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Map as M+import           Data.Maybe (fromJust, fromMaybe)+import qualified Data.Vector as V++-------------------------------------------------------------------------------+-- * Divide & Conqueror Delaunay Triangulation+--+-- Implementation of the Divide & Conqueror algorithm as described in:+--+-- Two Algorithms for Constructing a Delaunay Triangulation+-- Lee and Schachter+-- International Journal of Computer and Information Sciences, Vol 9, No. 3, 1980+--+-- We store all adjacency lists in clockwise order+--+-- : If v on the convex hull, then its first entry in the adj. lists is its CCW+-- successor (i.e. its predecessor) on the convex hull+--+-- Rotating Right <-> rotate clockwise+++-- | Computes the delaunay triangulation of a set of points.+--+-- Running time: $O(n \log n)$+-- (note: We use an IntMap in the implementation. So maybe actually $O(n \log^2 n)$)+--+-- pre: the input is a *SET*, i.e. contains no duplicate points. (If the+-- input does contain duplicate points, the implementation throws them away)+delaunayTriangulation      :: (Ord r, Fractional r)+                           => NonEmpty.NonEmpty (Point 2 r :+ p) -> Triangulation p r+delaunayTriangulation pts' = Triangulation vtxMap ptsV adjV+  where+    pts    = nub' . NonEmpty.sortBy (compare `on` (^.core)) $ pts'+    ptsV   = V.fromList . F.toList $ pts+    vtxMap = M.fromList $ zip (map (^.core) . V.toList $ ptsV) [0..]++    tr     = _unElem <$> asBalancedBinLeafTree pts++    (adj,_) = delaunayTriangulation' tr (vtxMap,ptsV)+    adjV    = V.fromList . IM.elems $ adj++++-- : pre: - Input points are sorted lexicographically+delaunayTriangulation' :: (Ord r, Fractional r)+                       => BinLeafTree Size (Point 2 r :+ p)+                       -> Mapping p r+                       -> (Adj, ConvexPolygon (p :+ VertexID) r)+delaunayTriangulation' pts mapping'@(vtxMap,_)+  | size' pts == 1 = let (Leaf p) = pts+                         i        = lookup' vtxMap (p^.core)+                     in (IM.singleton i CL.empty, fromPoints [withID p i])+  | size' pts <= 3 = let pts'            = NonEmpty.fromList+                                         . map (\p -> withID p (lookup' vtxMap (p^.core)))+                                         . F.toList $ pts+                         (ConvexHull ch) = GS.convexHull pts'+                     in (fromHull mapping' ch, ch)+  | otherwise      = let (Node lt _ rt) = pts+                         (ld,lch)       = delaunayTriangulation' lt mapping'+                         (rd,rch)       = delaunayTriangulation' rt mapping'+                         (ch, bt, ut)   = Convex.merge lch rch+                     in (merge ld rd bt ut mapping' (firsts ch), ch)++--------------------------------------------------------------------------------+-- * Implementation++-- | Mapping that says for each vtx in the convex hull what the first entry in+-- the adj. list should be. The input polygon is given in Clockwise order+firsts :: SimplePolygon (p :+ VertexID) r -> IM.IntMap VertexID+firsts = IM.fromList . map (\s -> (s^.end.extra.extra, s^.start.extra.extra))+       . F.toList . outerBoundaryEdges+++-- | Given a polygon; construct the adjacency list representation+-- pre: at least two elements+fromHull              :: Ord r => Mapping p r -> SimplePolygon (p :+ q) r -> Adj+fromHull (vtxMap,_) p = let vs@(u:v:vs') = map (lookup' vtxMap . (^.core))+                                         . F.toList . CS.rightElements $ p^.outerBoundary+                            es           = zipWith3 f vs (tail vs ++ [u]) (vs' ++ [u,v])+                            f prv c nxt  = (c,CL.fromList . L.nub $ [prv, nxt])+                        in IM.fromList es+++-- | Merge the two delaunay triangulations.+--+-- running time: $O(n)$ (although we cheat a bit by using a IntMap)+merge                            :: (Ord r, Fractional r)+                                 => Adj+                                 -> Adj+                                 -> LineSegment 2 (p :+ VertexID) r -- ^ lower tangent+                                 -> LineSegment 2 (p :+ VertexID) r -- ^ upper tangent+                                 -> Mapping p r+                                 -> Firsts+                                 -> Adj+merge ld rd bt ut mapping'@(vtxMap,_) fsts =+    flip runReader (mapping', fsts) . flip execStateT adj $ moveUp (tl,tr) l r+  where+    l   = lookup' vtxMap (bt^.start.core)+    r   = lookup' vtxMap (bt^.end.core)+    tl  = lookup' vtxMap (ut^.start.core)+    tr  = lookup' vtxMap (ut^.end.core)+    adj = ld `IM.union` rd++type Merge p r = StateT Adj (Reader (Mapping p r, Firsts))++type Firsts = IM.IntMap VertexID++-- | Merges the two delaunay traingulations.+moveUp          :: (Ord r, Fractional r)+                => (VertexID,VertexID) -> VertexID -> VertexID -> Merge p r ()+moveUp ut l r+  | (l,r) == ut = insert l r+  | otherwise   = do+                     insert l r+                     -- Get the neighbours of r and l along the convex hull+                     r1 <- pred' . rotateTo l . lookup'' r <$> get+                     l1 <- succ' . rotateTo r . lookup'' l <$> get++                     (r1',a) <- rotateR l r r1+                     (l1',b) <- rotateL l r l1+                     c       <- qTest l r r1' l1'+                     let (l',r') = case (a,b,c) of+                                     (True,_,_)          -> (focus' l1', r)+                                     (False,True,_)      -> (l,          focus' r1')+                                     (False,False,True)  -> (l,          focus' r1')+                                     (False,False,False) -> (focus' l1', r)+                     moveUp ut l' r'+++-- | ''rotates'' around r and removes all neighbours of r that violate the+-- delaunay condition. Returns the first vertex (as a Neighbour of r) that+-- should remain in the Delaunay Triangulation, as well as a boolean A that+-- helps deciding if we merge up by rotating left or rotating right (See+-- description in the paper for more info)+rotateR        :: (Ord r, Fractional r)+               => VertexID -> VertexID -> Vertex -> Merge p r (Vertex, Bool)+rotateR l r r1 = focus' r1 `isLeftOf` (l, r) >>= \case+                   True  -> (,False) <$> rotateR' l r r1 (pred' r1)+                   False -> pure (r1,True)++-- | The code that does the actual rotating+rotateR'     :: (Ord r, Fractional r)+             => VertexID -> VertexID -> Vertex -> Vertex -> Merge p r Vertex+rotateR' l r = go+  where+    go r1 r2 = qTest l r r1 r2 >>= \case+                 True  -> pure r1+                 False -> do modify $ delete r (focus' r1)+                             go r2 (pred' r2)+++-- | Symmetric to rotateR+rotateL     :: (Ord r, Fractional r)+                     => VertexID -> VertexID -> Vertex -> Merge p r (Vertex, Bool)+rotateL l r l1 = focus' l1 `isRightOf` (r, l) >>= \case+                   True  -> (,False) <$> rotateL' l r l1 (succ' l1)+                   False -> pure (l1,True)++-- | The code that does the actual rotating. Symmetric to rotateR'+rotateL'     :: (Ord r, Fractional r)+             => VertexID -> VertexID -> Vertex -> Vertex -> Merge p r Vertex+rotateL' l r = go+  where+    go l1 l2 = qTest l r l1 l2 >>= \case+                 True  -> pure l1+                 False -> do modify $ delete l (focus' l1)+                             go l2 (succ' l2)++--------------------------------------------------------------------------------+-- * Primitives used by the Algorithm++-- | returns True if the forth point (vertex) does not lie in the disk defined+-- by the first three points.+qTest         :: (Ord r, Fractional r)+              => VertexID -> VertexID -> Vertex -> Vertex -> Merge p r Bool+qTest h i j k = withPtMap . snd . fst <$> ask+  where+    withPtMap ptMap = let h' = ptMap V.! h+                          i' = ptMap V.! i+                          j' = ptMap V.! (focus' j)+                          k' = ptMap V.! (focus' k)+                      in not . maybe True ((k'^.core) `insideBall`) $ disk' h' i' j'+    disk' p q r = disk (p^.core) (q^.core) (r^.core)++-- | Inserts an edge into the right position.+insert     :: (Num r, Ord r) => VertexID -> VertexID -> Merge p r ()+insert u v = do+               (mapping',fsts) <- ask+               modify $ insert' u v mapping'+               rotateToFirst u fsts+               rotateToFirst v fsts+++-- | make sure that the first vtx in the adj list of v is its predecessor on the CH+rotateToFirst        :: VertexID -> Firsts -> Merge p r ()+rotateToFirst v fsts = modify $ IM.adjust f v+  where+    mfst   = IM.lookup v fsts+    f  cl  = fromMaybe cl $ mfst >>= flip CL.rotateTo cl+++-- | Inserts an edge (and makes sure that the vertex is inserted in the+-- correct. pos in the adjacency lists)+insert'               :: (Num r, Ord r)+                      => VertexID -> VertexID -> Mapping p r -> Adj -> Adj+insert' u v (_,ptMap) = IM.adjustWithKey (insert'' v) u+                      . IM.adjustWithKey (insert'' u) v+  where+    -- inserts b into the adjacency list of a+    insert'' bi ai = CU.insertOrdBy (cwCmpAround (ptMap V.! ai) `on` (ptMap V.!)) bi+++-- | Deletes an edge+delete     :: VertexID -> VertexID -> Adj -> Adj+delete u v = IM.adjust (delete' v) u . IM.adjust (delete' u) v+  where+    delete' x = CL.filterL (/= x) -- should we rotate left or right if it is the focus?+++++-- | Lifted version of Convex.IsLeftOf+isLeftOf           :: (Ord r, Num r)+                   => VertexID -> (VertexID, VertexID) -> Merge p r Bool+p `isLeftOf` (l,r) = withPtMap . snd . fst <$> ask+  where+    withPtMap ptMap = (ptMap V.! p) `Convex.isLeftOf` (ptMap V.! l, ptMap V.! r)++-- | Lifted version of Convex.IsRightOf+isRightOf           :: (Ord r, Num r)+                    => VertexID -> (VertexID, VertexID) -> Merge p r Bool+p `isRightOf` (l,r) = withPtMap . snd . fst <$> ask+  where+    withPtMap ptMap = (ptMap V.! p) `Convex.isRightOf` (ptMap V.! l, ptMap V.! r)++--------------------------------------------------------------------------------+-- * Some Helper functions+++lookup'     :: Ord k => M.Map k a -> k -> a+lookup' m x = fromJust $ M.lookup x m++size'              :: BinLeafTree Size a -> Size+size' (Leaf _)     = 1+size' (Node _ s _) = s++-- | an 'unsafe' version of rotateTo that assumes the element to rotate to+-- occurs in the list.+rotateTo   :: Eq a => a -> CL.CList a -> CL.CList a+rotateTo x = fromJust . CL.rotateTo x++-- | Adjacency lists are stored in clockwise order, so pred means rotate right+pred' :: CL.CList a -> CL.CList a+pred' = CL.rotR++-- | Adjacency lists are stored in clockwise order, so pred and succ rotate left+succ' :: CL.CList a -> CL.CList a+succ' = CL.rotL++focus' :: CL.CList a -> a+focus' = fromJust . CL.focus++-- | Removes duplicates from a sorted list+nub' :: Eq a => NonEmpty.NonEmpty (a :+ b) -> NonEmpty.NonEmpty (a :+ b)+nub' = fmap NonEmpty.head . NonEmpty.groupBy1 ((==) `on` (^.core))+++withID     :: c :+ e -> e' -> c :+ (e :+ e')+withID p i = p&extra %~ (:+i)++lookup'' :: Int -> IM.IntMap a -> a+lookup'' k m = fromJust . IM.lookup k $ m
+ src/Algorithms/Geometry/DelaunayTriangulation/Naive.hs view
@@ -0,0 +1,85 @@+module Algorithms.Geometry.DelaunayTriangulation.Naive where++import Algorithms.Geometry.DelaunayTriangulation.Types++import Control.Applicative+import Control.Monad(forM_)+import Control.Lens+import Data.Function(on)+import qualified Data.Foldable as F+import qualified Data.Vector as V+import qualified Data.Vector.Mutable as MV+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Map as M+import qualified Data.CircularList as C+import Data.Ext+import Data.Geometry+import Data.Geometry.Ball(disk, insideBall)+import qualified Data.List as L+++-- | Naive O(n^4) time implementation of the delaunay triangulation. Simply+-- tries each triple (p,q,r) and tests if it is delaunay, i.e. if there are no+-- other points in the circle defined by p, q, and r.+--+-- pre: the input is a *SET*, i.e. contains no duplicate points. (If the+-- input does contain duplicate points, the implementation throws them away)+delaunayTriangulation     :: (Ord r, Fractional r,       Show r, Show p)+                          => NonEmpty.NonEmpty (Point 2 r :+ p) -> Triangulation p r+delaunayTriangulation pts = Triangulation ptIds ptsV adjV+  where+    ptsV   = V.fromList . F.toList . NonEmpty.nubBy ((==) `on` (^.core)) $ pts+    ptIds  = M.fromList $ zip (map (^.core) . V.toList $ ptsV) [0..]+    adjV   = toAdjLists (ptIds,ptsV) . extractEdges $ fs+    n      = V.length ptsV - 1++    -- construct the list of faces/triangles in the delaunay triangulation+    fs = [ (p,q,r)+         | p <- [0..n], q <- [p..n], r <- [q..n], isDelaunay (ptIds,ptsV) p q r+         ]++-- | Given a list of edges, as vertexId pairs, construct a vector with the+-- adjacency lists, each in CW sorted order.+toAdjLists             :: (Num r, Ord r) => Mapping p r -> [(VertexID,VertexID)]+                       -> V.Vector (C.CList VertexID)+toAdjLists m@(_,ptsV) es = V.imap toCList $ V.create $ do+    v <- MV.replicate (V.length ptsV) []+    forM_ es $ \(i,j) -> do+      addAt v i j+      addAt v j i+    pure v+  where+    updateAt v i f = MV.read v i >>= \x -> MV.write v i (f x)+    addAt    v i j = updateAt v i (j:)++    -- convert to a CList, sorted in CCW order around point u+    toCList u = C.fromList . sortAround' m u++-- | Given a particular point u and a list of points vs, sort the points vs in+-- CW order around u.+-- running time: O(m log m), where m=|vs| is the number of vertices to sort.+sortAround'               :: (Num r, Ord r)+                          => Mapping p r -> VertexID -> [VertexID] -> [VertexID]+sortAround' (_,ptsV) u vs = reverse . map (^.extra) $ sortArround (f u) (map f vs)+  where+    f v = (ptsV V.! v)&extra .~ v++-- | Given a list of faces, construct a list of edges+extractEdges :: [(VertexID,VertexID,VertexID)] -> [(VertexID,VertexID)]+extractEdges = map head . L.group . L.sort+               . concatMap (\(p,q,r) -> [(p,q), (q,r), (p,r)])+               -- we encounter every edge twice. To get rid of the duplicates+               -- we sort, group, and take the head of the lists+++-- | Test if the given three points form a triangle in the delaunay triangulation.+-- running time: O(n)+isDelaunay                :: (Fractional r, Ord r)+                          => Mapping p r -> VertexID -> VertexID -> VertexID -> Bool+isDelaunay (_,ptsV) p q r = case disk (pt p) (pt q) (pt r) of+    Nothing -> False -- if the points are colinear, we interpret this as: all+                     -- pts in the plane are in the circle.+    Just d  -> not $ any (`insideBall` d)+      [pt i | i <- [0..(V.length ptsV - 1)], i /= p, i /= q, i /= r]+   where+     pt i = (ptsV V.! i)^.core
+ src/Algorithms/Geometry/DelaunayTriangulation/Types.hs view
@@ -0,0 +1,109 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Algorithms.Geometry.DelaunayTriangulation.Types where++import           Control.Lens+import qualified Data.CircularList as C+import           Data.Ext+import           Data.Geometry+import           Data.Geometry.Ipe+import qualified Data.IntMap.Strict as IM+import qualified Data.Map as M+import qualified Data.Map.Strict as SM+import           Data.Monoid (mempty)+import qualified Data.Permutation as P+import           Data.PlaneGraph+import qualified Data.Vector as V++import           Debug.Trace++--------------------------------------------------------------------------------++-- We store all adjacency lists in clockwise order++-- : If v on the convex hull, then its first entry in the adj. lists is its CCW+-- successor (i.e. its predecessor) on the convex hull++-- | Rotating Right <-> rotate clockwise++type VertexID = Int++type Vertex    = C.CList VertexID++type Adj = IM.IntMap (C.CList VertexID)++-- | Neighbours are stored in clockwise order: i.e. rotating right moves to the+-- next clockwise neighbour.+data Triangulation p r = Triangulation { _vertexIds  :: M.Map (Point 2 r) VertexID+                                       , _positions  :: V.Vector (Point 2 r :+ p)+                                       , _neighbours :: V.Vector (C.CList VertexID)+                                       }+                         deriving (Show,Eq)+makeLenses ''Triangulation+++type Mapping p r = (M.Map (Point 2 r) VertexID, V.Vector (Point 2 r :+ p))+++++showDT :: (Show p, Show r)  => Triangulation p r -> IO ()+showDT = mapM_ print . triangulationEdges+++triangulationEdges   :: Triangulation p r -> [(Point 2 r :+ p, Point 2 r :+ p)]+triangulationEdges t = let pts = _positions t+                       in map (\(u,v) -> (pts V.! u, pts V.! v)) . tEdges $ t+++tEdges :: Triangulation p r -> [(VertexID,VertexID)]+tEdges = concatMap (\(i,ns) -> map (i,) . filter (> i) . C.toList $ ns)+       . zip [0..] . V.toList . _neighbours++drawTriangulation :: IpeOut (Triangulation p r) (IpeObject r)+drawTriangulation = IpeOut $ \tr ->+    let es = map (uncurry ClosedLineSegment) . triangulationEdges $ tr+    in asIpeGroup $ map (\e -> asIpeObjectWith ipeLineSegment e mempty) es+++--------------------------------------------------------------------------------++data ST a b c = ST { fst' :: !a, snd' :: !b , trd' :: !c}++type ArcID = Int++-- | ST' is a strict triple (m,a,x) containing:+--+-- - m: a Map, mapping edges, represented by a pair of vertexId's (u,v) with+--            u < v, to arcId's.+-- - a: the next available unused arcID+-- - x: the data value we are interested in computing+type ST' a = ST (SM.Map (VertexID,VertexID) ArcID) ArcID a++toPlaneGraph    :: forall proxy s p r.+                   proxy s -> Triangulation p r -> PlaneGraph s Primal_ p () () r+toPlaneGraph _ tr = (planarGraph . P.toCycleRep n $ perm)&vertexData .~ tr^.positions+  where+    neighs    = C.rightElements <$> tr^.neighbours+    n         = sum . fmap length $ neighs++    vtxIDs = [0..]+    perm = trd' . foldr toOrbit (ST mempty 0 mempty) $ zip vtxIDs (V.toList neighs)++    -- | Given a vertex with its adjacent vertices (u,vs) (in CCW order) convert this+    -- vertex with its adjacent vertices into an Orbit+    toOrbit                     :: (VertexID,[VertexID]) -> ST' [[Dart s]]+                                -> ST' [[Dart s]]+    toOrbit (u,vs) (ST m a dss) =+      let (ST m' a' ds') = foldr (toDart . (u,)) (ST m a mempty) vs+      in ST m' a' (ds':dss)+++    -- | Given an edge (u,v) and a triplet (m,a,ds) we construct a new dart+    -- representing this edge.+    toDart                   :: (VertexID,VertexID) -> ST' [Dart s] -> ST' [Dart s]+    toDart (u,v) (ST m a ds) = let dir = if u < v then Positive else Negative+                                   t'  = (min u v, max u v)+                               in case M.lookup t' m of+      Just a' -> ST m                  a     (Dart (Arc a') dir : ds)+      Nothing -> ST (SM.insert t' a m) (a+1) (Dart (Arc a)  dir : ds)
+ src/Algorithms/Geometry/EuclideanMST/EuclideanMST.hs view
@@ -0,0 +1,47 @@+module Algorithms.Geometry.EuclideanMST.EuclideanMST where++import           Algorithms.Geometry.DelaunayTriangulation.DivideAndConqueror+import           Algorithms.Geometry.DelaunayTriangulation.Types+import           Algorithms.Graph.MST+import           Control.Lens+import           Data.Ext+import           Data.Geometry+import           Data.Geometry.Ipe+import qualified Data.List.NonEmpty as NonEmpty+import           Data.PlaneGraph+import           Data.Proxy+import           Data.Tree+++--------------------------------------------------------------------------------++-- | Computes the Euclidean Minimum Spanning Tree. We compute the Delaunay+-- Triangulation (DT), and then extract the EMST. Hence, the same restrictions+-- apply as for the DT:+--+-- pre: the input is a *SET*, i.e. contains no duplicate points. (If the input+-- does contain duplicate points, the implementation throws them away)+--+-- running time: $O(n \log n)$+euclideanMST     :: (Ord r, Fractional r)+                 => NonEmpty.NonEmpty (Point 2 r :+ p) -> Tree (Point 2 r :+ p)+euclideanMST pts = (\v -> g^.vDataOf v) <$> t+  where+    -- since we care only about the relative order of the edges we can use the+    -- squared Euclidean distance rather than the Euclidean distance, thus+    -- avoiding the Floating constraint+    g = withEdgeDistances squaredEuclideanDist . toPlaneGraph (Proxy :: Proxy MSTW)+      . delaunayTriangulation $ pts+    t = mst g+++data MSTW+++drawTree' :: IpeOut (Tree (Point 2 r :+ p)) (IpeObject r)+drawTree' = IpeOut $+  asIpeGroup . map (asIpeObject' mempty . uncurry ClosedLineSegment) . treeEdges+++treeEdges              :: Tree a -> [(a,a)]+treeEdges (Node v chs) = map ((v,) . rootLabel) chs ++ concatMap treeEdges chs
+ src/Algorithms/Geometry/PolyLineSimplification/DouglasPeucker.hs view
@@ -0,0 +1,57 @@+module Algorithms.Geometry.PolyLineSimplification.DouglasPeucker where++import Data.Semigroup+import Data.Ord(comparing)+import Control.Lens hiding (only)+import Data.Ext+import Data.Geometry.PolyLine+import Data.Geometry.Point+import Data.Geometry.Vector+import Data.Geometry.LineSegment+import qualified Data.Seq2 as S2+import qualified Data.Sequence as S+import qualified Data.Foldable as F++-- | Line simplification with the well-known Douglas Peucker alogrithm. Given a distance+-- value eps adn a polyline pl, constructs a simplification of pl (i.e. with+-- vertices from pl) s.t. all other vertices are within dist eps to the+-- original polyline.+--+-- Running time: O(n^2) worst case, O(n log n) expected.+douglasPeucker         :: (Ord r, Fractional r, Arity d)+                       => r -> PolyLine d p r -> PolyLine d p r+douglasPeucker eps pl+    | dst <= (eps*eps) = fromPoints [a,b]+    | otherwise        = douglasPeucker eps pref `merge` douglasPeucker eps subf+  where+    pts@(S2.Seq2 a _ b) = pl^.points+    (i,dst)             = maxDist pts (ClosedLineSegment a b)++    (pref,subf)         = split i pl++--------------------------------------------------------------------------------+-- * Internal functions++-- | Concatenate the two polylines, dropping their shared vertex+merge          :: PolyLine d p r -> PolyLine d p r -> PolyLine d p r+merge pref sub = PolyLine $ pref' >+< (sub^.points)+  where+    (pref' S2.:>> _) = S2.viewr $ pref^.points+    ~(a S2.:< as) >+< bs = S2.fromSeqUnsafe $ a S.<| as <> S2.toSeq bs++-- | Split the polyline at the given vertex. Both polylines contain this vertex+split                  :: Int -> PolyLine d p r+                       -> (PolyLine d p r, PolyLine d p r)+split i (PolyLine pts) = bimap f f (as,bs)+  where+    f = PolyLine . S2.fromSeqUnsafe+    as = S2.take (i+1) pts+    bs = S2.drop i     pts++-- | Given a sequence of points, find the index of the point that has the+-- Furthest distance to the LineSegment. The result is the index of the point+-- and this distance.+maxDist       :: (Ord r, Fractional r, Arity d)+              => S2.Seq2 (Point d r :+ p) -> LineSegment d p r -> (Int,r)+maxDist pts s = F.maximumBy (comparing snd) . S2.mapWithIndex (\i (p :+ _) ->+                                                     (i,sqDistanceToSeg p s)) $ pts
+ src/Algorithms/Geometry/SmallestEnclosingBall/Naive.hs view
@@ -0,0 +1,51 @@+module Algorithms.Geometry.SmallestEnclosingBall.Naive where++-- just for the types+import Control.Lens+import Data.Ext+import Algorithms.Geometry.SmallestEnclosingBall.Types+import Data.Geometry.Ball+import Data.Geometry.Point+import Data.List(minimumBy)+import Data.Function(on)+import Data.Maybe(fromMaybe)+import Algorithms.Util++--------------------------------------------------------------------------------++-- | Horrible O(n^4) implementation that simply tries all disks, checks if they+-- enclose all points, and takes the largest one. Basically, this is only useful+-- to check correctness of the other algorithm(s)+smallestEnclosingDisk          :: (Ord r, Fractional r)+                               => [Point 2 r :+ p]+                               -> DiskResult p r+smallestEnclosingDisk pts@(_:_:_) = smallestEnclosingDisk' pts $+                                      pairs pts ++ triplets pts+smallestEnclosingDisk _           = error "smallestEnclosingDisk: Too few points"++pairs     :: Fractional r => [Point 2 r :+ p] -> [DiskResult p r]+pairs pts = [DiskResult (fromDiameter (a^.core) (b^.core)) (Two a b)+            | SP a b <- uniquePairs pts]++triplets     :: (Ord r, Fractional r) => [Point 2 r :+ p] -> [DiskResult p r]+triplets pts = [DiskResult (disk' a b c) (Three a b c)+               | ST a b c <- uniqueTriplets pts]++disk'       :: (Ord r, Fractional r)+            => Point 2 r :+ p -> Point 2 r :+ p -> Point 2 r :+ p -> Disk () r+disk' a b c = fromMaybe degen $ disk (a^.core) (b^.core) (c^.core)+  where+    -- if the points are colinear, select the disk by the diametral pair+    degen = (smallestEnclosingDisk' [a,b,c] $ pairs [a,b,c])^.enclosingDisk+++-- | Given a list of canidate enclosing disks, report the smallest one.+smallestEnclosingDisk'     :: (Ord r, Num r)+                           => [Point 2 r :+ p] -> [DiskResult p r] -> DiskResult p r+smallestEnclosingDisk' pts = minimumBy (compare `on` (^.enclosingDisk.squaredRadius))+                           . filter (flip enclosesAll pts)+++-- | check if a disk encloses all points+enclosesAll   :: (Num r, Ord r) => DiskResult p r -> [Point 2 r :+ q] -> Bool+enclosesAll d = all (\(p :+ _) -> p `inClosedBall` (d^.enclosingDisk))
src/Algorithms/Geometry/SmallestEnclosingBall/RandomizedIncrementalConstruction.hs view
@@ -2,11 +2,13 @@ {-# LANGUAGE TemplateHaskell  #-} module Algorithms.Geometry.SmallestEnclosingBall.RandomizedIncrementalConstruction where +import           Algorithms.Geometry.SmallestEnclosingBall.Types+ import           Control.Lens import           Data.Ext import qualified Data.Foldable as F+import           Data.Geometry import           Data.Geometry.Ball-import           Data.Geometry.Point import qualified Data.List as L import           Data.List.NonEmpty import           Data.Maybe(fromMaybe)@@ -17,30 +19,17 @@   --- | List of two or three elements-data TwoOrThree a = Two !a !a | Three !a !a !a deriving (Show,Read,Eq,Ord,Functor)--instance F.Foldable TwoOrThree where-  foldMap f (Two   a b)   = f a <> f b-  foldMap f (Three a b c) = f a <> f b <> f c---- | The result of a smallest enclosing disk computation: The smallest ball---    and the points defining it-data DiskResult p r = DiskResult { _enclosingDisk  :: Circle () r-                                 , _definingPoints :: TwoOrThree (Point 2 r :+ p)-                                 }-makeLenses ''DiskResult- -- | O(n) expected time algorithm to compute the smallest enclosing disk of a -- set of points. we need at least two points. -- implemented using randomized incremental construction smallestEnclosingDisk           :: (Ord r, Fractional r, RandomGen g)                                 => g-                                -> Point 2 r :+ p -> Point 2 r :+ p -> [Point 2 r :+ p]+                                -> [Point 2 r :+ p]                                 -> DiskResult p r-smallestEnclosingDisk g p q pts = let (p':q':pts') = shuffle g (p:q:pts)-                                  in smallestEnclosingDisk' p' q' pts' +smallestEnclosingDisk g pts@(_:_:_) = let (p:q:pts') = shuffle g pts+                                      in smallestEnclosingDisk' p q pts'+smallestEnclosingDisk g _           = error "smallestEnclosingDisk: Too few points"  -- | Smallest enclosing disk. smallestEnclosingDisk'     :: (Ord r, Fractional r)@@ -78,8 +67,8 @@       | (r^.core) `inClosedBall` d = br       | otherwise                  = DiskResult (circle' r) (Three p q r) -    circle' r = fromMaybe degen $ circle (p^.core) (q^.core) (r^.core)-    degen = error "smallestEnclosingDisk: Unhandled degeneracy"+    circle' r = fromMaybe degen $ disk (p^.core) (q^.core) (r^.core)+    degen = error "smallestEnclosingDisk: Unhandled degeneracy, three points on a line"     -- TODO: handle degenerate case  
+ src/Algorithms/Geometry/SmallestEnclosingBall/Types.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE DeriveFunctor  #-}+{-# LANGUAGE TemplateHaskell  #-}+module Algorithms.Geometry.SmallestEnclosingBall.Types where++import           Data.Monoid+import qualified Data.Foldable as F+import           Data.Geometry+import           Data.Geometry.Ball+import           Control.Lens+import           Data.Ext++--------------------------------------------------------------------------------++-- | List of two or three elements+data TwoOrThree a = Two !a !a | Three !a !a !a deriving (Show,Read,Eq,Ord,Functor)++instance F.Foldable TwoOrThree where+  foldMap f (Two   a b)   = f a <> f b+  foldMap f (Three a b c) = f a <> f b <> f c+++fromList         :: [a] -> Either String (TwoOrThree a)+fromList [a,b]   = Right $ Two a b+fromList [a,b,c] = Right $ Three a b c+fromList _       = Left "Wrong number of elements"+++++-- | The result of a smallest enclosing disk computation: The smallest ball+--    and the points defining it+data DiskResult p r = DiskResult { _enclosingDisk  :: Disk () r+                                 , _definingPoints :: TwoOrThree (Point 2 r :+ p)+                                 }+makeLenses ''DiskResult
+ src/Algorithms/Graph/DFS.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE ScopedTypeVariables #-}+module Algorithms.Graph.DFS where++import           Control.Monad.ST (ST,runST)+import           Data.Maybe+import           Data.PlanarGraph+import           Data.Tree+import qualified Data.Vector as V+import qualified Data.Vector.Generic as GV+import qualified Data.Vector.Unboxed.Mutable as UMV+++-- | DFS on a planar graph.+--+-- Running time: $O(n)$+--+-- Note that since our planar graphs are always connected there is no need need+-- for dfs to take a list of start vertices.+dfs  :: forall s w v e f.+      PlanarGraph s w v e f -> VertexId s w -> Tree (VertexId s w)+dfs g = dfs' (adjacencyLists g)++-- | Adjacency list representation of a graph: for each vertex we simply list+-- all connected neighbours.+type AdjacencyLists s w = V.Vector [VertexId s w]++-- | Transform into adjacencylist representation+adjacencyLists   :: PlanarGraph s w v e f -> AdjacencyLists s w+adjacencyLists g = V.toList . flip neighboursOf g <$> vertices' g++-- | DFS, from a given vertex, on a graph in AdjacencyLists representation.+--+-- Running time: $O(n)$+dfs'          :: forall s w. AdjacencyLists s w -> VertexId s w -> Tree (VertexId s w)+dfs' g start = runST $ do+                 bv     <- UMV.replicate n False -- bit vector of marks+                 -- start will be unvisited, thus the fromJust is safe+                 fromJust <$> dfs'' bv start+  where+    n = GV.length g++    neighs              :: VertexId s w -> [VertexId s w]+    neighs (VertexId u) = g GV.! u++    visit   bv (VertexId i) = UMV.write bv i True+    visited bv (VertexId i) = UMV.read  bv i+    dfs''      :: UMV.MVector s' Bool -> VertexId s w+               -> ST s' (Maybe (Tree (VertexId s w)))+    dfs'' bv u = visited bv u >>= \case+                   True  -> pure Nothing+                   False -> do+                              visit bv u+                              Just . Node u . catMaybes <$> mapM (dfs'' bv) (neighs u)
+ src/Algorithms/Graph/MST.hs view
@@ -0,0 +1,152 @@+{-# LANGUAGE ScopedTypeVariables #-}+module Algorithms.Graph.MST( mst+                           , mstEdges+                           , makeTree+                           ) where++import           Algorithms.Graph.DFS (AdjacencyLists, dfs')+import           Control.Monad (forM_, when, filterM)+import           Control.Monad.ST (ST,runST)+import qualified Data.List as L+import           Data.PlanarGraph+import           Data.Tree+import qualified Data.Vector as V+import qualified Data.Vector.Mutable as MV+import qualified Data.Vector.Unboxed.Mutable as UMV++--------------------------------------------------------------------------------+++-- | Minimum spanning tree of the edges. The result is a rooted tree, in which+-- the nodes are the vertices in the planar graph together with the edge weight+-- of the edge to their parent. The root's weight is zero.+--+-- The algorithm used is Kruskal's.+--+-- running time: $O(n \log n)$+mst   :: Ord e => PlanarGraph s w v e f -> Tree (VertexId s w)+mst g = makeTree g $ mstEdges g+  -- TODO: Add edges/darts to the output somehow.++-- | Computes the set of edges in the Minimum spanning tree+--+-- running time: $O(n \log n)$+mstEdges   :: Ord e => PlanarGraph s w v e f -> [Dart s]+mstEdges g = runST $ do+          uf <- new (numVertices g)+          filterM (\e -> union uf (headOf e g) (tailOf e g)) edges''+  where+    edges'' = map fst . L.sortOn snd . V.toList $ edges g+++-- | Given an underlying planar graph, and a set of edges that form a tree,+-- create the actual tree.+--+-- pre: the planar graph has at least one vertex.+makeTree   :: forall s w v e f.+              PlanarGraph s w v e f -> [Dart s] -> Tree (VertexId s w)+makeTree g = flip dfs' start . mkAdjacencyLists+  where+    n = numVertices g+    start = V.head $ vertices' g++    append                  :: MV.MVector s' [a] -> VertexId s w -> a -> ST s' ()+    append v (VertexId i) x = MV.read v i >>= MV.write v i . (x:)++    mkAdjacencyLists         :: [Dart s] -> AdjacencyLists s w+    mkAdjacencyLists edges'' = V.create $ do+                                 vs <- MV.replicate n []+                                 forM_ edges'' $ \e -> do+                                   let u = headOf e g+                                       v = tailOf e g+                                   append vs u v+                                   append vs v u+                                 pure vs+--------------------------------------------------------------------------------++-- | Union find DS+newtype UF s a = UF { _unUF :: UMV.MVector s (Int,Int) }++new   :: Enum a => Int -> ST s (UF s a)+new n = do+          v <- UMV.new n+          forM_ [0..n-1] $ \i ->+            UMV.write v i (i,0)+          pure $ UF v++-- | Union the components containing x and y. Returns weather or not the two+-- components were already in the same component or not.+union               :: (Enum a, Eq a) => UF s a -> a -> a -> ST s Bool+union uf@(UF v) x y = do+                        (rx,rrx) <- find' uf x+                        (ry,rry) <- find' uf y+                        let b = rx /= ry+                            rx' = fromEnum rx+                            ry' = fromEnum ry+                        when b $ case rrx `compare` rry of+                            LT -> UMV.write v rx'  (ry',rrx)+                            GT -> UMV.write v ry' (rx',rry)+                            EQ -> do UMV.write v ry' (rx',rry)+                                     UMV.write v rx' (rx',rrx+1)+                        pure b+++-- | Get the representative of the component containing x+-- find    :: (Enum a, Eq a) => UF s a -> a -> ST s a+-- find uf = fmap fst . find' uf++-- | get the representative (and its rank) of the component containing x+find'             :: (Enum a, Eq a) => UF s a -> a -> ST s (a,Int)+find' uf@(UF v) x = do+                      (p,r) <- UMV.read v (fromEnum x) -- get my parent+                      if toEnum p == x then+                        pure (x,r) -- I am a root+                      else do+                        rt@(j,_) <- find' uf (toEnum p)  -- get the root of my parent+                        UMV.write v (fromEnum x) (fromEnum j,r)   -- path compression+                        pure rt+++--------------------------------------------------------------------------------++-- partial implementation of Prims+-- mst g = undefined++-- -- | runs MST with a given root+-- mstFrom     :: (Ord e, Monoid e)+--             => VertexId s w -> PlanarGraph s w v e f -> Tree (VertexId s w, e)+-- mstFrom r g = prims initialQ (Node (r,mempty) [])+--   where+--     update' k p q = Q.adjust (const p) k q++--     -- initial Q has the value of the root set to the zero element, and has no+--     -- parent. The others are all set to Top (and have no parent yet)+--     initialQ = update' r (ValT (mempty,Nothing))+--              . GV.foldr (\v q -> Q.insert v (Top,Nothing) q) Q.empty $ vertices g++--     prims qq t = case Q.minView qq of+--       Nothing -> t+--       Just (v Q.:-> (w,p), q) -> prims $++--------------------------------------------------------------------------------+-- Testing Stuff++-- testG = planarGraph' [ [ (Dart aA Negative, "a-")+--                        , (Dart aC Positive, "c+")+--                        , (Dart aB Positive, "b+")+--                        , (Dart aA Positive, "a+")+--                        ]+--                      , [ (Dart aE Negative, "e-")+--                        , (Dart aB Negative, "b-")+--                        , (Dart aD Negative, "d-")+--                        , (Dart aG Positive, "g+")+--                        ]+--                      , [ (Dart aE Positive, "e+")+--                        , (Dart aD Positive, "d+")+--                        , (Dart aC Negative, "c-")+--                        ]+--                      , [ (Dart aG Negative, "g-")+--                        ]+--                      ]+--   where+--     (aA:aB:aC:aD:aE:aG:_) = take 6 [Arc 0..]
+ src/Algorithms/Util.hs view
@@ -0,0 +1,20 @@+module Algorithms.Util where++import qualified Data.List as L+++data SP a b = SP !a !b deriving (Eq,Ord,Show)++-- | Given a list xs, generate all unique (unordered) pairs.+uniquePairs    :: [a] -> [SP a a]+uniquePairs xs = [ SP x y | (x:ys) <- nonEmptyTails xs, y <- ys ]++nonEmptyTails :: [a] -> [[a]]+nonEmptyTails = L.init . L.tails+++data ST a b c = ST !a !b !c deriving (Eq,Ord,Show)++-- | All unieuqe unordered triplets.+uniqueTriplets    :: [a] -> [ST a a a]+uniqueTriplets xs = [ ST x y z | (x:ys) <- nonEmptyTails xs, SP y z <- uniquePairs ys]
src/Control/Monad/State/Persistent.hs view
@@ -1,7 +1,3 @@-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-} module Control.Monad.State.Persistent( PersistentStateT                                      , PersistentState                                      , store
+ src/Data/BinaryTree.hs view
@@ -0,0 +1,63 @@+{-# Language DeriveFunctor #-}+{-# Language FunctionalDependencies #-}+module Data.BinaryTree where++import Control.Applicative+import Data.Foldable+import Data.List.NonEmpty(NonEmpty)+import Data.Semigroup+import Data.Traversable+import Data.Semigroup.Foldable++data BinLeafTree v a = Leaf a+                     | Node (BinLeafTree v a) v (BinLeafTree v a)+                     deriving (Show,Read,Eq,Ord,Functor)+++class Semigroup v => Measured v a | a -> v where+  measure :: a -> v++-- | smart constructor+node     :: Measured v a => BinLeafTree v a -> BinLeafTree v a -> BinLeafTree v a+node l r = Node l (measure l <> measure r) r+++instance Measured v a => Measured v (BinLeafTree v a) where+  measure (Leaf x)     = measure x+  measure (Node _ v _) = v+++instance Foldable (BinLeafTree v) where+  foldMap f (Leaf a)     = f a+  foldMap f (Node l _ r) = foldMap f l `mappend` foldMap f r++instance Foldable1 (BinLeafTree v)++instance Traversable (BinLeafTree v) where+  traverse f (Leaf a)     = Leaf <$> f a+  traverse f (Node l v r) = Node <$> traverse f l <*> pure v <*> traverse f r++instance Measured v a => Semigroup (BinLeafTree v a) where+  l <> r = node l r+++asBalancedBinLeafTree    :: NonEmpty a -> BinLeafTree Size (Elem a)+asBalancedBinLeafTree ys = asBLT (length ys') ys'+  where+    ys' = toList ys++    asBLT _ [x] = Leaf (Elem x)+    asBLT n xs  = let h       = n `div` 2+                      (ls,rs) = splitAt h xs+                  in node (asBLT h ls) (asBLT (n-h) rs)++newtype Size = Size Int deriving (Show,Read,Eq,Num,Integral,Enum,Real,Ord)++instance Semigroup Size where+  x <> y = x + y++newtype Elem a = Elem { _unElem :: a }+               deriving (Show,Read,Eq,Ord,Functor,Foldable,Traversable)++instance Measured Size (Elem a) where+  measure _ = 1
+ src/Data/CircularList/Util.hs view
@@ -0,0 +1,82 @@+{-# LANGUAGE CPP #-}+module Data.CircularList.Util where++import           Control.Lens+import           Data.Tuple+import qualified Data.CircularList as C+import qualified Data.List as L+import qualified Data.Traversable as T++import Debug.Trace++-- $setup+-- >>> let ordList = C.fromList [5,6,10,20,30,1,2,3]++++-- | Given a circular list, whose elements are in increasing order, insert the+-- new element into the Circular list in its sorted order.+--+-- >>> insertOrd 1 C.empty+-- fromList [1]+-- >>> insertOrd 1 $ C.fromList [2]+-- fromList [2,1]+-- >>> insertOrd 2 $ C.fromList [1,3]+-- fromList [1,2,3]+-- >>> insertOrd 31 ordList+-- fromList [5,6,10,20,30,31,1,2,3]+-- >>> insertOrd 1 ordList+-- fromList [5,6,10,20,30,1,1,2,3]+-- >>> insertOrd 4 ordList+-- fromList [5,6,10,20,30,1,2,3,4]+-- >>> insertOrd 11 ordList+-- fromList [5,6,10,11,20,30,1,2,3]+insertOrd :: Ord a => a -> C.CList a -> C.CList a+insertOrd = insertOrdBy compare++-- | Insert an element into an increasingly ordered circular list, with+-- specified compare operator.+insertOrdBy       :: (a -> a -> Ordering) -> a -> C.CList a -> C.CList a+insertOrdBy cmp x = C.fromList . insertOrdBy' cmp x . C.rightElements++-- | List version of insertOrdBy; i.e. the list contains the elements in+-- cirulcar order. Again produces a list that has the items in circular order.+insertOrdBy'         :: (a -> a -> Ordering) -> a -> [a] -> [a]+insertOrdBy' cmp x xs = case (rest, x `cmp` head rest) of+    ([],  _)   -> L.insertBy cmp x pref+    (z:zs, GT) -> (z : L.insertBy cmp x zs) ++ pref+    (_:_,  EQ) -> (x : xs) -- == x : rest ++ pref+    (_:_,  LT) -> rest ++ L.insertBy cmp x pref+  where+    -- split the list at its maximum.+    (pref,rest) = splitIncr cmp xs++-- given a list of elements that is supposedly a a cyclic-shift of a list of+-- increasing items, find the splitting point. I.e. returns a pair of lists+-- (ys,zs) such that xs = zs ++ ys, and ys ++ zs is (supposedly) in sorted+-- order.+splitIncr              :: (a -> a -> Ordering) -> [a] -> ([a],[a])+splitIncr _   []       = ([],[])+splitIncr cmp xs@(x:_) = swap . bimap (map snd) (map snd)+                      . L.break (\(a,b) -> (a `cmp` b) == GT) $ zip (x:xs) xs++-- | Test if the circular list is a cyclic shift of the second list.+-- Running time: O(n), where n is the size of the smallest list+isShiftOf         :: Eq a => C.CList a -> C.CList a -> Bool+xs `isShiftOf` ys = let rest = tail . C.leftElements+                    in maybe False (\xs' -> rest xs' == rest ys) $+                         C.focus ys >>= flip C.rotateTo xs+++-- minimumBy     :: (a -> a -> Ordering) -> C.CList a -> a+-- minimumBy cmp = L.minimumBy cmp . C.rightElements++#if !MIN_VERSION_data_clist(0,1,0)++instance Foldable C.CList where+  foldMap = T.foldMapDefault++instance T.Traversable C.CList where+  traverse f = fmap C.fromList . T.traverse f . C.rightElements++#endif
+ src/Data/CircularSeq.hs view
@@ -0,0 +1,281 @@+module Data.CircularSeq( CSeq+                       , cseq+                       , singleton+                       , fromNonEmpty+                       , fromList++                       , focus+                       , index, adjust+                       , item++                       , rotateL+                       , rotateR+                       , rotateNL, rotateNR++                       , rightElements+                       , leftElements+                       , asSeq++                       , reverseDirection+                       , allRotations++                       , findRotateTo+                       , rotateTo+                       ) where++import           Control.Applicative+import           Control.Lens(lens, Lens')+import qualified Data.Foldable as F+import qualified Data.List.NonEmpty as NonEmpty+import           Data.Maybe (listToMaybe)+import           Data.Semigroup+import           Data.Semigroup.Foldable+import           Data.Sequence ((|>),(<|),ViewL(..),ViewR(..),Seq)+import qualified Data.Sequence as S+import qualified Data.Traversable as T+import           Data.Tuple (swap)++--------------------------------------------------------------------------------++-- | Nonempty circular sequence+data CSeq a = CSeq !(Seq a) !a !(Seq a)+            deriving (Eq)+                     -- we keep the seq balanced, i.e. size left >= size right++instance Show a => Show (CSeq a) where+  showsPrec d s = showParen (d > app_prec) $+                    showString (("CSeq " <>) . show . F.toList . rightElements $ s)+    where app_prec = 10++-- traverses starting at the focus, going to the right.+instance T.Traversable CSeq where+  traverse f (CSeq l x r) = (\x' r' l' -> CSeq l' x' r')+                         <$> f x <*> traverse f r <*> traverse f l+-- instance Traversable1 CSeq where+--   traverse1 f (CSeq l x r) = liftF3 (\x' r' l' -> CSeq l' x' r')+--                                     (f x) (traverse f r) (traverse f l)++instance Foldable1 CSeq+++instance F.Foldable CSeq where+  foldMap = T.foldMapDefault+  length (CSeq l _ r) = 1 + S.length l + S.length r+++++instance Functor CSeq where+  fmap = T.fmapDefault+++singleton   :: a -> CSeq a+singleton x = CSeq S.empty x S.empty++-- | Gets the focus of the CSeq+-- running time: O(1)+focus              :: CSeq a -> a+focus (CSeq _ x _) = x++-- | Access the i^th item  (w.r.t the focus) in the CSeq (indices modulo n).+--+-- running time: $O(\log (i \mod n))$+--+-- >>> index (fromList [0..5]) 1+-- 1+-- >>> index (fromList [0..5]) 2+-- 2+-- >>> index (fromList [0..5]) 5+-- 5+-- >>> index (fromList [0..5]) 10+-- 4+-- >>> index (fromList [0..5]) 6+-- 0+-- >>> index (fromList [0..5]) (-1)+-- 5+-- >>> index (fromList [0..5]) (-6)+-- 0+index                   :: CSeq a -> Int -> a+index s@(CSeq l x r) i' = let i  = i' `mod` length s+                              rn = length r+                          in if i == 0 then x+                               else if i - 1 < rn then S.index r (i - 1)+                                                  else S.index l (i - rn - 1)++-- | Adjusts the i^th element w.r.t the focus in the CSeq+--+-- running time: $O(\log (i \mod n))$+--+-- >>> adjust (const 1000) 2 (fromList [0..5])+-- CSeq [0,1,1000,3,4,5]+adjust                     :: (a -> a) -> Int -> CSeq a -> CSeq a+adjust f i' s@(CSeq l x r) = let i  = i' `mod` length s+                                 rn = length r+                             in if i == 0 then CSeq l (f x) r+                                else if i - 1 < rn+                                     then CSeq l                           x (S.adjust f (i - 1) r)+                                     else CSeq (S.adjust f (i - rn - 1) l) x r+++-- | Access te ith item in the CSeq (w.r.t the focus) as a lens+item   :: Int -> Lens' (CSeq a) a+item i = lens (flip index i) (\s x -> adjust (const x) i s)+++resplit   :: Seq a -> (Seq a, Seq a)+resplit s = swap $ S.splitAt (length s `div` 2) s+++-- | smart constructor that automatically balances the seq+cseq                   :: Seq a -> a -> Seq a -> CSeq a+cseq l x r+    | ln > 1 + 2*rn    = withFocus x (r <> l)+    | ln < rn `div`  2 = withFocus x (r <> l)+    | otherwise        = CSeq l x r+  where+    rn = length r+    ln = length l++-- | Builds a balanced seq with the element as the focus.+withFocus     :: a -> Seq a -> CSeq a+withFocus x s = let (l,r) = resplit s in CSeq l x r++-- | rotates one to the right+--+-- running time: O(1) (amortized)+--+-- >>> rotateR $ fromList [3,4,5,1,2]+-- CSeq [4,5,1,2,3]+rotateR                :: CSeq a -> CSeq a+rotateR s@(CSeq l x r) = case S.viewl r of+                           EmptyL    -> case S.viewl l of+                             EmptyL    -> s+                             (y :< l') -> cseq (S.singleton x) y l'+                           (y :< r') -> cseq (l |> x) y r'++-- | rotates the focus to the left+--+-- running time: O(1) (amortized)+--+-- >>> rotateL $ fromList [3,4,5,1,2]+-- CSeq [2,3,4,5,1]+-- >>> mapM_ print . take 5 $ iterate rotateL $ fromList [1..5]+-- CSeq [1,2,3,4,5]+-- CSeq [5,1,2,3,4]+-- CSeq [4,5,1,2,3]+-- CSeq [3,4,5,1,2]+-- CSeq [2,3,4,5,1]+rotateL                :: CSeq a -> CSeq a+rotateL s@(CSeq l x r) = case S.viewr l of+                           EmptyR    -> case S.viewr r of+                             EmptyR     -> s+                             (r' :> y)  -> cseq r' y (S.singleton x)+                           (l' :> y) -> cseq l' y (x <| r)+++-- | Convert to a single Seq, starting with the focus.+asSeq :: CSeq a -> Seq a+asSeq = rightElements+++-- | All elements, starting with the focus, going to the right++-- >>> rightElements $ fromList [3,4,5,1,2]+-- fromList [3,4,5,1,2]+rightElements              :: CSeq a -> Seq a+rightElements (CSeq l x r) = x <| r <> l+++-- | All elements, starting with the focus, going to the left+--+-- >>> leftElements $ fromList [3,4,5,1,2]+-- fromList [3,2,1,5,4]+leftElements              :: CSeq a -> Seq a+leftElements (CSeq l x r) = x <| S.reverse l <> S.reverse r++-- | builds a CSeq+fromNonEmpty                    :: NonEmpty.NonEmpty a -> CSeq a+fromNonEmpty (x NonEmpty.:| xs) = withFocus x $ S.fromList xs++fromList        :: [a] -> CSeq a+fromList (x:xs) = withFocus x $ S.fromList xs+fromList []     = error "fromList: Empty list"++-- | Rotates i elements to the right.+--+-- pre: 0 <= i < n+--+-- running time: $O(\log i)$ amortized+--+-- >>> rotateNR 0 $ fromList [1..5]+-- CSeq [1,2,3,4,5]+-- >>> rotateNR 1 $ fromList [1..5]+-- CSeq [2,3,4,5,1]+-- >>> rotateNR 4 $ fromList [1..5]+-- CSeq [5,1,2,3,4]+rotateNR     :: Int -> CSeq a -> CSeq a+rotateNR i s = let (l, r')  = S.splitAt i $ rightElements s+                   (x :< r) = S.viewl r'+               in cseq l x r+++-- | Rotates i elements to the left.+--+-- pre: 0 <= i < n+--+-- running time: $O(\log i)$ amoritzed+--+-- >>> rotateNL 0 $ fromList [1..5]+-- CSeq [1,2,3,4,5]+-- >>> rotateNL 1 $ fromList [1..5]+-- CSeq [5,1,2,3,4]+-- >>> rotateNL 2 $ fromList [1..5]+-- CSeq [4,5,1,2,3]+-- >>> rotateNL 3 $ fromList [1..5]+-- CSeq [3,4,5,1,2]+-- >>> rotateNL 4 $ fromList [1..5]+-- CSeq [2,3,4,5,1]+rotateNL     :: Int -> CSeq a -> CSeq a+rotateNL i s = let (x :< xs) = S.viewl $ rightElements s+                   (l',r)    = S.splitAt (length s - i) $ xs |> x+                   (l :> y)  = S.viewr l'+               in cseq l y r+++-- | Reversres the direction of the CSeq+--+-- running time: $O(n)$+--+-- >>> reverseDirection $ fromList [1..5]+-- CSeq [1,5,4,3,2]+reverseDirection              :: CSeq a -> CSeq a+reverseDirection (CSeq l x r) = CSeq (S.reverse r) x (S.reverse l)+++-- | Finds an element in the CSeq+--+-- >>> findRotateTo (== 3) $ fromList [1..5]+-- Just (CSeq [3,4,5,1,2])+-- >>> findRotateTo (== 7) $ fromList [1..5]+-- Nothing+findRotateTo   :: (a -> Bool) -> CSeq a -> Maybe (CSeq a)+findRotateTo p = listToMaybe . filter (p . focus) . allRotations'+++rotateTo   :: Eq a => a -> CSeq a -> Maybe (CSeq a)+rotateTo x = findRotateTo (== x)+++-- | All rotations, the input CSeq is the focus.+--+-- >>> mapM_ print . allRotations $ fromList [1..5]+-- CSeq [1,2,3,4,5]+-- CSeq [2,3,4,5,1]+-- CSeq [3,4,5,1,2]+-- CSeq [4,5,1,2,3]+-- CSeq [5,1,2,3,4]+allRotations :: CSeq a -> CSeq (CSeq a)+allRotations = fromList . allRotations'++allRotations'   :: CSeq a -> [CSeq a]+allRotations' s = take (length s) . iterate rotateR $ s
src/Data/Ext.hs view
@@ -3,26 +3,45 @@ import Control.Applicative import Control.Lens import Data.Semigroup+import Data.Biapplicative+import Data.Bifunctor.Apply+import Data.Bifoldable+import Data.Bitraversable+import Data.Semigroup.Bifoldable+import Data.Semigroup.Bitraversable+import Data.Functor.Apply(liftF2)  -------------------------------------------------------------------------------- -data Ext extra core = core :+ extra deriving (Show,Read,Eq,Ord)+data core :+ extra = core :+ extra deriving (Show,Read,Eq,Ord,Bounded)+infixr 1 :+ -instance Functor (Ext e) where-  fmap f (c :+ e) = f c :+ e -instance Monoid e => Applicative (Ext e) where-  pure x = x :+ mempty-  -- | This implementation ignores any extra values f may have-  (f :+ _) <*> (c :+ce) = f c :+ ce+instance Bifunctor (:+) where+  bimap f g (c :+ e) = f c :+ g e -instance (Semigroup core, Semigroup extra) => Semigroup (Ext extra core) where-  (c :+ e) <> (c' :+ e') = c <> c' :+ e <> e'+instance Biapply (:+) where+  (f :+ g) <<.>> (c :+ e) = f c :+ g e +instance Biapplicative (:+) where+  bipure = (:+)+  (f :+ g) <<*>> (c :+ e) = f c :+ g e -type core :+ extra = Ext extra core-infixr 1 :++instance Bifoldable (:+) where+  bifoldMap f g (c :+ e) = f c `mappend` g e +instance Bitraversable (:+) where+  bitraverse f g (c :+ e) = (:+) <$> f c <*> g e++instance Bifoldable1 (:+)++instance Bitraversable1 (:+) where+  bitraverse1 f g (c :+ e) = liftF2 (:+) (f c) (g e)++instance (Semigroup core, Semigroup extra) => Semigroup (core :+ extra) where+  (c :+ e) <> (c' :+ e') = c <> c' :+ e <> e'++ _core :: (core :+ extra) -> core _core (c :+ _) = c @@ -35,6 +54,5 @@ extra :: Lens (core :+ extra) (core :+ extra') extra extra' extra = lens _extra (\(c :+ _) e -> (c :+ e)) --only   :: a -> a :+ ()-only x = x :+ ()+ext   :: a -> a :+ ()+ext x = x :+ ()
src/Data/Geometry.hs view
@@ -1,18 +1,29 @@-module Data.Geometry( module Prop-                    , module T-                    , module P-                    , module V-                    , module L+{-|+Module    : Data.Geometry+Description: Basic Geometry types+Copyright : (c) Frank Staals+License : See LICENCE file+-}+module Data.Geometry( module Data.Geometry.Properties+                    , module Data.Geometry.Transformation+                    , module Data.Geometry.Point+                    , module Data.Geometry.Vector+                    , module Data.Geometry.Line+                    , module Data.Geometry.LineSegment+                    , module Data.Geometry.PolyLine+                    , module Data.Geometry.Polygon                     , module Linear.Affine+                    , module Linear.Vector                     ) where  -import Data.Geometry.Properties as Prop-import Data.Geometry.Transformation as T--import Data.Geometry.Point as P-import Data.Geometry.Vector as V-import Data.Geometry.Line as L-+import Data.Geometry.Line+import Data.Geometry.LineSegment+import Data.Geometry.Point+import Data.Geometry.PolyLine hiding (fromPoints)+import Data.Geometry.Polygon hiding (fromPoints)+import Data.Geometry.Properties+import Data.Geometry.Transformation+import Data.Geometry.Vector import Linear.Affine hiding (Point, origin)-import Linear.Vector as V+import Linear.Vector
src/Data/Geometry/Ball.hs view
@@ -1,24 +1,29 @@ {-# LANGUAGE TemplateHaskell  #-}-{-# LANGUAGE DeriveFunctor  #-}-{-# LANGUAGE MultiParamTypeClasses  #-} {-# LANGUAGE UndecidableInstances #-} module Data.Geometry.Ball where -import Data.Ext-import Control.Lens hiding (only)+import           Control.Lens+import           Data.Bifunctor+import           Data.Ext+import qualified Data.Foldable as F+import           Data.Geometry.Boundary+import           Data.Geometry.Line+import           Data.Geometry.LineSegment+import           Data.Geometry.Point+import           Data.Geometry.Properties+import           Data.Geometry.Vector import qualified Data.List as L-import Data.Geometry.Line-import Data.Geometry.LineSegment-import Data.Geometry.Point-import Data.Geometry.Properties-import Data.Geometry.Vector-import GHC.TypeLits-import Linear.Affine(qdA, (.-.), (.+^))-import Linear.Vector((^/),(*^),(^+^))+import qualified Data.Traversable as T+import           Data.Vinyl+import           Frames.CoRec+import           GHC.TypeLits+import           Linear.Affine(qdA, (.-.), (.+^))+import           Linear.Vector((^/),(*^),(^+^))  -------------------------------------------------------------------------------- -- * A d-dimensional ball +-- | A d-dimensional ball. data Ball d p r = Ball { _center        :: Point d r :+ p                        , _squaredRadius :: r                        }@@ -26,16 +31,22 @@  deriving instance (Show r, Show p, Arity d) => Show (Ball d p r) deriving instance (Eq r, Eq p, Arity d)     => Eq (Ball d p r)-deriving instance Arity d                   => Functor (Ball d p)  type instance NumType   (Ball d p r) = r type instance Dimension (Ball d p r) = d +instance Arity d => Functor (Ball d p) where+  fmap f (Ball c r) = Ball (first (fmap f) c) (f r)++instance Arity d => Bifunctor (Ball d) where+  bimap f g (Ball c r) = Ball (bimap (fmap g) f c) (g r)++ -- * Constructing Balls  -- | Given two points on the diameter of the ball, construct a ball. fromDiameter     :: (Arity d, Fractional r) => Point d r -> Point d r -> Ball d () r-fromDiameter p q = let c = p .+^ ((q .-. p) ^/ 2) in Ball (only c) (qdA c p)+fromDiameter p q = let c = p .+^ ((q .-. p) ^/ 2) in Ball (ext c) (qdA c p)  -- | Construct a ball given the center point and a point p on the boundary. fromCenterAndPoint     :: (Arity d, Num r) => Point d r :+ p -> Point d r :+ p -> Ball d p r@@ -43,18 +54,15 @@  -- | A d dimensional unit ball centered at the origin. unitBall :: (Arity d, Num r) => Ball d () r-unitBall = Ball (only origin) 1+unitBall = Ball (ext origin) 1  -- * Querying if a point lies in a ball --- | Result of a inBall query-data PointBallQueryResult = Inside | On | Outside deriving (Show,Read,Eq)- inBall                 :: (Arity d, Ord r, Num r)-                       => Point d r -> Ball d p r -> PointBallQueryResult+                       => Point d r -> Ball d p r -> PointLocationResult p `inBall` (Ball c sr) = case qdA p (c^.core) `compare` sr of                            LT -> Inside-                           EQ -> On+                           EQ -> OnBoundary                            GT -> Outside  -- | Test if a point lies strictly inside a ball@@ -85,26 +93,45 @@ -- False onBall       :: (Arity d, Ord r, Num r)              => Point d r -> Ball d p r -> Bool-p `onBall` b = p `inBall` b == On+p `onBall` b = p `inBall` b == OnBoundary   ----------------------------------------------------------------------------------- * Circles, aka 2-dimensional Balls+-- | Spheres, i.e. the boundary of a ball. -type Circle = Ball 2+type Sphere d p r = Boundary (Ball d p r) --- | Given three points, get the circle through the three points. If the three+pattern Sphere c r = Boundary (Ball c r)+++++--------------------------------------------------------------------------------+-- * Disks and Circles, aka 2-dimensional Balls and Spheres++type Disk p r = Ball 2 p r++pattern Disk c r = Ball c r+++type Circle p r = Sphere 2 p r++pattern Circle c r = Sphere c r++-- | Given three points, get the disk through the three points. If the three -- input points are colinear we return Nothing ----- >>> circle (point2 0 10) (point2 10 0) (point2 (-10) 0)--- Just (Ball {_center = Point {toVec = Vector {_unV = fromList [0.0,0.0]}} :+ (), _squaredRadius = 100.0})-circle       :: (Eq r, Fractional r)-             => Point 2 r -> Point 2 r -> Point 2 r -> Maybe (Circle () r)-circle p q r = case f p `intersect` f q of-                 LineLineIntersection c -> Just $ Ball (only c) (qdA c p)-                 _                      -> Nothing -- The two lines f p and f q are-                                                   -- parallel, that means the three-                                                   -- input points where colinear.+-- >>> disk (point2 0 10) (point2 10 0) (point2 (-10) 0)+-- Just (Ball {_center = Point2 [0.0,0.0] :+ (), _squaredRadius = 100.0})+disk       :: (Eq r, Fractional r)+           => Point 2 r -> Point 2 r -> Point 2 r -> Maybe (Disk () r)+disk p q r = match ((f p) `intersect` (f q)) $+       (H $ \NoIntersection -> Nothing)+    :& (H $ \c@(Point _)    -> Just $ Ball (ext c) (qdA c p))+    :& (H $ \_              -> Nothing)+    :& RNil+       -- If the intersection is not a point, The two lines f p and f q are+       -- parallel, that means the three input points where colinear.   where     -- Given a point p', get the line perpendicular, and through the midpoint     -- of the line segment p'r@@ -113,21 +140,24 @@            in perpendicularTo (Line midPoint v)  -instance (Ord r, Floating r) => (Line 2 r) `IsIntersectableWith` (Circle p r) where+newtype Touching p = Touching p deriving (Show,Eq,Ord,Functor,F.Foldable,T.Traversable) -  data Intersection (Line 2 r) (Circle p r) = NoLineCircleIntersection-                                            | LineTouchesCircle        (Point 2 r)-                                            | LineCircleIntersection   (Point 2 r) (Point 2 r)-                                              deriving (Show,Eq)+-- | No intersection, one touching point, or two points+type instance IntersectionOf (Line 2 r) (Circle p r) = [ NoIntersection+                                                       , Touching (Point 2 r)+                                                       , (Point 2 r, Point 2 r)+                                                       ] -  nonEmptyIntersection NoLineCircleIntersection = False-  nonEmptyIntersection _                        = True -  (Line p' v) `intersect` (Ball (c :+ _) r) = case discr `compare` 0 of-                                                LT -> NoLineCircleIntersection-                                                EQ -> LineTouchesCircle $ q' (lambda (+))+instance (Ord r, Floating r) => (Line 2 r) `IsIntersectableWith` (Circle p r) where++  nonEmptyIntersection = defaultNonEmptyIntersection++  (Line p' v) `intersect` (Circle (c :+ _) r) = case discr `compare` 0 of+                                                LT -> coRec $ NoIntersection+                                                EQ -> coRec . Touching $ q' (lambda (+))                                                 GT -> let [l1,l2] = L.sort [lambda (-), lambda (+)]-                                                      in LineCircleIntersection (q' l1) (q' l2)+                                                      in coRec $ (q' l1, q' l2)     where       (Vector2 vx vy)   = v       -- (px, py) is the vector/point after translating the circle s.t. it is centered at the@@ -152,23 +182,28 @@       lambda (|+-|)        = (-bb |+-| discr') / (2*aa)  -instance (Ord r, Floating r) => (LineSegment 2 p r) `IsIntersectableWith` (Circle q r) where+-- | A line segment may not intersect a circle, touch it, or intersect it+-- properly in one or two points.+type instance IntersectionOf (LineSegment 2 p r) (Circle q r) = [ NoIntersection+                                                                , Touching (Point 2 r)+                                                                , Point 2 r+                                                                , (Point 2 r, Point 2 r)+                                                                ] -  data Intersection (LineSegment 2 p r) (Circle q r) = NoLineSegmentCircleIntersection-                                                     | LineSegmentTouchesCircle    (Point 2 r)-                                                     | LineSegmentIntersectsCircle (Point 2 r)-                                                     | LineSegmentCrossesCircle    (Point 2 r) (Point 2 r)-                                                     deriving (Show,Eq) -  nonEmptyIntersection NoLineSegmentCircleIntersection = False-  nonEmptyIntersection _                               = True+instance (Ord r, Floating r) => (LineSegment 2 p r) `IsIntersectableWith` (Circle q r) where -  s `intersect` c = case supportingLine s `intersect` c of-    NoLineCircleIntersection    -> NoLineSegmentCircleIntersection-    LineTouchesCircle p         -> if p `onSegment` s then LineSegmentTouchesCircle p-                                                      else NoLineSegmentCircleIntersection-    LineCircleIntersection p q -> case (p `onSegment` s, q `onSegment` s) of-                                    (False,False) -> NoLineSegmentCircleIntersection-                                    (False,True)  -> LineSegmentIntersectsCircle q-                                    (True, False) -> LineSegmentIntersectsCircle p-                                    (True, True)  -> LineSegmentCrossesCircle p q+  nonEmptyIntersection = defaultNonEmptyIntersection++  s `intersect` c = match (supportingLine s `intersect` c) $+       (H $ \NoIntersection -> coRec NoIntersection)+    :& (H $ \(Touching p)   -> if p `onSegment` s then coRec $ Touching p+                                                 else  coRec   NoIntersection+       )+    :& (H $ \(p,q)          -> case (p `onSegment` s, q `onSegment` s) of+                                 (False,False) -> coRec NoIntersection+                                 (False,True)  -> coRec q+                                 (True, False) -> coRec p+                                 (True, True)  -> coRec (p,q)+       )+    :& RNil
+ src/Data/Geometry/Boundary.hs view
@@ -0,0 +1,22 @@+module Data.Geometry.Boundary where+++import Data.Geometry.Properties+import qualified Data.Foldable as F+import qualified Data.Traversable as T+import           Data.Geometry.Transformation++--------------------------------------------------------------------------------++-- | The boundary of a geometric object.+newtype Boundary g = Boundary g+                   deriving (Show,Eq,Ord,Read,IsTransformable)+++type instance NumType (Boundary g)   = NumType g+type instance Dimension (Boundary g) = Dimension g+++-- | Result of a query that asks if something is Inside a g, *on* the boundary+-- of the g, or outside.+data PointLocationResult = Inside | OnBoundary | Outside deriving (Show,Read,Eq)
src/Data/Geometry/Box.hs view
@@ -2,183 +2,55 @@ {-# LANGUAGE MultiParamTypeClasses  #-} {-# LANGUAGE ScopedTypeVariables  #-} {-# LANGUAGE DeriveFunctor  #-}-module Data.Geometry.Box(-                        -- * d-dimensional boxes-                          Box(..)--                        -- * Constructing bounding boxes-                        , IsBoxable(..)-                        , IsAlwaysTrueBoundingBox-                        , boundingBoxList--                        -- * Functions on d-dimensonal boxes-                        , minPoint, maxPoint-                        , extent, size, widthIn-                        , inBox--                        -- * Rectangles, aka 2-dimensional boxes-                        , Rectangle-                        , width , height+module Data.Geometry.Box( module Data.Geometry.Box.Internal+                        , topSide, leftSide, bottomSide, rightSide+                        , sides, sides'                         ) where -import           Control.Applicative-import qualified Data.Foldable as F-import           Data.Maybe(catMaybes, maybe)-import           Data.Semigroup -import           Control.Lens(Getter,to,(^.),view)-import           Data.Ext-import           Data.Geometry.Point-import qualified Data.Geometry.Interval as I-import           Data.Geometry.Properties-import           Data.Geometry.Transformation-import qualified Data.Geometry.Vector as V-import           Data.Geometry.Vector(Vector, Arity, Index',C(..))-import           Linear.Affine((.-.))--import qualified Data.Vector.Fixed                as FV+import           Control.Applicative+import qualified Data.Traversable as Tr+import           Data.Geometry.Box.Internal+import           Data.Geometry.LineSegment -import           GHC.TypeLits -------------------------------------------------------------------------------- -data Box d p r = Empty-               | Box { _minP :: Min (Point d r) :+ p-                     , _maxP :: Max (Point d r) :+ p-                     }--deriving instance (Show r, Show p, Arity d) => Show (Box d p r)-deriving instance (Eq r, Eq p, Arity d)     => Eq   (Box d p r)-deriving instance (Ord r, Ord p, Arity d)   => Ord  (Box d p r)--instance (Arity d, Ord r, Semigroup p) => Semigroup (Box d p r) where-  Empty       <> b             = b-  b           <> Empty         = b-  (Box mi ma) <> (Box mi' ma') = Box (mi <> mi') (ma <> ma')---instance (Arity d, Ord r, Semigroup p) => Monoid (Box d p r) where-  mempty = Empty-  b `mappend` b' = b <> b'---instance (Arity d, Ord r) => (Box d p r) `IsIntersectableWith` (Box d p r) where-  data Intersection (Box d p r) (Box d p r) = BoxBoxIntersection !(Box d () r)--  nonEmptyIntersection (BoxBoxIntersection Empty) = True-  nonEmptyIntersection _                          = False--  (Box a b) `intersect` (Box c d) = BoxBoxIntersection $ Box (mi :+ ()) (ma :+ ())-    where-      mi = (a^.core) `max` (c^.core)-      ma = (b^.core) `min` (d^.core)--deriving instance (Show r, Show p, Arity d) => Show (Intersection (Box d p r) (Box d p r))-deriving instance (Eq r, Eq p, Arity d)     => Eq   (Intersection (Box d p r) (Box d p r))-deriving instance (Ord r, Ord p, Arity d)   => Ord  (Intersection (Box d p r) (Box d p r))------- Note that this does not guarantee the box is still a proper box-instance PointFunctor (Box d p) where-  pmap f (Box mi ma) = Box (fmap f <$> mi) (fmap f <$> ma)--instance (Num r, AlwaysTruePFT d) => IsTransformable (Box d p r) where-  transformBy = transformPointFunctor---type instance Dimension (Box d p r) = d-type instance NumType   (Box d p r) = r--to'     :: (m -> Point d r) -> (Box d p r -> m :+ p) ->-           Getter (Box d p r) (Maybe (Point d r :+ p))-to' f g = to $ \x -> case x of-                  Empty -> Nothing-                  b     -> Just . fmap f . g $ b--minPoint :: Getter (Box d p r) (Maybe (Point d r :+ p))-minPoint = to' getMin _minP+topSide :: Num r => Rectangle p r -> LineSegment 2 p r+topSide = (\(l,r,_,_) -> ClosedLineSegment l r) . corners -maxPoint :: Getter (Box d p r) (Maybe (Point d r :+ p))-maxPoint = to' getMax _maxP+-- | Oriented from *left to right*+bottomSide :: Num r => Rectangle p r -> LineSegment 2 p r+bottomSide = (\(_,_,r,l) -> ClosedLineSegment l r) . corners --- | Check if a point lies a box ----- >>> origin `inBox` (boundingBoxList [point3 1 2 3, point3 10 20 30] :: Box 3 () Int)--- False--- >>> origin `inBox` (boundingBoxList [point3 (-1) (-2) (-3), point3 10 20 30] :: Box 3 () Int)--- True-inBox :: (Arity d, Ord r) => Point d r -> Box d p r -> Bool-p `inBox` b = maybe False f $ extent b-  where-    f = FV.and . FV.zipWith I.inInterval (toVec p)------ | Get a vector with the extent of the box in each dimension. Note that the--- resulting vector is 0 indexed whereas one would normally count dimensions--- starting at zero.------ >>> extent (boundingBoxList [point3 1 2 3, point3 10 20 30] :: Box 3 () Int)--- Just (Vector {_unV = fromList [Interval {_start = 1 :+ (), _end = 10 :+ ()},Interval {_start = 2 :+ (), _end = 20 :+ ()},Interval {_start = 3 :+ (), _end = 30 :+ ()}]})-extent                                 :: (Arity d)-                                       => Box d p r -> Maybe (Vector d (I.Interval p r))-extent Empty                           = Nothing-extent (Box (Min a :+ p) (Max b :+ q)) = Just $ FV.zipWith f (toVec a) (toVec b)-  where-    f x y = I.Interval (x :+ p) (y :+ q)---- | Get the size of the box (in all dimensions). Note that the resulting vector is 0 indexed--- whereas one would normally count dimensions starting at zero.------ >>> size (boundingBoxList [origin, point3 1 2 3] :: Box 3 () Int)--- Vector {_unV = fromList [1,2,3]}-size :: (Arity d, Num r) => Box d p r -> Vector d r-size = maybe (pure 0) (fmap I.width) . extent---- | Given a dimension, get the width of the box in that dimension. Dimensions are 1 indexed.------ >>> widthIn (C :: C 1) (boundingBoxList [origin, point3 1 2 3] :: Box 3 () Int)--- 1--- >>> widthIn (C :: C 3) (boundingBoxList [origin, point3 1 2 3] :: Box 3 () Int)--- 3-widthIn   :: forall proxy p i d r. (Arity d, Num r, Index' (i-1) d) => proxy i -> Box d p r -> r-widthIn _ = view (V.element (C :: C (i - 1))) . size--------------------------------------------type Rectangle = Box 2---- >>> width (boundingBoxList [origin, point2 1 2] :: Rectangle () Int)--- 1--- >>> width (boundingBoxList [origin] :: Rectangle () Int)--- 0-width :: Num r => Rectangle p r -> r-width = widthIn (C :: C 1)+leftSide  :: Num r => Rectangle p r -> LineSegment 2 p r+leftSide = (\(t,_,_,b) -> ClosedLineSegment b t) . corners --- >>> height (boundingBoxList [origin, point2 1 2] :: Rectangle () Int)--- 2--- >>> height (boundingBoxList [origin] :: Rectangle () Int)--- 0-height :: Num r => Rectangle p r -> r-height = widthIn (C :: C 2)+-- | The right side, oriented from *bottom* to top+rightSide :: Num r => Rectangle p r -> LineSegment 2 p r+rightSide = (\(_,t,b,_) -> ClosedLineSegment b t) . corners  -----------------------------------------------------------------------------------class IsBoxable g where-  boundingBox :: (Monoid p, Semigroup p, Ord (NumType g))-              => g -> Box (Dimension g) p (NumType g)--type IsAlwaysTrueBoundingBox g p = (Semigroup p, Arity (Dimension g))+-- | The sides of the rectangle, in order (Top, Right, Bottom, Left). The sides+-- themselves are also oriented in clockwise order. If, you want them in the+-- same order as the functions `topSide`, `bottomSide`, `leftSide`, and+-- `rightSide`, use `sides'` instead.+sides :: Num r => Rectangle p r -> ( LineSegment 2 p r+                                   , LineSegment 2 p r+                                   , LineSegment 2 p r+                                   , LineSegment 2 p r+                                   )+sides = (\(t,r,b,l) -> (t,flipSegment r,flipSegment b,l)) . sides'  -boundingBoxList :: (IsBoxable g, Monoid p, F.Foldable c, Ord (NumType g)-                   , IsAlwaysTrueBoundingBox g p-                   ) => c g -> Box (Dimension g) p (NumType g)-boundingBoxList = F.foldMap boundingBox--------------------------------------------instance IsBoxable (Point d r) where-  boundingBox p = Box (Min p :+ mempty) (Max p :+ mempty)+-- | The sides of the rectangle. The order of the segments is (Top, Right,+-- Bottom, Left).  Note that the segments themselves, are oriented as described+-- by the functions topSide, bottomSide, leftSide, rightSide (basically: from+-- left to right, and from bottom to top). If you want the segments oriented+-- along the boundary of the rectangle, use the `sides` function instead.+sides'   :: Num r => Rectangle p r -> ( LineSegment 2 p r+                                      , LineSegment 2 p r+                                      , LineSegment 2 p r+                                      , LineSegment 2 p r+                                      )+sides' r = (topSide r, rightSide r, bottomSide r, leftSide r)
+ src/Data/Geometry/Box/Internal.hs view
@@ -0,0 +1,203 @@+{-# LANGUAGE TemplateHaskell  #-}+{-# LANGUAGE MultiParamTypeClasses  #-}+{-# LANGUAGE ScopedTypeVariables  #-}+{-# LANGUAGE DeriveFunctor  #-}+module Data.Geometry.Box.Internal where++import           Control.Applicative+import           Control.Lens+import           Data.Bifunctor+import           Data.Ext+import qualified Data.Semigroup.Foldable as F+import qualified Data.Range as R+import           Data.Geometry.Point+import           Data.Geometry.Properties+import           Data.Geometry.Transformation+import qualified Data.Geometry.Vector as V+import qualified Data.List.NonEmpty as NE+import           Data.Geometry.Vector(Vector, Arity, Index',C(..))+import           Data.Semigroup++import qualified Data.Vector.Fixed                as FV++import           GHC.TypeLits++--------------------------------------------------------------------------------+-- * d-dimensional boxes++data Box d p r = Box { _minP :: Min (Point d r) :+ p+                     , _maxP :: Max (Point d r) :+ p+                     }+makeLenses ''Box++-- | Given the point with the lowest coordinates and the point with highest+-- coordinates, create a box.+fromCornerPoints          :: Point d r :+ p -> Point d r :+ p -> Box d p r+fromCornerPoints low high = Box (low&core %~ Min) (high&core %~ Max)+++deriving instance (Show r, Show p, Arity d) => Show (Box d p r)+deriving instance (Eq r, Eq p, Arity d)     => Eq   (Box d p r)+deriving instance (Ord r, Ord p, Arity d)   => Ord  (Box d p r)++instance (Arity d, Ord r, Semigroup p) => Semigroup (Box d p r) where+  (Box mi ma) <> (Box mi' ma') = Box (mi <> mi') (ma <> ma')++type instance IntersectionOf (Box d p r) (Box d q r) = '[ NoIntersection, Box d () r]++-- In principle this should also just work for Boxes in higher dimensions. It is just+-- that we need a better way to compute their corners+instance (Num r, Ord r) => (Rectangle p r) `IsIntersectableWith` (Rectangle p r) where++  nonEmptyIntersection = defaultNonEmptyIntersection++  box@(Box a b) `intersect` box'@(Box c d)+      |    box  `containsACornerOf` box'+        || box' `containsACornerOf` box = coRec $ Box (mi :+ ()) (ma :+ ())+      | otherwise                       = coRec NoIntersection+    where++      mi = (a^.core) `max` (c^.core)+      ma = (b^.core) `min` (d^.core)++      bx `containsACornerOf` bx' = let (a',b',c',d') = corners bx'+                                   in any (\(p :+ _) -> p `inBox` bx) [a',b',c',d']+++instance PointFunctor (Box d p) where+  pmap f (Box mi ma) = Box (first (fmap f) mi) (first (fmap f) ma)+++instance (Num r, AlwaysTruePFT d) => IsTransformable (Box d p r) where+  -- Note that this does not guarantee the box is still a proper box Only use+  -- this to do translations and scalings. Other transformations may produce+  -- unexpected results.+  transformBy = transformPointFunctor+++type instance Dimension (Box d p r) = d+type instance NumType   (Box d p r) = r++--------------------------------------------------------------------------------0+-- * Functions on d-dimensonal boxes++minPoint :: Box d p r -> Point d r :+ p+minPoint b = let (Min p :+ e) = b^.minP in p :+ e++maxPoint :: Box d p r -> Point d r :+ p+maxPoint b = let (Max p :+ e) = b^.maxP in p :+ e++-- | Check if a point lies a box+--+-- >>> origin `inBox` (boundingBoxList' [point3 1 2 3, point3 10 20 30] :: Box 3 () Int)+-- False+-- >>> origin `inBox` (boundingBoxList' [point3 (-1) (-2) (-3), point3 10 20 30] :: Box 3 () Int)+-- True+inBox :: (Arity d, Ord r) => Point d r -> Box d p r -> Bool+p `inBox` b = FV.and . FV.zipWith R.inRange (toVec p) . extent $ b++-- | Get a vector with the extent of the box in each dimension. Note that the+-- resulting vector is 0 indexed whereas one would normally count dimensions+-- starting at zero.+--+-- >>> extent (boundingBoxList' [point3 1 2 3, point3 10 20 30] :: Box 3 () Int)+-- Vector3 [Range {_lower = Closed 1, _upper = Closed 10},Range {_lower = Closed 2, _upper = Closed 20},Range {_lower = Closed 3, _upper = Closed 30}]+extent                                 :: (Arity d)+                                       => Box d p r -> Vector d (R.Range r)+extent (Box (Min a :+ _) (Max b :+ _)) = FV.zipWith R.ClosedRange (toVec a) (toVec b)++-- | Get the size of the box (in all dimensions). Note that the resulting vector is 0 indexed+-- whereas one would normally count dimensions starting at zero.+--+-- >>> size (boundingBoxList' [origin, point3 1 2 3] :: Box 3 () Int)+-- Vector3 [1,2,3]+size :: (Arity d, Num r) => Box d p r -> Vector d r+size = fmap R.width . extent++-- | Given a dimension, get the width of the box in that dimension. Dimensions are 1 indexed.+--+-- >>> widthIn (C :: C 1) (boundingBoxList' [origin, point3 1 2 3] :: Box 3 () Int)+-- 1+-- >>> widthIn (C :: C 3) (boundingBoxList' [origin, point3 1 2 3] :: Box 3 () Int)+-- 3+widthIn   :: forall proxy p i d r. (Arity d, Num r, Index' (i-1) d) => proxy i -> Box d p r -> r+widthIn _ = view (V.element (C :: C (i - 1))) . size+++-- | Same as 'widthIn' but with a runtime int instead of a static dimension.+--+-- >>> widthIn' 1 (boundingBoxList' [origin, point3 1 2 3] :: Box 3 () Int)+-- Just 1+-- >>> widthIn' 3 (boundingBoxList' [origin, point3 1 2 3] :: Box 3 () Int)+-- Just 3+-- >>> widthIn' 10 (boundingBoxList' [origin, point3 1 2 3] :: Box 3 () Int)+-- Nothing+widthIn'   :: (Arity d, KnownNat d, Num r) => Int -> Box d p r -> Maybe r+widthIn' i = preview (V.element' (i-1)) . size+++----------------------------------------+-- * Rectangles, aka 2-dimensional boxes++type Rectangle = Box 2++-- >>> width (boundingBoxList' [origin, point2 1 2] :: Rectangle () Int)+-- 1+-- >>> width (boundingBoxList' [origin] :: Rectangle () Int)+-- 0+width :: Num r => Rectangle p r -> r+width = widthIn (C :: C 1)++-- >>> height (boundingBoxList' [origin, point2 1 2] :: Rectangle () Int)+-- 2+-- >>> height (boundingBoxList' [origin] :: Rectangle () Int)+-- 0+height :: Num r => Rectangle p r -> r+height = widthIn (C :: C 2)+++-- | Get the corners of a rectangle, the order is:+-- (TopLeft, TopRight, BottomRight, BottomLeft).+-- The extra values in the Top points are taken from the Top point,+-- the extra values in the Bottom points are taken from the Bottom point+corners :: Num r => Rectangle p r -> ( Point 2 r :+ p+                                     , Point 2 r :+ p+                                     , Point 2 r :+ p+                                     , Point 2 r :+ p+                                     )+corners r     = let w = width r+                    p = (_maxP r)&core %~ getMax+                    q = (_minP r)&core %~ getMin+                in ( p&core.xCoord %~ (subtract w)+                   , p+                   , q&core.xCoord %~ (+ w)+                   , q+                   )++--------------------------------------------------------------------------------+-- * Constructing bounding boxes++class IsBoxable g where+  boundingBox :: (Monoid p, Semigroup p, Ord (NumType g))+              => g -> Box (Dimension g) p (NumType g)++type IsAlwaysTrueBoundingBox g p = (Semigroup p, Arity (Dimension g))++++boundingBoxList :: (IsBoxable g, Monoid p, F.Foldable1 c, Ord (NumType g)+                    , IsAlwaysTrueBoundingBox g p+                    ) => c g -> Box (Dimension g) p (NumType g)+boundingBoxList = F.foldMap1 boundingBox+++-- | Unsafe version of boundingBoxList, that does not check if the list is non-empty+boundingBoxList' :: (IsBoxable g, Monoid p, Ord (NumType g)+                    , IsAlwaysTrueBoundingBox g p+                    ) => [g] -> Box (Dimension g) p (NumType g)+boundingBoxList' = boundingBoxList . NE.fromList++----------------------------------------++instance IsBoxable (Point d r) where+  boundingBox p = Box (Min p :+ mempty) (Max p :+ mempty)
+ src/Data/Geometry/Duality.hs view
@@ -0,0 +1,21 @@+module Data.Geometry.Duality where++import Control.Applicative+import Data.Geometry.Line+import Data.Geometry.Point+import Data.Maybe(fromJust)+--------------------------------------------------------------------------------+-- * Standard Point-Line duality in R^2++-- | Maps a line point (px,py) to a line (y=px*x - py)+dualLine              :: Num r => Point 2 r -> Line 2 r+dualLine (Point2 x y) = fromLinearFunction x (-y)++-- | Returns Nothing if the input line is vertical+-- Maps a line l: y = ax + b to a point (a,-b)+dualPoint   :: (Fractional r, Eq r) => Line 2 r -> Maybe (Point 2 r)+dualPoint l = (\(a,b) -> point2 a (-b)) <$> toLinearFunction l++-- | Pre: the input line is not vertical+dualPoint' :: (Fractional r, Eq r) => Line 2 r -> Point 2 r+dualPoint' = fromJust . dualPoint
src/Data/Geometry/HalfLine.hs view
@@ -1,22 +1,27 @@ {-# LANGUAGE TemplateHaskell  #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE DeriveFunctor  #-} {-# LANGUAGE UndecidableInstances #-} module Data.Geometry.HalfLine where  import           Control.Applicative import           Control.Lens import           Data.Ext+import qualified Data.Foldable as F import           Data.Geometry.Interval+import           Data.Geometry.Line+import           Data.Geometry.LineSegment import           Data.Geometry.Point import           Data.Geometry.Properties+import           Data.Geometry.SubLine import           Data.Geometry.Transformation import           Data.Geometry.Vector-import           Data.Geometry.Line-import           Data.Geometry.LineSegment-import           Linear.Vector((*^))+import           Data.Range+import qualified Data.Traversable as T+import           Data.UnBounded+import           Frames.CoRec import           Linear.Affine(Affine(..),distanceA)+import           Linear.Vector((*^)) + -------------------------------------------------------------------------------- -- * d-dimensional Half-Lines @@ -29,6 +34,8 @@ deriving instance (Show r, Arity d) => Show    (HalfLine d r) deriving instance (Eq r, Arity d)   => Eq      (HalfLine d r) deriving instance Arity d           => Functor (HalfLine d)+deriving instance Arity d           => F.Foldable    (HalfLine d)+deriving instance Arity d           => T.Traversable (HalfLine d)  type instance Dimension (HalfLine d r) = d type instance NumType   (HalfLine d r) = r@@ -47,75 +54,117 @@   transformBy t = toHalfLine . transformPointFunctor t . toLineSegment'     where       toLineSegment' :: (Num r, Arity d) => HalfLine d r -> LineSegment d () r-      toLineSegment' (HalfLine p v) = LineSegment (p :+ ()) ((p .+^ v) :+ ())+      toLineSegment' (HalfLine p v) = ClosedLineSegment (p :+ ()) ((p .+^ v) :+ ()) -instance (Ord r, Fractional r) => (HalfLine 2 r) `IsIntersectableWith` (Line 2 r) where-  data Intersection (HalfLine 2 r) (Line 2 r) = NoHalfLineLineIntersection-                                              | HalfLineLineIntersection !(Point 2 r)-                                              | HalfLineLineOverlap      !(HalfLine 2 r)-                                              deriving (Show,Eq)+-------------------------------------------------------------------------------- -  nonEmptyIntersection NoHalfLineLineIntersection = False-  nonEmptyIntersection _                          = True+halfLineToSubLine                :: (Arity d, Num r)+                                 => HalfLine d r -> SubLine d () (UnBounded r)+halfLineToSubLine (HalfLine p v) = let l = fmap Val $ Line p v+                                   in SubLine l (Interval (Closed $ ext (Val 0))+                                                          (Open   $ ext MaxInfinity)) -  hl `intersect` l = case supportingLine hl `intersect` l of-    SameLine _             -> HalfLineLineOverlap hl-    LineLineIntersection p -> if p `onHalfLine` hl then HalfLineLineIntersection p-                                                   else NoHalfLineLineIntersection-    ParallelLines          -> NoHalfLineLineIntersection +fromSubLine               :: (Num r, Arity d) => SubLine d p (UnBounded r) -> Maybe (HalfLine d r)+fromSubLine (SubLine l' i) = case (i^.start.core, i^.end.core) of+                               (Val x, MaxInfinity) -> f x+                               (MinInfinity, Val x) -> f x+                               _                    -> Nothing+  where+    f x = (\l@(Line _ v) -> HalfLine (pointAt x l) v)+       <$> T.mapM unBoundedToMaybe l' -instance (Ord r, Fractional r) => (HalfLine 2 r) `IsIntersectableWith` (HalfLine 2 r) where-  data Intersection (HalfLine 2 r) (HalfLine 2 r) = NoHalfLineHalfLineIntersection-                                                  | HLHLIntersectInPoint    !(Point 2 r)-                                                  | HLHLIntersectInSegment  !(LineSegment 2 () r)-                                                  | HLHLIntersectInHalfLine !(HalfLine 2 r)-                                                  deriving (Show,Eq) -  nonEmptyIntersection NoHalfLineHalfLineIntersection = False-  nonEmptyIntersection _                              = True -  hl' `intersect` hl = case supportingLine hl' `intersect` supportingLine hl of-    ParallelLines          -> NoHalfLineHalfLineIntersection-    LineLineIntersection p -> if p `onHalfLine` hl' && p `onHalfLine` hl then HLHLIntersectInPoint p-                                                                         else NoHalfLineHalfLineIntersection-    SameLine _             -> let p   = _startPoint hl'-                                  q   = _startPoint hl-                                  seg = LineSegment (p :+ ()) (q :+ ())-                              in case (p `onHalfLine` hl, q `onHalfLine` hl') of-                                   (False,False) -> NoHalfLineHalfLineIntersection-                                   (False,True)  -> HLHLIntersectInHalfLine hl-                                   (True, False) -> HLHLIntersectInHalfLine hl'-                                   (True, True)  -> if hl == hl' then HLHLIntersectInHalfLine hl-                                                                 else HLHLIntersectInSegment seg +type instance IntersectionOf (HalfLine 2 r) (Line 2 r) = [ NoIntersection+                                                         , Point 2 r+                                                         , HalfLine 2 r+                                                         ] +type instance IntersectionOf (HalfLine 2 r) (HalfLine 2 r) = [ NoIntersection+                                                             , Point 2 r+                                                             , LineSegment 2 () r+                                                             , HalfLine 2 r+                                                             ] -instance (Ord r, Fractional r) => (LineSegment 2 p r) `IsIntersectableWith` (HalfLine 2 r) where-  data Intersection (LineSegment 2 p r) (HalfLine 2 r) = NoSegmentHalfLineIntersection-                                                       | SegmentHalfLineIntersection !(Point 2 r)-                                                       | SegmentOnHalfLine           !(LineSegment 2 () r)+type instance IntersectionOf (HalfLine 2 r) (LineSegment 2 p r) = [ NoIntersection+                                                                  , Point 2 r+                                                                  , LineSegment 2 () r+                                                                  ] -  nonEmptyIntersection NoSegmentHalfLineIntersection = False-  nonEmptyIntersection _                             = True -  s `intersect` hl = case supportingLine s `intersect` supportingLine hl of-    ParallelLines          -> NoSegmentHalfLineIntersection-    LineLineIntersection p -> if p `onSegment` s && p `onHalfLine` hl then SegmentHalfLineIntersection p-                                                                      else NoSegmentHalfLineIntersection-    SameLine _             -> let p = s  ^.start.core-                                  q = s  ^.end.core-                                  r = hl ^.start.core-                                  seg a b = LineSegment (a :+ ()) (b :+ ())-                              in case (p `onHalfLine` hl, q `onHalfLine` hl) of-                                   (False, False)   -> NoSegmentHalfLineIntersection-                                   (False, True)    -> SegmentOnHalfLine $ seg r q-                                   (True,  False)   -> SegmentOnHalfLine $ seg p r-                                   (True,  True)    -> SegmentOnHalfLine $ seg p q+-- instance (Ord r, Fractional r) => (HalfLine 2 r) `IsIntersectableWith` (Line 2 r) where+  -- hl `intersect` l = match (halfLineToSubLine hl, l)  +-- instance (Ord r, Fractional r) => (HalfLine 2 r) `IsIntersectableWith` (Line 2 r) where+--   data Intersection (HalfLine 2 r) (Line 2 r) = NoHalfLineLineIntersection+--                                               | HalfLineLineIntersection !(Point 2 r)+--                                               | HalfLineLineOverlap      !(HalfLine 2 r)+--                                               deriving (Show,Eq) +--   nonEmptyIntersection NoHalfLineLineIntersection = False+--   nonEmptyIntersection _                          = True +--   hl `intersect` l = case supportingLine hl `intersect` l of+--     SameLine _             -> HalfLineLineOverlap hl+--     LineLineIntersection p -> if p `onHalfLine` hl then HalfLineLineIntersection p+--                                                    else NoHalfLineLineIntersection+--     ParallelLines          -> NoHalfLineLineIntersection+++-- instance (Ord r, Fractional r) => (HalfLine 2 r) `IsIntersectableWith` (HalfLine 2 r) where+--   data Intersection (HalfLine 2 r) (HalfLine 2 r) = NoHalfLineHalfLineIntersection+--                                                   | HLHLIntersectInPoint    !(Point 2 r)+--                                                   | HLHLIntersectInSegment  !(LineSegment 2 () r)+--                                                   | HLHLIntersectInHalfLine !(HalfLine 2 r)+--                                                   deriving (Show,Eq)++--   nonEmptyIntersection NoHalfLineHalfLineIntersection = False+--   nonEmptyIntersection _                              = True++--   hl' `intersect` hl = case supportingLine hl' `intersect` supportingLine hl of+--     ParallelLines          -> NoHalfLineHalfLineIntersection+--     LineLineIntersection p -> if p `onHalfLine` hl' && p `onHalfLine` hl then HLHLIntersectInPoint p+--                                                                          else NoHalfLineHalfLineIntersection+--     SameLine _             -> let p   = _startPoint hl'+--                                   q   = _startPoint hl+--                                   seg = LineSegment (p :+ ()) (q :+ ())+--                               in case (p `onHalfLine` hl, q `onHalfLine` hl') of+--                                    (False,False) -> NoHalfLineHalfLineIntersection+--                                    (False,True)  -> HLHLIntersectInHalfLine hl+--                                    (True, False) -> HLHLIntersectInHalfLine hl'+--                                    (True, True)  -> if hl == hl' then HLHLIntersectInHalfLine hl+--                                                                  else HLHLIntersectInSegment seg++++-- instance (Ord r, Fractional r) => (LineSegment 2 p r) `IsIntersectableWith` (HalfLine 2 r) where+--   data Intersection (LineSegment 2 p r) (HalfLine 2 r) = NoSegmentHalfLineIntersection+--                                                        | SegmentHalfLineIntersection !(Point 2 r)+--                                                        | SegmentOnHalfLine           !(LineSegment 2 () r)++--   nonEmptyIntersection NoSegmentHalfLineIntersection = False+--   nonEmptyIntersection _                             = True++--   s `intersect` hl = case supportingLine s `intersect` supportingLine hl of+--     ParallelLines          -> NoSegmentHalfLineIntersection+--     LineLineIntersection p -> if p `onSegment` s && p `onHalfLine` hl then SegmentHalfLineIntersection p+--                                                                       else NoSegmentHalfLineIntersection+--     SameLine _             -> let p = s  ^.start.core+--                                   q = s  ^.end.core+--                                   r = hl ^.start.core+--                                   seg a b = LineSegment (a :+ ()) (b :+ ())+--                               in case (p `onHalfLine` hl, q `onHalfLine` hl) of+--                                    (False, False)   -> NoSegmentHalfLineIntersection+--                                    (False, True)    -> SegmentOnHalfLine $ seg r q+--                                    (True,  False)   -> SegmentOnHalfLine $ seg p r+--                                    (True,  True)    -> SegmentOnHalfLine $ seg p q++++ -- | Test if a point lies on a half-line onHalfLine :: (Ord r, Fractional r, Arity d) => Point d r -> HalfLine d r -> Bool p `onHalfLine` (HalfLine q v) = maybe False (>= 0) $ scalarMultiple (p .-. q) v@@ -125,7 +174,8 @@   -- | Transform a LineSegment into a half-line, by forgetting the second endpoint.-toHalfLine                     :: (Num r, Arity d) => LineSegment d p r -> HalfLine d r-toHalfLine (LineSegment p' q') = let p = p' ^.core-                                     q = q' ^.core-                                 in HalfLine p (q .-. p)+-- Note that this also forgets about if the starting point was open or closed.+toHalfLine   :: (Num r, Arity d) => LineSegment d p r -> HalfLine d r+toHalfLine s = let p = s^.start.core+                   q = s^.end.core+               in HalfLine p (q .-. p)
src/Data/Geometry/Interval.hs view
@@ -1,50 +1,78 @@ {-# LANGUAGE TemplateHaskell  #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE DeriveFunctor  #-}-module Data.Geometry.Interval(-                             -- * 1 dimensional Intervals-                               Interval(..)-                             , Intersection(..)+{-# LANGUAGE DeriveFunctor #-}+module Data.Geometry.Interval-- (+                             -- -- * 1 dimensional Intervals+                             --   Interval(..)+                             -- , Intersection(..) -                             -- * querying the start and end of intervals-                             , HasStart(..), HasEnd(..)-                             -- * Working with intervals-                             , width-                             , inInterval-                             ) where+                             -- -- * querying the start and end of intervals+                             -- , HasStart(..), HasEnd(..)+                             -- -- * Working with intervals+                             -- , width+                             -- , inInterval+                             -- )+       where -import           Control.Lens+import           Control.Applicative+import           Control.Lens(makeLenses, (^.),(%~),(&), Lens')+import           Data.Bitraversable import           Data.Ext+import qualified Data.Foldable as F import           Data.Geometry.Properties+import           Data.Range+import           Data.Semigroup+import qualified Data.Traversable as T+import           Data.Vinyl+import           Frames.CoRec+import           Data.Bifunctor  -------------------------------------------------------------------------------- -data EndPointType = Open | Closed deriving (Show, Eq, Ord)+-- | An Interval is essentially a 'Data.Range' but with possible payload+newtype Interval a r = GInterval { _unInterval :: Range (r :+ a) }+                     deriving (Eq)+makeLenses ''Interval -newtype EndPoint (t :: EndPointType) a = EndPoint a deriving (Show,Read,Eq,Ord)+instance (Show a, Show r) => Show (Interval a r) where+  show ~(Interval l u) = concat [ "Interval (", show l, ") (", show u,")"] +instance Functor (Interval a) where+  fmap = T.fmapDefault -data Range l u a = Range { _lower :: EndPoint l a-                         , _upper :: EndPoint u a-                         } deriving (Show,Read,Eq,Ord)+instance F.Foldable (Interval a) where+  foldMap = T.foldMapDefault +instance T.Traversable (Interval a) where+  traverse f (GInterval r) = GInterval <$> T.traverse f' r+    where+      f' = bitraverse f pure --- newtype GLineSegment s t d p r = GLineSegment (GInterval s t p (Point d r))+instance Bifunctor Interval where+  bimap f g (GInterval r) = GInterval $ fmap (bimap g f) r --- newtype GInterval s t a r = GInterval (Range s t (r :+ a)) --- data CurveSegment c s t r = SubLine { _support :: c , _range :: Range s t r } +-- | Test if a value lies in an interval. Note that the difference between+--  inInterval and inRange is that the extra value is *not* used in the+--  comparison with inInterval, whereas it is in inRange.+inInterval       :: Ord r => r -> Interval a r -> Bool+x `inInterval` r = x `inRange` (fmap (^.core) $ r^.unInterval )  ---------------------------------------------------------------------------------+pattern OpenInterval       :: (r :+ a) -> (r :+ a) -> Interval a r+pattern OpenInterval   l u = GInterval (OpenRange   l u) -data Interval a r = Interval { _start :: r :+ a-                             , _end   :: r :+ a-                             }-                  deriving (Show,Read,Eq,Functor)+pattern ClosedInterval     :: (r :+ a) -> (r :+ a) -> Interval a r+pattern ClosedInterval l u = GInterval (ClosedRange l u)  +pattern Interval     :: EndPoint (r :+ a) -> EndPoint (r :+ a) -> Interval a r+pattern Interval l u = GInterval (Range l u)+++--------------------------------------------------------------------------------+ class HasStart t where   type StartCore t   type StartExtra t@@ -53,7 +81,7 @@ instance HasStart (Interval a r) where   type StartCore (Interval a r) = r   type StartExtra (Interval a r) = a-  start = lens _start (\(Interval _ e) s -> Interval s e)+  start = unInterval.lower.unEndPoint  class HasEnd t where   type EndCore t@@ -63,61 +91,30 @@ instance HasEnd (Interval a r) where   type EndCore (Interval a r) = r   type EndExtra (Interval a r) = a-  end = lens _end (\(Interval s _) e -> Interval s e)----- | When comparing intervals, compare them lexicographically on--- (start^.core,end^.core,start^.extra,end^.extra)-instance (Ord a, Ord r) => Ord (Interval a r) where-  (Interval (s :+ a) (t :+ b)) `compare` (Interval (p :+ c) (q :+ d)) =-    (s,t,a,b) `compare` (p,q,c,d)+  end = unInterval.upper.unEndPoint  type instance Dimension (Interval a r) = 1 type instance NumType   (Interval a r) = r  -instance Ord r => IsIntersectableWith (Interval a r) (Interval a r) where--  data Intersection (Interval a r) (Interval a r) = IntervalIntersection (Interval a r)-                                                  | NoOverlap-                                                  deriving (Show,Read,Eq,Ord)--  nonEmptyIntersection NoOverlap = False-  nonEmptyIntersection _         = True--  (Interval a b) `intersect` (Interval c d)-      | s^.core <= t^.core = IntervalIntersection $ Interval s t-      | otherwise          = NoOverlap-    where--      s = a `maxOnCore` c-      t = b `minOnCore` d-+type instance IntersectionOf (Interval a r) (Interval a r) = [NoIntersection, Interval a r] -instance Ord r => IsUnionableWith (Interval a r) (Interval a r) where+instance Ord r => (Interval a r) `IsIntersectableWith` (Interval a r) where -  data Union (Interval a r) (Interval a r) = DisjointIntervals (Interval a r) (Interval a r)-                                           | OneInterval       (Interval a r)-                                           deriving (Show,Read,Eq,Ord)+  nonEmptyIntersection = defaultNonEmptyIntersection -  i@(Interval a b) `union` j@(Interval c d)-      | i `intersects` j = OneInterval $ Interval s t-      | otherwise        = DisjointIntervals i j+  (GInterval r) `intersect` (GInterval s) = match (r' `intersect` s') $+         (H $ \NoIntersection -> coRec NoIntersection)+      :& (H $ \(Range l u)    -> coRec . GInterval $ Range (l&unEndPoint %~ g)+                                                           (u&unEndPoint %~ g) )+      :& RNil     where-      s = a `minOnCore` c-      t = b `maxOnCore` d---maxOnCore :: Ord c => c :+ e -> c :+ e -> c :+ e-l@(lc :+ _) `maxOnCore` r@(rc :+ _) = if lc >= rc then l else r+      f x = Arg (x^.core) x+      r' = fmap f r+      s' = fmap f s -minOnCore :: Ord c => c :+ e -> c :+ e -> c :+ e-l@(lc :+ _) `minOnCore` r@(rc :+ _) = if lc <= rc then l else r+      g (Arg _ x) = x  --- | Get the width of the interval-width   :: Num r => Interval a r -> r-width i = i^.end.core - i^.start.core--inInterval       :: Ord r => r -> Interval a r -> Bool-x `inInterval` i = i^.start.core <= x && x <= i^.end.core+shiftLeft'   :: Num r => r -> Interval a r -> Interval a r+shiftLeft' x = fmap (subtract x)
src/Data/Geometry/Ipe.hs view
@@ -1,8 +1,14 @@-module Data.Geometry.Ipe( module I+module Data.Geometry.Ipe( module Data.Geometry.Ipe.Types+                        , module Data.Geometry.Ipe.Writer+                        , module Data.Geometry.Ipe.Reader+                        , module Data.Geometry.Ipe.IpeOut+                        , module Data.Geometry.Ipe.FromIpe+                        , module Data.Geometry.Ipe.Attributes                         ) where -import qualified Data.Geometry.Ipe.Types as I-import qualified Data.Geometry.Ipe.Writer as I-import qualified Data.Geometry.Ipe.Reader as I-import qualified Data.Geometry.Ipe.Relations as I-import qualified Data.Geometry.Ipe.Attributes as I+import Data.Geometry.Ipe.Types+import Data.Geometry.Ipe.Writer+import Data.Geometry.Ipe.Reader+import Data.Geometry.Ipe.IpeOut+import Data.Geometry.Ipe.FromIpe+import Data.Geometry.Ipe.Attributes
src/Data/Geometry/Ipe/Attributes.hs view
@@ -1,13 +1,145 @@+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE UnicodeSyntax #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE UndecidableInstances #-} module Data.Geometry.Ipe.Attributes where -import           Control.Lens-import           Data.Text(Text)-import           Data.Geometry.Transformation(Matrix)+import           Control.Applicative hiding (Const)+import           Control.Lens hiding (rmap, Const)+import qualified Data.Foldable as F+import qualified Data.Geometry.Transformation as Transf+import           Data.Semigroup import           Data.Singletons import           Data.Singletons.TH+import           Data.Text(Text)+import qualified Data.Traversable as T+import           Data.Vinyl+import           Data.Vinyl.Functor+import           Data.Vinyl.TypeLevel+import           GHC.Exts +-------------------------------------------------------------------------------- ++data AttributeUniverse = -- common+                         Layer | Matrix | Pin | Transformations+                       -- symbol+                       | Stroke | Fill | Pen | Size+                       -- Path+                       | Dash | LineCap | LineJoin+                       | FillRule | Arrow | RArrow | Opacity | Tiling | Gradient+                       -- Group+                       | Clip+                       -- Extra+--                       | X Text+                       deriving (Show,Read,Eq)+++genSingletons [ ''AttributeUniverse ]+++type CommonAttributes = [ Layer, Matrix, Pin, Transformations ]+++type TextLabelAttributes = CommonAttributes+type MiniPageAttributes  = CommonAttributes++type ImageAttributes     = CommonAttributes+++type SymbolAttributes = CommonAttributes +++                          [Stroke, Fill, Pen, Size]++type PathAttributes = CommonAttributes +++                      [ Stroke, Fill, Dash, Pen, LineCap, LineJoin+                      , FillRule, Arrow, RArrow, Opacity, Tiling, Gradient+                      ]++type GroupAttributes = CommonAttributes ++ '[ 'Clip]+++-- | Attr implements the mapping from labels to types as specified by the+-- (symbol representing) the type family 'f'+newtype Attr (f :: TyFun u * -> *) -- Symbol repr. the Type family mapping+                                   -- Labels in universe u to concrete types+             (label :: u) = GAttr { _getAttr :: Maybe (Apply f label) }+                          deriving (Show,Read,Eq,Ord)++makeLenses ''Attr++pattern Attr x = GAttr (Just x)+pattern NoAttr = GAttr Nothing++-- | Give pref. to the *RIGHT*+instance Monoid (Attr f l) where+  mempty                 = NoAttr+  _ `mappend` b@(Attr x) = b+  a `mappend` _          = a+++newtype Attributes (f :: TyFun u * -> *) (ats :: [u]) =+  Attrs { _unAttrs :: Rec (Attr f) ats }++makeLenses ''Attributes+++-- type All' c i = RecAll (Attr (IpeObjectSymbolF i)) (IpeObjectAttrF i) c++-- deriving instance All' Show atsShow (Attributes f ats)++deriving instance (RecAll (Attr f) ats Show) => Show (Attributes f ats)++instance (RecAll (Attr f) ats Eq)   => Eq   (Attributes f ats) where+  (Attrs a) == (Attrs b) = and . recordToList+                         . zipRecsWith (\x (Compose (Dict y)) -> Const $ x == y) a+                         . (reifyConstraint (Proxy :: Proxy Eq)) $ b++instance RecApplicative ats => Monoid (Attributes f ats) where+  mempty        = Attrs $ rpure mempty+  a `mappend` b = a <> b++instance Semigroup (Attributes f ats) where+  (Attrs as) <> (Attrs bs) = Attrs $ zipRecsWith mappend as bs++++zipRecsWith                       :: (forall a. f a -> g a -> h a)+                                  -> Rec f as -> Rec g as -> Rec h as+zipRecsWith f RNil      _         = RNil+zipRecsWith f (r :& rs) (s :& ss) = f r s :& zipRecsWith f rs ss++attrLens   :: (at ∈ ats) => proxy at -> Lens' (Attributes f ats) (Maybe (Apply f at))+attrLens p = unAttrs.rlens p.getAttr++lookupAttr   :: (at ∈ ats) => proxy at -> Attributes f ats -> Maybe (Apply f at)+lookupAttr p = view (attrLens p)++setAttr               :: forall proxy at ats f. (at ∈ ats)+                      => proxy at -> Apply f at -> Attributes f ats -> Attributes f ats+setAttr p a (Attrs r) = Attrs $ rput (Attr a :: Attr f at) r+++-- | gets and removes the attribute from Attributes+takeAttr       :: forall proxy at ats f. (at ∈ ats)+               => proxy at -> Attributes f ats -> ( Maybe (Apply f at)+                                                  , Attributes f ats )+takeAttr p ats = (lookupAttr p ats, ats&attrLens p .~ Nothing)+++-- | unsets/Removes an attribute+unSetAttr   :: forall proxy at ats f. (at ∈ ats)+            => proxy at -> Attributes f ats -> Attributes f ats+unSetAttr p = snd . takeAttr p+++attr     :: (at ∈ ats, RecApplicative ats)+         => proxy at -> Apply f at -> Attributes f ats+attr p x = setAttr p x mempty++++ -------------------------------------------------------------------------------- -- | Common Attributes @@ -15,22 +147,9 @@ -- pairs. The key is some name. Which attributes an object can have depends on -- the type of the object. However, all ipe objects support the following -- 'common attributes':-data CommonAttributeUniverse = Layer | Matrix | Pin | Transformations-                             deriving (Show,Read,Eq) ---- | The CommonAttrElf family lists names of the the common attributes--- (i.e. the keys in the key value pairs), and specifies the types that the--- values should have.  For example, it specifies that 'Matrix' is a (common)--- attribute, and that the values of this attribute are of type 'Matrix 3 3 r'.-type family CommonAttrElf (f :: CommonAttributeUniverse) (r :: *) where-  CommonAttrElf 'Layer          r = Text-  CommonAttrElf 'Matrix         r = Matrix 3 3 r-  CommonAttrElf Pin             r = PinType-  CommonAttrElf Transformations r = TransformationTypes---- | A wrapper type around common attributes.-newtype CommonAttribute r s = CommonAttribute (CommonAttrElf s r)+-- data CommonAttributeUniverse = Layer | Matrix | Pin | Transformations+--                              deriving (Show,Read,Eq)  -- | Possible values for Pin data PinType = No | Yes | Horizontal | Vertical@@ -39,6 +158,18 @@ -- | Possible values for Transformation data TransformationTypes = Affine | Rigid | Translations deriving (Show,Read,Eq) +-- type family CommonAttrElf (r :: *) (f :: CommonAttributeUniverse)where+--   CommonAttrElf r 'Layer          = Text+--   CommonAttrElf r 'Matrix         = Matrix 3 3 r+--   CommonAttrElf r Pin             = PinType+--   CommonAttrElf r Transformations = TransformationTypes++-- genDefunSymbols [''CommonAttrElf]+++-- type CommonAttributes r =+--   Attributes (CommonAttrElfSym1 r) [ 'Layer, 'Matrix, Pin, Transformations ]+ -------------------------------------------------------------------------------- -- Text Attributes @@ -46,67 +177,56 @@ -- MiniPages. The same structure as for the `CommonAttributes' applies here.  -- | TODO-newtype TextLabelAttribute s r = TextLabelAttribute (CommonAttribute s r)-newtype MiniPageAttribute s r = MiniPageAttribute (CommonAttribute s r) - -------------------------------------------------------------------------------- -- | Symbol Attributes  -- | The optional Attributes for a symbol-data SymbolAttributeUniverse = SymbolStroke | SymbolFill | SymbolPen | Size-                             deriving (Show,Eq)---- | And the corresponding types-type family SymbolAttrElf (s :: SymbolAttributeUniverse) (r :: *) :: * where-  SymbolAttrElf SymbolStroke r = IpeColor-  SymbolAttrElf SymbolPen    r = IpePen r-  SymbolAttrElf SymbolFill   r = IpeColor-  SymbolAttrElf Size         r = IpeSize r----- | Wrapper around the possible SymbolAttributes-newtype SymbolAttribute r s = SymbolAttribute (SymbolAttrElf s r)+-- data SymbolAttributeUniverse = SymbolStroke | SymbolFill | SymbolPen | Size+--                              deriving (Show,Eq)   -- | Many types either consist of a symbolc value, or a value of type v data IpeValue v = Named Text | Valued v deriving (Show,Eq,Ord) +instance IsString (IpeValue v) where+  fromString = Named . fromString+ type Colour = Text -- TODO: Make this a Colour.Colour  newtype IpeSize r = IpeSize  (IpeValue r)      deriving (Show,Eq,Ord) newtype IpePen  r = IpePen   (IpeValue r)      deriving (Show,Eq,Ord) newtype IpeColor  = IpeColor (IpeValue Colour) deriving (Show,Eq,Ord) ++-- -- | And the corresponding types+-- type family SymbolAttrElf (r :: *) (s :: SymbolAttributeUniverse) :: * where+--   SymbolAttrElf r SymbolStroke = IpeColor+--   SymbolAttrElf r SymbolPen    = IpePen r+--   SymbolAttrElf r SymbolFill   = IpeColor+--   SymbolAttrElf r Size         = IpeSize r++-- genDefunSymbols [''SymbolAttrElf]+++-- type SymbolAttributes r = [SymbolStroke, SymbolFill, SymbolPen, Size]++-- type SymbolAttributes r =+--   Attributes (SymbolAttrElfSym1 r) [SymbolStroke, SymbolFill, SymbolPen, Size]+ ------------------------------------------------------------------------------- -- | Path Attributes  -- | Possible attributes for a path-data PathAttributeUniverse = Stroke | Fill | Dash | Pen | LineCap | LineJoin-                           | FillRule | Arrow | RArrow | Opacity | Tiling | Gradient-                           deriving (Show,Eq)+-- data PathAttributeUniverse = Stroke | Fill | Dash | Pen | LineCap | LineJoin+--                            | FillRule | Arrow | RArrow | Opacity | Tiling | Gradient+--                            deriving (Show,Eq) --- | and their types-type family PathAttrElf (s :: PathAttributeUniverse) (r :: *) :: * where-  PathAttrElf Stroke   r = IpeColor-  PathAttrElf Fill     r = IpeColor-  PathAttrElf Dash     r = IpeDash r-  PathAttrElf Pen      r = IpePen r-  PathAttrElf LineCap  r = Int-  PathAttrElf LineJoin r = Int-  PathAttrElf FillRule r = FillType-  PathAttrElf Arrow    r = IpeArrow r-  PathAttrElf RArrow   r = IpeArrow r-  PathAttrElf Opacity  r = IpeOpacity-  PathAttrElf Tiling   r = IpeTiling-  PathAttrElf Gradient r = IpeGradient --- | Wrapper type around possible PathAttributes-newtype PathAttribute r s = PathAttribute (PathAttrElf s r)- -- | Possible values for Dash data IpeDash r = DashNamed Text                | DashPattern [r] r+               deriving (Show,Eq)  -- | Allowed Fill types data FillType = Wind | EOFill deriving (Show,Read,Eq)@@ -122,22 +242,112 @@                            } deriving (Show,Eq) makeLenses ''IpeArrow ++-- -- | and their types+-- type family PathAttrElf (r :: *) (s :: PathAttributeUniverse) :: * where+--   PathAttrElf r Stroke   = IpeColor+--   PathAttrElf r Fill     = IpeColor+--   PathAttrElf r Dash     = IpeDash r+--   PathAttrElf r Pen      = IpePen r+--   PathAttrElf r LineCap  = Int+--   PathAttrElf r LineJoin = Int+--   PathAttrElf r FillRule = FillType+--   PathAttrElf r Arrow    = IpeArrow r+--   PathAttrElf r RArrow   = IpeArrow r+--   PathAttrElf r Opacity  = IpeOpacity+--   PathAttrElf r Tiling   = IpeTiling+--   PathAttrElf r Gradient = IpeGradient++-- genDefunSymbols [''PathAttrElf]++-- type PathAttributes r = [ Stroke, Fill, Dash, Pen, LineCap, LineJoin+--                         , FillRule, Arrow, RArrow, Opacity, Tiling, Gradient+--                         ]++-- type PathAttributes r =+--   Attributes (PathAttrElfSym1 r) [ Stroke, Fill, Dash, Pen, LineCap, LineJoin+--                                  , FillRule, Arrow, RArrow, Opacity, Tiling, Gradient+--                                  ]+ -------------------------------------------------------------------------------- -- | Group Attributes   -- | The only group attribute is a Clip-data GroupAttributeUniverse = Clip deriving (Show,Read,Eq,Ord)+-- data GroupAttributeUniverse = Clip deriving (Show,Read,Eq,Ord)  -- A clipping path is a Path. Which is defined in Data.Geometry.Ipe.Types. To -- avoid circular imports, we define GroupAttrElf and GroupAttribute there.   --------------------------------------------------------------------------------+-- * Attribute names in Ipe --- | Generate singletons for all the Universes-genSingletons [ ''CommonAttributeUniverse-              , ''SymbolAttributeUniverse-              , ''PathAttributeUniverse-              , ''GroupAttributeUniverse-              ]++-- | For the types representing attribute values we can get the name/key to use+-- when serializing to ipe.+class IpeAttrName (a :: AttributeUniverse) where+  attrName :: Proxy a -> Text++-- CommonAttributeUnivers+instance IpeAttrName Layer           where attrName _ = "layer"+instance IpeAttrName Matrix          where attrName _ = "matrix"+instance IpeAttrName Pin             where attrName _ = "pin"+instance IpeAttrName Transformations where attrName _ = "transformations"++-- IpeSymbolAttributeUniversre+instance IpeAttrName Stroke       where attrName _ = "stroke"+instance IpeAttrName Fill         where attrName _ = "fill"+instance IpeAttrName Pen          where attrName _ = "pen"+instance IpeAttrName Size         where attrName _ = "size"++-- PathAttributeUniverse+instance IpeAttrName Dash       where attrName _ = "dash"+instance IpeAttrName LineCap    where attrName _ = "cap"+instance IpeAttrName LineJoin   where attrName _ = "join"+instance IpeAttrName FillRule   where attrName _ = "fillrule"+instance IpeAttrName Arrow      where attrName _ = "arrow"+instance IpeAttrName RArrow     where attrName _ = "rarrow"+instance IpeAttrName Opacity    where attrName _ = "opacity"+instance IpeAttrName Tiling     where attrName _ = "tiling"+instance IpeAttrName Gradient   where attrName _ = "gradient"++-- GroupAttributeUniverse+instance IpeAttrName Clip     where attrName _ = "clip"+++-- | Wrap up a value with a capability given by its type+data GDict (c :: k -> Constraint) (a :: k) where+  GDict :: c a => Proxy a -> GDict c a++-- -- | Sometimes we may know something for /all/ fields of a record, but when+-- -- you expect to be able to /each/ of the fields, you are then out of luck.+-- -- Surely given @∀x:u.φ(x)@ we should be able to recover @x:u ⊢ φ(x)@! Sadly,+-- -- the constraint solver is not quite smart enough to realize this and we must+-- -- make it patently obvious by reifying the constraint pointwise with proof.+-- gReifyConstraint+--   :: RecAll f rs c+--   => proxy c+--   -> Rec f rs+--   -> Rec (GDict c :. f) rs+-- gReifyConstraint prx RNil      = RNil+-- gReifyConstraint prx (x :& xs) = Compose (mkDict x) :& reifyConstraint prx xs+--   where+--     mkDict   :: f l -> (GDict c l)+--     mkDict _ = GDict (Proxy :: Proxy l)++-- | Function that states that all elements in xs satisfy a given constraint c+type family AllSatisfy (c :: k -> Constraint) (xs :: [k]) :: Constraint where+  AllSatisfy c '[] = ()+  AllSatisfy c (x ': xs) = (c x, AllSatisfy c xs)+++-- | Writing Attribute names+writeAttrNames           :: AllSatisfy IpeAttrName rs => Rec f rs -> Rec (Const Text) rs+writeAttrNames RNil      = RNil+writeAttrNames (x :& xs) = Const (write'' x) :& writeAttrNames xs+  where+    write''   :: forall f s. IpeAttrName s => f s -> Text+    write'' _ = attrName (Proxy :: Proxy s)++--
+ src/Data/Geometry/Ipe/FromIpe.hs view
@@ -0,0 +1,50 @@+module Data.Geometry.Ipe.FromIpe where++import Control.Applicative+import qualified Data.Traversable as Tr+import Data.Ext+import Data.Geometry.Ipe.Types+import Data.Geometry.Line+import Data.Geometry.LineSegment+import qualified Data.Geometry.PolyLine as PolyLine+import Data.Geometry.Polygon+import Control.Lens+import qualified Data.Seq2 as S2+++-- | Try to convert a path into a line segment, fails if the path is not a line+-- segment or a polyline with more than two points.+_asLineSegment :: Prism' (Path r) (LineSegment 2 () r)+_asLineSegment = prism' seg2path path2seg+  where+    seg2path   = review _asPolyLine . PolyLine.fromLineSegment+    path2seg p = PolyLine.asLineSegment' =<< preview _asPolyLine p+++-- | Convert to a polyline. Ignores all non-polyline parts+_asPolyLine :: Prism' (Path r) (PolyLine.PolyLine 2 () r)+_asPolyLine = prism' poly2path path2poly+  where+    poly2path = Path . S2.l1Singleton  . PolyLineSegment+    path2poly = preview (pathSegments.Tr.traverse._PolyLineSegment)+    -- TODO: Check that the path actually is a polyline, rather+    -- than ignoring everything that does not fit++-- | Convert to a simple polygon: simply takes the first closed path+_asSimplePolygon :: Prism' (Path r) (SimplePolygon () r)+_asSimplePolygon = prism' poly2path path2poly+  where+    poly2path = Path . S2.l1Singleton . PolygonPath+    path2poly = preview (pathSegments.Tr.traverse._PolygonPath)+    -- TODO: Check that the path actually is a simple polygon, rather+    -- than ignoring everything that does not fit++-- | use the first prism to select the ipe object to depicle with, and the second+-- how to select the geometry object from there on. Then we can select the geometry+-- object, directly with its attributes here.+_withAttrs       :: Prism' (IpeObject r) (i r :+ IpeAttributes i r) -> Prism' (i r) g+                 -> Prism' (IpeObject r) (g :+ IpeAttributes i r)+_withAttrs po pg = prism' g2o o2g+  where+    g2o    = review po . over core (review pg)+    o2g o  = preview po o >>= \(i :+ ats) -> (:+ ats) <$> preview pg i
+ src/Data/Geometry/Ipe/IpeOut.hs view
@@ -0,0 +1,212 @@+{-# LANGUAGE OverloadedStrings #-}+module Data.Geometry.Ipe.IpeOut where++import           Control.Applicative+import           Control.Lens+import           Data.Bifunctor+import           Data.Ext+import qualified Data.Foldable as F+import           Data.Geometry.Ball+import           Data.Geometry.Boundary+import           Data.Geometry.Ipe.Attributes+import           Data.Geometry.Ipe.Types+import           Data.Geometry.LineSegment+import           Data.Geometry.Point+import           Data.Geometry.Box+import           Data.Geometry.Polygon+import           Data.Geometry.PolyLine+import           Data.Geometry.Properties+import           Data.Geometry.Transformation+import qualified Data.List.NonEmpty as NE+import           Data.Semigroup+import           Data.Proxy+import qualified Data.Seq2 as S2+import           Data.Text(Text)+import qualified Data.Traversable as Tr+import           Data.Vinyl++--------------------------------------------------------------------------------++-- | An IpeOut is essentially a funciton to convert a geometry object of type+-- 'g' into an ipe object of type 'i'.+newtype IpeOut g i = IpeOut { asIpe :: g -> i } deriving (Functor)++-- | Given an geometry object, and a record with its attributes, construct an ipe+-- Object representing it using the default conversion.+asIpeObject :: (HasDefaultIpeOut g, DefaultIpeOut g ~ i, NumType g ~ r)+            => g -> IpeAttributes i r -> IpeObject r+asIpeObject = asIpeObjectWith defaultIpeOut++-- | asIpeObject with its arguments flipped. Convenient if you don't want to+-- map asIpeObject over a list or so.+asIpeObject' :: (HasDefaultIpeOut g, DefaultIpeOut g ~ i, NumType g ~ r)+             => IpeAttributes i r -> g -> IpeObject r+asIpeObject' = flip asIpeObject+++-- -- | Given a IpeOut that specifies how to convert a geometry object into an+-- ipe geometry object, the geometry object, and a record with its attributes,+-- construct an ipe Object representing it.+asIpeObjectWith          :: (ToObject i, NumType g ~ r)+                      => IpeOut g (IpeObject' i r) -> g -> IpeAttributes i r -> IpeObject r+asIpeObjectWith io g ats = asIpe (ipeObject io ats) g+++-- | Create an ipe group without group attributes+asIpeGroup :: [IpeObject r] -> IpeObject r+asIpeGroup = flip asIpeGroup' mempty++-- | Creates a group out of ipe+asIpeGroup'        :: [IpeObject r] -> IpeAttributes Group r -> IpeObject r+asIpeGroup' gs ats = IpeGroup $ Group gs :+ ats++--------------------------------------------------------------------------------++-- | Helper to construct an IpeOut g IpeObject , if we already know how to+-- construct a specific Ipe type.+ipeObject        :: (ToObject i, NumType g ~ r)+                   => IpeOut g (IpeObject' i r) -> IpeAttributes i r -> IpeOut g (IpeObject r)+ipeObject io ats = IpeOut $ \g -> let (i :+ ats') = asIpe io g+                                    in ipeObject' i (ats' <> ats)++-- | Construct an ipe object from the core of an Ext+coreOut    :: IpeOut g i -> IpeOut (g :+ a) i+coreOut io = IpeOut $ asIpe io . (^.core)++--------------------------------------------------------------------------------+-- * Default Conversions++-- | Class that specifies a default conversion from a geometry type g into an+-- ipe object.+class ToObject (DefaultIpeOut g) => HasDefaultIpeOut g where+  type DefaultIpeOut g :: * -> *++  defaultIpeOut :: IpeOut g (IpeObject' (DefaultIpeOut g) (NumType g))++  -- defaultIpeObject :: RecApplicative (AttributesOf (DefaultIpeOut g))+  --                  => IpeOut g (IpeObject (NumType g))+  -- defaultIpeObject = IpeOut $ flip asIpeObject mempty++instance HasDefaultIpeOut (Point 2 r) where+  type DefaultIpeOut (Point 2 r) = IpeSymbol+  defaultIpeOut = ipeDiskMark++instance HasDefaultIpeOut (LineSegment 2 p r) where+  type DefaultIpeOut (LineSegment 2 p r) = Path+  defaultIpeOut = ipeLineSegment++instance Floating r => HasDefaultIpeOut (Disk p r) where+  type DefaultIpeOut (Disk p r) = Path+  defaultIpeOut = ipeDisk++instance HasDefaultIpeOut (PolyLine 2 p r) where+  type DefaultIpeOut (PolyLine 2 p r) = Path+  defaultIpeOut = noAttrs ipePolyLine++instance HasDefaultIpeOut (SimplePolygon p r) where+  type DefaultIpeOut (SimplePolygon p r) = Path+  defaultIpeOut = flip addAttributes ipeSimplePolygon $+                    mempty <> attr SFill (IpeColor "red")++--------------------------------------------------------------------------------+-- * Point Converters++ipeMark   :: Text -> IpeOut (Point 2 r) (IpeObject' IpeSymbol r)+ipeMark n = noAttrs . IpeOut $ flip Symbol n++ipeDiskMark :: IpeOut (Point 2 r) (IpeObject' IpeSymbol r)+ipeDiskMark = ipeMark "mark/disk(sx)"++--------------------------------------------------------------------------------++noAttrs :: Monoid extra => IpeOut g core -> IpeOut g (core :+ extra)+noAttrs = addAttributes mempty++addAttributes :: extra -> IpeOut g core -> IpeOut g (core :+ extra)+addAttributes ats io = IpeOut $ \g -> asIpe io g :+ ats+++-- | Default size of the cliping rectangle used to clip lines. This is+-- Rectangle is large enough to cover the normal page size in ipe.+defaultClipRectangle :: (Num r, Ord r) => Rectangle () r+defaultClipRectangle = boundingBox (point2 (-200) (-200)) <>+                       boundingBox (point2 1000 1000)++-- -- | An ipe out to draw a line, by clipping it to stay within a rectangle of+-- -- default size.+-- line :: IpeOut (Line 2 r) (IpeObject' Path r)+-- line = line' defaultClipRectangle++-- -- | An ipe out to draw a line, by clipping it to stay within the rectangle+-- line'   :: Rectangle p r -> IpeOut (Line 2 r) (IpeObject' Path r)+-- line' r = IpeOut $ \l -> error "not implemented yet"+++ipeLineSegment :: IpeOut (LineSegment 2 p r) (IpeObject' Path r)+ipeLineSegment = noAttrs $ fromPathSegment ipeLineSegment'++ipeLineSegment' :: IpeOut (LineSegment 2 p r) (PathSegment r)+ipeLineSegment' = IpeOut $ PolyLineSegment . fromLineSegment . first (const ())+++ipePolyLine :: IpeOut (PolyLine 2 p r) (Path r)+ipePolyLine = fromPathSegment ipePolyLine'++ipePolyLine' :: IpeOut (PolyLine 2 a r) (PathSegment r)+ipePolyLine' = IpeOut $ PolyLineSegment . first (const ())++ipeDisk :: Floating r => IpeOut (Disk p r) (IpeObject' Path r)+ipeDisk = noAttrs . IpeOut $ asIpe ipeCircle . Boundary++ipeCircle :: Floating r => IpeOut (Circle p r) (Path r)+ipeCircle = fromPathSegment ipeCircle'++ipeCircle' :: Floating r => IpeOut (Circle p r) (PathSegment r)+ipeCircle' = IpeOut circle''+  where+    circle'' (Circle (c :+ _) r) = EllipseSegment m+      where+        m = translation (toVec c) |.| uniformScaling (sqrt r) ^. transformationMatrix+        -- m is the matrix s.t. if we apply m to the unit circle centered at the origin, we+        -- get the input circle.+++-- | Helper to construct a IpeOut g Path, for when we already have an IpeOut g PathSegment+fromPathSegment    :: IpeOut g (PathSegment r) -> IpeOut g (Path r)+fromPathSegment io = IpeOut $ Path . S2.l1Singleton . asIpe io++ipeSimplePolygon :: IpeOut (SimplePolygon p r) (Path r)+ipeSimplePolygon = fromPathSegment . IpeOut $ PolygonPath . dropExt+  where+    dropExt                    :: SimplePolygon p r -> SimplePolygon () r+    dropExt (SimplePolygon vs) = SimplePolygon $ fmap (&extra .~ ()) vs++++++-- ls = (ClosedLineSegment (ext origin) (ext (point2 1 1)))+++-- testzz :: IpeObject Integer+-- testzz = asIpeObjectWith ipeLineSegment ls $ mempty <> attr SStroke (IpeColor "red")+++++-- test' :: Attributes (PathAttrElfSym1 Integer) (AttributesOf (Path Integer) (PathAttrElfSym1 Integer))+-- -- test' :: RecApplicative (AttributesOf (Path Integer) (IpeObjectSymbolF (Path Integer)))+-- --       => IpeAttributes (Path Integer)+-- test' = mempty+++++-- -- test' :: IpeObject Integer ('IpePath '[])+-- test' = asIpeObject' ls emptyPathAttributes+++++-- emptyPathAttributes :: Rec (PathAttribute r) '[]+-- emptyPathAttributes = RNil
+ src/Data/Geometry/Ipe/Literal.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE TemplateHaskell #-}+module Data.Geometry.Ipe.Literal where+++import Language.Haskell.TH+import Language.Haskell.TH.Quote+import qualified Data.Text as T+import qualified Data.ByteString.Char8 as C+import Text.XML.Expat.Tree++literally :: String -> Q Exp+literally = return . LitE . StringL++lit :: QuasiQuoter+lit = QuasiQuoter { quoteExp = literally+                  , quotePat = undefined+                  , quoteType = undefined+                  , quoteDec  = undefined+                  }++litFile :: QuasiQuoter+litFile = quoteFile lit+++xmlLiteral :: String -> Node T.Text T.Text+xmlLiteral = either (error "xmlLiteral. error parsing xml: " . show) id+           .  parse' defaultParseOptions . C.pack
src/Data/Geometry/Ipe/ParserPrimitives.hs view
@@ -6,17 +6,13 @@                                          , (<*><>) , (<*><)                                          , (<***>) , (<***) , (***>)                                          , pMaybe , pCount , pSepBy-                                         , Parser(..) , ParseError+                                         , Parser, ParseError                                          , pNotFollowedBy                                          ) where ---- import Data.Functor.Identity- import           Control.Applicative hiding (many,(<|>))- import           Text.Parsec(try)-import           Text.Parsec(ParsecT(..),Parsec, Stream(..))+import           Text.Parsec(ParsecT, Stream) import           Text.Parsec.Text import           Text.ParserCombinators.Parsec hiding (Parser,try) @@ -74,11 +70,12 @@   -- | infix variant of notfollowed by+pNotFollowedBy :: Parser a -> Parser b -> Parser a p `pNotFollowedBy` q = do { x <- p ; notFollowedBy' q ; return x }     where       -- | copy of the original notFollowedBy but replaced the error message       -- to get rid of the Show dependency-      notFollowedBy' p     = try (do{ c <- try p; unexpected "not followed by" }+      notFollowedBy' z     = try (do{ _ <- try z; unexpected "not followed by" }                                     <|> return ()                                  ) @@ -98,7 +95,9 @@   return $ f x  -p <*>< q = (\a _ -> a) <$> p <*><> q+(<*><)   :: (Stream s m t, Reversable s)+          => ParsecT s u m b -> ParsecT s u m a -> ParsecT s u m b+p <*>< q = const <$> p <*><> q   rev :: (Reversable s, Stream s m t) => ParsecT s u m ()
src/Data/Geometry/Ipe/PathParser.hs view
@@ -4,19 +4,21 @@  import           Numeric +import           Data.Ext(ext) import           Control.Applicative import           Control.Monad  import           Data.Bifunctor import           Data.Monoid(mconcat) import           Data.Semigroup-import           Data.Validation(AccValidation(..))+-- import           Data.Validation(AccValidation(..))  import           Data.Char(isSpace) import           Data.Ratio  import           Text.Parsec.Error(messageString, errorMessages) import           Data.Geometry.Point+import           Data.Geometry.Box import           Data.Geometry.Vector import           Data.Geometry.Transformation import           Data.Geometry.Ipe.ParserPrimitives@@ -50,9 +52,15 @@ ----------------------------------------------------------------------- -- | Running the parsers +readCoordinate :: Coordinate r => Text -> Either Text r+readCoordinate = runParser pCoordinate+ readPoint :: Coordinate r => Text -> Either Text (Point 2 r)-readPoint = bimap errorText fst . runP pPoint+readPoint = runParser pPoint +runParser   :: Parser a -> Text -> Either Text a+runParser p = bimap errorText fst . runP p+ -- Collect errors data Either' l r = Left' l | Right' r deriving (Show,Eq) instance (Semigroup l, Semigroup r, Monoid r) => Monoid (Either' l r) where@@ -100,8 +108,12 @@   readMatrix :: Coordinate r => Text -> Either Text (Matrix 3 3 r)-readMatrix = bimap errorText fst . runP pMatrix+readMatrix = runParser pMatrix ++readRectangle :: Coordinate r => Text -> Either Text (Rectangle () r)+readRectangle = runParser pRectangle+ ----------------------------------------------------------------------- -- | The parsers themselves @@ -131,6 +143,11 @@               where                 pDecimal = pMaybe (pChar '.' *> pInteger) +pRectangle :: Coordinate r => Parser (Rectangle () r)+pRectangle = (\p q -> fromCornerPoints (ext p) (ext q)) <$> pPoint+                                                        <*  pWhiteSpace+                                                        <*> pPoint+ pMatrix :: Coordinate r => Parser (Matrix 3 3 r) pMatrix = (\a b -> mkMatrix (a:b)) <$> pCoordinate                                    <*> pCount 5 (pWhiteSpace *> pCoordinate)@@ -148,3 +165,4 @@                          -- But ipe uses the following order:                          -- 024                          -- 135+mkMatrix _             = error "mkMatrix: need exactly 6 arguments"
src/Data/Geometry/Ipe/Reader.hs view
@@ -1,65 +1,431 @@+{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleInstances #-}-module Data.Geometry.Ipe.Reader where+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE PolyKinds #-}+module Data.Geometry.Ipe.Reader( -- * Reading ipe Files+                                 readRawIpeFile+                               , readIpeFile+                               , readSinglePageFile+                               , ConversionError +                               -- * Reading XML directly+                               , fromIpeXML+                               , readXML++                               -- * Read classes+                               , IpeReadText(..)+                               , IpeRead(..)+                               , IpeReadAttr(..)+++                               -- * Some low level implementation functions+                               , ipeReadTextWith+                               , ipeReadObject+                               , ipeReadAttrs+                               , ipeReadRec+                               ) where++import           Data.Proxy import           Data.Either(rights)-import           Control.Applicative-import           Control.Lens+import           Control.Applicative hiding (Const)+import           Control.Lens hiding (Const, rmap)  import           Data.Ext import qualified Data.Foldable as F+import qualified Data.Traversable as Tr+import           Data.Maybe(fromMaybe, isJust, mapMaybe)+import qualified Data.List as L import           Data.Vinyl+import           Data.Vinyl.Functor+import           Data.Vinyl.TypeLevel -import           Data.Validation+import qualified Data.List.NonEmpty as NE+-- import           Data.Validation+import qualified Data.Seq2     as S2  import           Data.Geometry.Point-import           Data.Geometry.Line-import           Data.Geometry.LineSegment+import           Data.Geometry.Box+import qualified Data.Geometry.Polygon as Polygon import           Data.Geometry.PolyLine+import qualified Data.Geometry.Transformation as Trans import           Data.Geometry.Ipe.Types import           Data.Geometry.Ipe.Attributes+import qualified Data.Geometry.Ipe.Attributes as IA+import           Data.Geometry.Ipe.PathParser+import           Data.Geometry.Ipe.ParserPrimitives(pInteger)  import qualified Data.ByteString as B import           Data.Monoid+import           Data.Singletons import           Data.Text(Text) -import           Data.Geometry.Ipe.PathParser  import           Text.XML.Expat.Tree  import qualified Data.Text as T+-- import qualified Data.Map as M --- fromIpeFile :: (Coordinate r, IpeRead t) => FilePath -> IO [PolyLine 2 () r]--- fromIpeFile+-------------------------------------------------------------------------------- +type ConversionError = Text -fromIpeXML   :: (Coordinate r, IpeRead t) => B.ByteString -> Either ConversionError (t r)-fromIpeXML b = (bimap (T.pack . show) id $ parse' defaultParseOptions b) >>= ipeRead -class IpeReadText t where-  ipeReadText :: Coordinate r => Text -> Either ConversionError (t r)+-- | Given a file path, tries to read an ipe file+readRawIpeFile :: Coordinate r => FilePath -> IO (Either ConversionError (IpeFile r))+readRawIpeFile = fmap fromIpeXML . B.readFile -type ConversionError = Text --- TODO: We also want to do something with the attributes+-- | Given a file path, tries to read an ipe file. This function applies all+-- matrices to objects.+readIpeFile :: Coordinate r => FilePath -> IO (Either ConversionError (IpeFile r))+readIpeFile = fmap (bimap id applyMatrices) . readRawIpeFile ++-- | Since most Ipe file contain only one page, we provide a shortcut for that+-- as well. This function applies all matrices.+readSinglePageFile :: Coordinate r => FilePath -> IO (Either ConversionError (IpePage r))+readSinglePageFile = fmap f . readIpeFile+  where+    f (Left e)  = Left e+    f (Right i) = maybe (Left "No Ipe pages found") Right . firstOf (pages.traverse) $ i++-- | Given a Bytestring, try to parse the bytestring into anything that is+-- IpeReadable, i.e. any of the Ipe elements.+fromIpeXML   :: (Coordinate r, IpeRead (t r))+             => B.ByteString -> Either ConversionError (t r)+fromIpeXML b = readXML b >>= ipeRead++-- | Reads the data from a Bytestring into a proper Node+readXML :: B.ByteString -> Either ConversionError (Node Text Text)+readXML = bimap (T.pack . show) id . parse' defaultParseOptions++--------------------------------------------------------------------------------++-- | Reading an ipe elemtn from a Text value+class IpeReadText t where+  ipeReadText :: Text -> Either ConversionError t++-- | Reading an ipe lement from Xml class IpeRead t where-  ipeRead :: Coordinate r => Node Text Text -> Either ConversionError (t r)+  ipeRead  :: Node Text Text -> Either ConversionError t --- instance IpeRead IpeSymbol where---   ipeRead (Element "use" ats _) = case extract ["pos","name"] ats of+--------------------------------------------------------------------------------+--  ReadText instances --- given a list of keys, and a list of attributes. Extracts the values matching--- the keys. The result is a pair (vs,others), where others are the remaining--- attributes, and vs are the values corresponding to the keys. Sorted on--- increasing order of their keys.-extract :: [Text] -> [(Text,Text)] -> ([Text],[(Text,Text)])-extract = undefined+instance IpeReadText Text where+  ipeReadText = Right -instance IpeReadText (PolyLine 2 ()) where+instance IpeReadText Int where+  ipeReadText = fmap fromInteger . runParser pInteger++instance Coordinate r => IpeReadText (Point 2 r) where+  ipeReadText = readPoint++instance Coordinate r => IpeReadText (Trans.Matrix 3 3 r) where+  ipeReadText = readMatrix++instance IpeReadText LayerName where+  ipeReadText = Right . LayerName++instance IpeReadText PinType where+  ipeReadText "yes" = Right Yes+  ipeReadText "h"   = Right Horizontal+  ipeReadText "v"   = Right Vertical+  ipeReadText ""    = Right No+  ipeReadText _     = Left "invalid PinType"++instance IpeReadText TransformationTypes where+  ipeReadText "affine"       = Right Affine+  ipeReadText "rigid"        = Right Rigid+  ipeReadText "translations" = Right Translations+  ipeReadText _              = Left "invalid TransformationType"++instance IpeReadText FillType where+  ipeReadText "wind"   = Right Wind+  ipeReadText "eofill" = Right EOFill+  ipeReadText _        = Left "invalid FillType"++instance Coordinate r => IpeReadText (IpeArrow r) where+  ipeReadText t = case T.split (== '/') t of+                    [n,s] -> IpeArrow <$> pure n <*> ipeReadText s+                    _     -> Left "ipeArrow: name contains not exactly 1 / "++instance Coordinate r => IpeReadText (IpeDash r) where+  ipeReadText t = Right . DashNamed $ t+                  -- TODO: Implement proper parsing here+++ipeReadTextWith     :: (Text -> Either t v) -> Text -> Either ConversionError (IpeValue v)+ipeReadTextWith f t = case f t of+                        Right v -> Right (Valued v)+                        Left _  -> Right (Named t)+++instance Coordinate r => IpeReadText (Rectangle () r) where+  ipeReadText = readRectangle++instance IpeReadText IpeColor where+  ipeReadText = fmap IpeColor . ipeReadTextWith Right++instance Coordinate r => IpeReadText (IpePen r) where+  ipeReadText = fmap IpePen . ipeReadTextWith readCoordinate++instance Coordinate r => IpeReadText (IpeSize r) where+  ipeReadText = fmap IpeSize . ipeReadTextWith readCoordinate+++instance Coordinate r => IpeReadText [Operation r] where+  ipeReadText = readPathOperations++instance Coordinate r => IpeReadText (NE.NonEmpty (PathSegment r)) where+  ipeReadText t = ipeReadText t >>= fromOpsN+    where+      fromOpsN xs = case fromOps xs of+                      Left l       -> Left l+                      Right []     -> Left "No path segments produced"+                      Right (p:ps) -> Right $ p NE.:| ps++      fromOps []            = Right []+      fromOps (MoveTo p:xs) = fromOps' p xs+      fromOps _             = Left "Path should start with a move to"++      fromOps' _ []             = Left "Found only a MoveTo operation"+      fromOps' s (LineTo q:ops) = let (ls,xs) = span' _LineTo ops+                                      pts  = map ext $ s:q:mapMaybe (^?_LineTo) ls+                                      poly = Polygon.fromPoints pts+                                      pl   = fromPoints pts+                                  in case xs of+                                       (ClosePath : xs') -> PolygonPath poly   <<| xs'+                                       _                 -> PolyLineSegment pl <<| xs+      fromOps' _ _ = Left "fromOpts': rest not implemented yet."++      span' pr = L.span (not . isn't pr)++      x <<| xs = (x:) <$> fromOps xs++instance Coordinate r => IpeReadText (Path r) where+  ipeReadText = fmap (Path . S2.viewL1FromNonEmpty) . ipeReadText++--------------------------------------------------------------------------------+-- Reading attributes++-- | Basically IpeReadText for attributes. This class is not really meant to be+-- implemented directly. Just define an IpeReadText instance for the type+-- (Apply f at), then the generic instance below takes care of looking up the+-- name of the attribute, and calling the right ipeReadText value. This class+-- is just so that reifyConstraint in `ipeReadRec` can select the right+-- typeclass when building the rec.+class IpeReadAttr t where+  ipeReadAttr  :: Text -> Node Text Text -> Either ConversionError t++instance IpeReadText (Apply f at) => IpeReadAttr (Attr f at) where+  ipeReadAttr n (Element _ ats _) = GAttr <$> Tr.mapM ipeReadText (lookup n ats)+  ipeReadAttr _ _                 = Left "IpeReadAttr: Element expected, Text found"++-- | Combination of zipRecWith and traverse+zipTraverseWith                       :: forall f g h i (rs :: [u]). Applicative h+                                      => (forall (x :: u). f x -> g x -> h (i x))+                                      -> Rec f rs -> Rec g rs -> h (Rec i rs)+zipTraverseWith _ RNil      RNil      = pure RNil+zipTraverseWith f (x :& xs) (y :& ys) = (:&) <$> f x y <*> zipTraverseWith f xs ys++-- | Reading the Attributes into a Rec (Attr f), all based on the types of f+-- (the type family mapping labels to types), and a list of labels (ats).+ipeReadRec       :: forall f ats.+                 ( RecApplicative ats+                 , RecAll (Attr f) ats IpeReadAttr+                 , AllSatisfy IpeAttrName ats+                 )+                 => Proxy f -> Proxy ats+                 -> Node Text Text+                 -> Either ConversionError (Rec (Attr  f) ats)+ipeReadRec _ _ x = zipTraverseWith f (writeAttrNames r) r'+  where+    r  = rpure (GAttr Nothing)+    r' = reifyConstraint (Proxy :: Proxy IpeReadAttr) r+++    f                              :: forall at.+                                      Const Text at+                                   -> (Dict IpeReadAttr :. Attr f) at+                                   -> Either ConversionError (Attr f at)+    f (Const n) (Compose (Dict _)) = ipeReadAttr n x+++-- | Reader for records. Given a proxy of some ipe type i, and a proxy of an+-- coordinate type r, read the IpeAttributes for i from the xml node.+ipeReadAttrs     :: forall proxy proxy' i r f ats.+                 ( f ~ AttrMapSym1 r, ats ~ AttributesOf i+                 , RecApplicative ats+                 , RecAll (Attr f) ats IpeReadAttr+                 , AllSatisfy IpeAttrName ats+                 )+                 => proxy i -> proxy' r+                 -> Node Text Text+                 -> Either ConversionError (IpeAttributes i r)+ipeReadAttrs _ _ = fmap Attrs . ipeReadRec (Proxy :: Proxy f) (Proxy :: Proxy ats)+++testSym :: B.ByteString+testSym = "<use name=\"mark/disk(sx)\" pos=\"320 736\" size=\"normal\" stroke=\"black\"/>"+++++-- readAttrsFromXML :: B.ByteString -> Either++readSymAttrs :: Either ConversionError (IpeAttributes IpeSymbol Double)+readSymAttrs = readXML testSym+               >>= ipeReadAttrs (Proxy :: Proxy IpeSymbol) (Proxy :: Proxy Double)++++++-- | If we can ipeRead an ipe element, and we can ipeReadAttrs its attributes+-- we can properly read an ipe object using ipeReadObject+ipeReadObject           :: ( IpeRead (i r)+                           , f ~ AttrMapSym1 r, ats ~ AttributesOf i+                           ,  RecApplicative ats+                           , RecAll (Attr f) ats IpeReadAttr+                           , AllSatisfy IpeAttrName ats+                           )+                        => Proxy i -> proxy r -> Node Text Text+                        -> Either ConversionError (i r :+ IpeAttributes i r)+ipeReadObject prI prR xml = (:+) <$> ipeRead xml <*> ipeReadAttrs prI prR xml+++--------------------------------------------------------------------------------+-- | Ipe read instances++instance Coordinate r => IpeRead (IpeSymbol r) where+  ipeRead (Element "use" ats _) = case lookup "pos" ats of+      Nothing -> Left "symbol without position"+      Just ps -> flip Symbol name <$> ipeReadText ps+    where+      name = fromMaybe "mark/disk(sx)" $ lookup "name" ats+  ipeRead _ = Left "symbol element expected, text found"++-- | Given a list of Nodes, try to parse all of them as a big text. If we+-- encounter anything else then text, the parsing fails.+allText :: [Node Text Text] -> Either ConversionError Text+allText = fmap T.unlines . mapM unT+  where+    unT (Text t) = Right t+    unT _        = Left "allText: Expected Text, found an Element"++instance Coordinate r => IpeRead (Path r) where+  ipeRead (Element "path" _ chs) = allText chs >>= ipeReadText+  ipeRead _                      = Left "path: expected element, found text"+++lookup'   :: Text -> [(Text,a)] -> Either ConversionError a+lookup' k = maybe (Left $ "lookup' " <> k <> " not found") Right . lookup k++instance Coordinate r => IpeRead (TextLabel r) where+  ipeRead (Element "text" ats chs)+    | lookup "type" ats == Just "label" = Label+                                       <$> allText chs+                                       <*> (lookup' "pos" ats >>= ipeReadText)+    | otherwise                         = Left "Not a Text label"+  ipeRead _                             = Left "textlabel: Expected element, found text"++++instance Coordinate r => IpeRead (MiniPage r) where+  ipeRead (Element "text" ats chs)+    | lookup "type" ats == Just "minipage" = MiniPage+                                          <$> allText chs+                                          <*> (lookup' "pos"   ats >>= ipeReadText)+                                          <*> (lookup' "width" ats >>= readCoordinate)+    | otherwise                            = Left "Not a MiniPage"+  ipeRead _                                = Left "MiniPage: Expected element, found text"+++instance Coordinate r => IpeRead (Image r) where+  ipeRead (Element "image" ats _) = Image () <$> (lookup' "rect" ats >>= ipeReadText)+  ipeRead _                       = Left "Image: Element expected, text found"++instance Coordinate r => IpeRead (IpeObject r) where+  ipeRead x = firstRight [ IpeUse       <$> ipeReadObject (Proxy :: Proxy IpeSymbol) r x+                         , IpePath      <$> ipeReadObject (Proxy :: Proxy Path)      r x+                         , IpeGroup     <$> ipeReadObject (Proxy :: Proxy Group)     r x+                         , IpeTextLabel <$> ipeReadObject (Proxy :: Proxy TextLabel) r x+                         , IpeMiniPage  <$> ipeReadObject (Proxy :: Proxy MiniPage)  r x+                         , IpeImage     <$> ipeReadObject (Proxy :: Proxy Image)     r x+                         ]+    where+      r = Proxy :: Proxy r++firstRight :: [Either ConversionError a] -> Either ConversionError a+firstRight = maybe (Left "No matching object") Right . firstOf (traverse._Right)+++instance Coordinate r => IpeRead (Group r) where+  ipeRead (Element "group" _ chs) = Group <$> mapM ipeRead chs+  ipeRead _                       = Left "ipeRead Group: expected Element, found Text"+++instance IpeRead LayerName where+  ipeRead (Element "layer" ats _) = LayerName <$> lookup' "name" ats+  ipeRead _                       = Left "layer: Expected element, found text"++instance IpeRead View where+  ipeRead (Element "view" ats _) = (\lrs a -> View (map LayerName $ T.words lrs) a)+                                <$> lookup' "layers" ats+                                <*> (lookup' "active" ats >>= ipeReadText)+  ipeRead _                      = Left "View Expected element, found text"+++-- TODO: this instance throws away all of our error collecting (and is pretty+-- slow/stupid since it tries parsing all children with all parsers)+instance Coordinate r => IpeRead (IpePage r) where+  ipeRead (Element "page" _ chs) = Right $ IpePage (readAll chs) (readAll chs) (readAll chs)+  ipeRead _                      = Left "page: Element expected, text found"+      -- withDef   :: b -> Either a b -> Either c b+      -- withDef d = either (const $ Right d) Right++      -- readLayers  = withDef ["alpha"] . readAll+      -- readViews   = withDef []        . readAll+      -- readObjects = withDef []        . readAll++-- | try reading everything as an a. Throw away whatever fails.+readAll   :: IpeRead a => [Node Text Text] -> [a]+readAll   = rights . map ipeRead+++instance Coordinate r => IpeRead (IpeFile r) where+  ipeRead (Element "ipe" _ chs) = case readAll chs of+                                    []  -> Left "Ipe: no pages found"+                                    pgs -> Right $ IpeFile Nothing [] (NE.fromList pgs)+  ipeRead _                     = Left "Ipe: Element expected, text found"++++testz :: Either ConversionError (IpeObject Double)+testz = (bimap (T.pack . show) id $ parse' defaultParseOptions testSym)+               >>= ipeRead -- Object (Proxy :: Proxy IpeSymbol) (Proxy :: Proxy Double)+++++++++++++++  -- ipeReadAttrs (Element _ ats _) =++instance Coordinate r => IpeReadText (PolyLine 2 () r) where   ipeReadText t = readPathOperations t >>= fromOps     where-      fromOps (MoveTo p:LineTo q:ops) = (\ps -> fromPoints $ [p,q] ++ ps)+      fromOps (MoveTo p:LineTo q:ops) = (\ps -> fromPoints' $ [p,q] ++ ps)                                      <$> validateAll "Expected LineTo p" _LineTo ops       fromOps _                       = Left "Expected MoveTo p:LineTo q:... " @@ -76,25 +442,24 @@     toEither = either' Left Right  -- This is a bit of a hack-instance IpeRead (PolyLine 2 ()) where+instance Coordinate r => IpeRead (PolyLine 2 () r) where   ipeRead (Element "path" ats ts) = ipeReadText . T.unlines . map unText $ ts                                     -- apparently hexpat already splits the text into lines   ipeRead _                       = Left "iperead: no polyline." +unText          :: Node t t1 -> t1 unText (Text t) = t-+unText _        = error "unText: element found, text expected" -instance IpeRead PathSegment where+instance Coordinate r => IpeRead (PathSegment r) where   ipeRead = fmap PolyLineSegment . ipeRead  testP :: B.ByteString testP = "<path stroke=\"black\">\n128 656 m\n224 768 l\n304 624 l\n432 752 l\n</path>" -testO :: Text-testO = "\n128 656 m\n224 768 l\n304 624 l\n432 752 l\n" -testPoly :: Either Text (PolyLine 2 () Double)-testPoly = fromIpeXML testP+-- testPoly :: Either Text (Path Double)+-- testPoly = fromIpeXML testP  -- ipeRead' :: [Element Text Text] -- ipeRead' = map ipeRead@@ -117,3 +482,5 @@ polylinesFromIpeFile = fmap readPolies . B.readFile   where     readPolies = either (const []) readPolyLines . parse' defaultParseOptions++--------------------------------------------------------------------------------
src/Data/Geometry/Ipe/Types.hs view
@@ -1,41 +1,44 @@-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-}-+{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE OverloadedStrings #-} module Data.Geometry.Ipe.Types where  import           Control.Applicative import           Control.Lens+import           Data.Monoid import           Data.Proxy import           Data.Vinyl -import           Linear.Affine((.-.), qdA)- import           Data.Ext-import           Data.Geometry.Ball-import           Data.Geometry.Point-import           Data.Geometry.Properties-import           Data.Geometry.Transformation(Matrix) import           Data.Geometry.Box(Rectangle)-import           Data.Geometry.Line+import           Data.Geometry.Point import           Data.Geometry.PolyLine+import           Data.Geometry.Polygon(SimplePolygon)+import           Data.Geometry.Properties+import           Data.Geometry.Transformation -import           Data.Geometry.Ipe.Attributes+import           Data.Maybe(mapMaybe)+import           Data.Singletons.TH(genDefunSymbols)++import           Data.Geometry.Ipe.Literal+import qualified Data.Geometry.Ipe.Attributes as AT+import           Data.Geometry.Ipe.Attributes hiding (Matrix) import           Data.Text(Text)-import           Data.TypeLevel.Filter+import           Text.XML.Expat.Tree(Node)  import           GHC.Exts -import           GHC.TypeLits -import qualified Data.Sequence as S+import qualified Data.List.NonEmpty as NE import qualified Data.Seq2     as S2  --------------------------------------------------------------------------------  +newtype LayerName = LayerName {_layerName :: Text } deriving (Show,Read,Eq,Ord,IsString)+ -------------------------------------------------------------------------------- -- | Image Objects @@ -45,6 +48,12 @@                      } deriving (Show,Eq,Ord) makeLenses ''Image +type instance NumType   (Image r) = r+type instance Dimension (Image r) = 2++instance Num r => IsTransformable (Image r) where+  transformBy t = over rect (transformBy t)+ -------------------------------------------------------------------------------- -- | Text Objects @@ -54,6 +63,18 @@ data MiniPage r = MiniPage Text (Point 2 r) r                  deriving (Show,Eq,Ord) +type instance NumType   (TextLabel r) = r+type instance Dimension (TextLabel r) = 2++type instance NumType   (MiniPage r) = r+type instance Dimension (MiniPage r) = 2++instance Num r => IsTransformable (TextLabel r) where+  transformBy t (Label txt p) = Label txt (transformBy t p)++instance Num r => IsTransformable (MiniPage r) where+  transformBy t (MiniPage txt p w) = MiniPage txt (transformBy t p) w+ width                  :: MiniPage t -> t width (MiniPage _ _ w) = w @@ -67,37 +88,53 @@                  deriving (Show,Eq,Ord) makeLenses ''IpeSymbol -type instance NumType (IpeSymbol r) = r+type instance NumType   (IpeSymbol r) = r+type instance Dimension (IpeSymbol r) = 2 +instance Num r => IsTransformable (IpeSymbol r) where+  transformBy t = over symbolPoint (transformBy t) ++ -- | Example of an IpeSymbol. I.e. A symbol that expresses that the size is 'large'-sizeSymbol :: SymbolAttribute Int Size-sizeSymbol = SymbolAttribute . IpeSize $ Named "large"+-- sizeSymbol :: Attributes (AttrMapSym1 r) (SymbolAttributes r)+-- sizeSymbol = attr SSize (IpeSize $ Named "large")  -------------------------------------------------------------------------------- -- | Paths  -- | Paths consist of Path Segments. PathSegments come in the following forms: data PathSegment r = PolyLineSegment        (PolyLine 2 () r)+                   | PolygonPath            (SimplePolygon () r)                      -- TODO-                   | PolygonPath                    | CubicBezierSegment     -- (CubicBezier 2 r)                    | QuadraticBezierSegment -- (QuadraticBezier 2 r)                    | EllipseSegment (Matrix 3 3 r)                    | ArcSegment                    | SplineSegment          -- (Spline 2 r)                    | ClosedSplineSegment    -- (ClosedSpline 2 r)-                   deriving (Show,Eq,Ord)+                   deriving (Show,Eq) makePrisms ''PathSegment +type instance NumType   (PathSegment r) = r+type instance Dimension (PathSegment r) = 2++instance Num r => IsTransformable (PathSegment r) where+  transformBy t (PolyLineSegment p) = PolyLineSegment $ transformBy t p+  transformBy t (PolygonPath p)     = PolygonPath $ transformBy t p+  transformBy _ _                   = error "transformBy: not implemented yet"++ -- | A path is a non-empty sequence of PathSegments. newtype Path r = Path { _pathSegments :: S2.ViewL1 (PathSegment r) }-                 deriving (Show,Eq,Ord)+                 deriving (Show,Eq) makeLenses ''Path --type instance NumType (Path r) = r+type instance NumType   (Path r) = r+type instance Dimension (Path r) = 2 +instance Num r => IsTransformable (Path r) where+  transformBy t (Path s) = Path $ fmap (transformBy t) s  -- | type that represents a path in ipe. data Operation r = MoveTo (Point 2 r)@@ -112,236 +149,264 @@                  deriving (Eq, Show) makePrisms ''Operation - ----------------------------------------------------------------------------------- | Group Attributes+-- * Attribute Mapping --- | Now that we know what a Path is we can define the Attributes of a Group.-type family GroupAttrElf (s :: GroupAttributeUniverse) (r :: *) :: * where-  GroupAttrElf Clip r = Path r -- strictly we event want this to be a closed path I guess -newtype GroupAttribute r s = GroupAttribute (GroupAttrElf s r)+-- | The mapping between the labels of the the attributes and the types of the+-- attributes with these labels. For example, the 'Matrix' label/attribute should+-- have a value of type 'Matrix 3 3 r'.+type family AttrMap (r :: *) (l :: AttributeUniverse) :: * where+  AttrMap r 'Layer          = LayerName+  AttrMap r AT.Matrix       = Matrix 3 3 r+  AttrMap r Pin             = PinType+  AttrMap r Transformations = TransformationTypes ------------------------------------------------------------------------------------ | Groups+  AttrMap r Stroke = IpeColor+  AttrMap r Pen    = IpePen r+  AttrMap r Fill   = IpeColor+  AttrMap r Size   = IpeSize r --- | To define groups, we need some poly kinded, type-level only, 2-tuples.-data (a :: ka) :.: (b :: kb)+  AttrMap r Dash     = IpeDash r+  AttrMap r LineCap  = Int+  AttrMap r LineJoin = Int+  AttrMap r FillRule = FillType+  AttrMap r Arrow    = IpeArrow r+  AttrMap r RArrow   = IpeArrow r+  AttrMap r Opacity  = IpeOpacity+  AttrMap r Tiling   = IpeTiling+  AttrMap r Gradient = IpeGradient --- | An IpeGroup can store IpeObjects. We distinguish the following different--- ipeObjects. The parameter t will cary additional information about the--- particular object. In particular, we will use it to keep track of the--- attributes each item has.------ Note: We will use this type on the type-level only! In particular, we will--- use it as a Label in a Vinyl Rec.-data IpeObjectType t = IpeGroup     t-                     | IpeImage     t-                     | IpeTextLabel t-                     | IpeMiniPage  t-                     | IpeUse       t-                     | IpePath      t-                     deriving (Show,Read,Eq)+  AttrMap r Clip = Path r -- strictly we event want this to be a closed path I guess --- | A group is essentially a hetrogenious list of IpeObjects. We represent a--- group by means of a Vinyl Rec.-type Group gt r = Rec (IpeObject r) gt+genDefunSymbols [''AttrMap] -type instance NumType (Group gt r) = r --- | This type family links each 'IpeObjectType' to the type (in Haskell) that--- we use to represent such an IpeObject. In principle we represent each object--- by means of an Ext (:+), in which the 'core' is one of the previously seen types--- (i.e. an Image, TextLabel, IpeSymbol, Path, etc), and the extra is a Vinyl record--- storing the attributes.------ The different ipe types use a different Universe to draw the labels form, to--- make sure that i.e. a Path only has attributes applicable to a Path.------ We also see the parameter of the IpeObjectType here: in all but the group case it is--- a type level list that tells which attributes the object has. In case of a group it is a--- poly-kinded pair (i.e. a `gt :.: gs`)  where the gt is a type level list that captures--- *ALL* type information of the objects stored in this group, and the *gs* is a type level--- list that specifies which attributes this group itself has.-type IpeObjectElF r (f :: IpeObjectType k) = IpeObjectValueElF r f :+ IpeObjectAttrElF r f+--------------------------------------------------------------------------------+-- | Groups and Objects -type family IpeObjectValueElF r (f :: IpeObjectType k) :: * where-  IpeObjectValueElF r (IpeGroup (gt :.: gs)) = Group gt r-  IpeObjectValueElF r (IpeImage is)          = Image r-  IpeObjectValueElF r (IpeTextLabel ts)      = TextLabel r-  IpeObjectValueElF r (IpeMiniPage mps)      = MiniPage r-  IpeObjectValueElF r (IpeUse  ss)           = IpeSymbol r-  IpeObjectValueElF r (IpePath ps)           = Path r+--------------------------------------------------------------------------------+-- | Group Attributes +-- -- | Now that we know what a Path is we can define the Attributes of a Group.+-- type family GroupAttrElf (r :: *) (s :: GroupAttributeUniverse) :: * where+--   GroupAttrElf r Clip = Path r -- strictly we event want this to be a closed path I guess -type family RevIpeObjectValueElF (t :: *) :: (k -> IpeObjectType k) where-  RevIpeObjectValueElF (Group gt r)   = IpeGroup-  RevIpeObjectValueElF (Image r)      = IpeImage-  RevIpeObjectValueElF (TextLabel r)  = IpeTextLabel-  RevIpeObjectValueElF (MiniPage r)   = IpeMiniPage-  RevIpeObjectValueElF (IpeSymbol r ) = IpeUse-  RevIpeObjectValueElF (Path r)       = IpePath+-- genDefunSymbols [''GroupAttrElf] +-- type GroupAttributes r = Attributes (GroupAttrElfSym1 r) '[ 'Clip]  +-- | A group is essentially a list of IpeObjects.+newtype Group r = Group { _groupItems :: [IpeObject r] }+                  deriving (Show,Eq) -type family IpeObjectAttrElF r (f :: IpeObjectType k) :: * where-  IpeObjectAttrElF r (IpeGroup (gt :.: gs)) = Rec (GroupAttribute     r) gs-  IpeObjectAttrElF r (IpeImage is)          = Rec (CommonAttribute    r) is-  IpeObjectAttrElF r (IpeTextLabel ts)      = Rec (TextLabelAttribute r) ts-  IpeObjectAttrElF r (IpeMiniPage mps)      = Rec (MiniPageAttribute  r) mps-  IpeObjectAttrElF r (IpeUse  ss)           = Rec (SymbolAttribute    r) ss-  IpeObjectAttrElF r (IpePath ps)           = Rec (PathAttribute      r) ps+type instance NumType   (Group r) = r+type instance Dimension (Group r) = 2 +instance Num r => IsTransformable (Group r) where+  transformBy t (Group s) = Group $ fmap (transformBy t) s  -type family IpeObjectAttrFunctorElF (f :: IpeObjectType k) :: (* -> u -> *) where-  IpeObjectAttrFunctorElF (IpeGroup (gt :.: gs)) = GroupAttribute-  IpeObjectAttrFunctorElF (IpeImage is)          = CommonAttribute-  IpeObjectAttrFunctorElF (IpeTextLabel ts)      = TextLabelAttribute-  IpeObjectAttrFunctorElF (IpeMiniPage mps)      = MiniPageAttribute-  IpeObjectAttrFunctorElF (IpeUse  ss)           = SymbolAttribute-  IpeObjectAttrFunctorElF (IpePath ps)           = PathAttribute+type family AttributesOf (t :: * -> *) :: [u] where+  AttributesOf Group     = GroupAttributes+  AttributesOf Image     = CommonAttributes+  AttributesOf TextLabel = CommonAttributes+  AttributesOf MiniPage  = CommonAttributes+  AttributesOf IpeSymbol = SymbolAttributes+  AttributesOf Path      = PathAttributes  +-- | Attributes' :: * -> [AttributeUniverse] -> *+type Attributes' r = Attributes (AttrMapSym1 r) --- TODO: Maybe split this TF into two TFS' one that determines the core type, the other--- that gives the Attribute wrapper type--- It would be nice if we could tell taht IpeObjecTELF was injective ...+type IpeAttributes g r = Attributes' r (AttributesOf g) --- | An ipe Object is then simply a thin wrapper around the IpeObjectELF type family.-newtype IpeObject r (fld :: IpeObjectType k) =-  IpeObject { _ipeObject :: IpeObjectElF r fld } -makeLenses ''IpeObject+-- | An IpeObject' is essentially the oject ogether with its attributes+type IpeObject' g r = g r :+ IpeAttributes g r -type instance NumType (IpeObject r t) = r+attributes :: Lens' (IpeObject' g r) (IpeAttributes g r)+attributes = extra ---------------------------------------------------------------------------------+data IpeObject r =+    IpeGroup     (IpeObject' Group     r)+  | IpeImage     (IpeObject' Image     r)+  | IpeTextLabel (IpeObject' TextLabel r)+  | IpeMiniPage  (IpeObject' MiniPage  r)+  | IpeUse       (IpeObject' IpeSymbol r)+  | IpePath      (IpeObject' Path      r)  -symb'' :: IpeObjectElF Int (IpeUse '[Size])-symb'' = Symbol origin "myLargesymbol"  :+ ( sizeSymbol :& RNil )+deriving instance (Show r) => Show (IpeObject r)+deriving instance (Eq r)   => Eq   (IpeObject r) -symb :: IpeObjectElF Int (IpeUse ('[] :: [SymbolAttributeUniverse]))-symb = Symbol origin "foo" :+ RNil+type instance NumType   (IpeObject r) = r+type instance Dimension (IpeObject r) = 2 -symb' :: IpeObject Int (IpeUse '[Size])-symb' = IpeObject symb''+makePrisms ''IpeObject+makeLenses ''Group -gr :: Group '[IpeUse '[Size]] Int-gr = symb' :& RNil+class ToObject i where+  ipeObject' :: i r -> IpeAttributes i r -> IpeObject r -grr :: IpeObjectElF Int (IpeGroup ('[IpeUse '[Size]]-                                   :.:-                                   ('[] :: [GroupAttributeUniverse])-                                  )-                        )-grr = gr :+ RNil+instance ToObject Group      where ipeObject' g a = IpeGroup     (g :+ a)+instance ToObject Image      where ipeObject' p a = IpeImage     (p :+ a)+instance ToObject TextLabel  where ipeObject' p a = IpeTextLabel (p :+ a)+instance ToObject MiniPage   where ipeObject' p a = IpeMiniPage  (p :+ a)+instance ToObject IpeSymbol  where ipeObject' s a = IpeUse       (s :+ a)+instance ToObject Path       where ipeObject' p a = IpePath      (p :+ a) +instance Num r => IsTransformable (IpeObject r) where+  transformBy t (IpeGroup i)     = IpeGroup     $ i&core %~ transformBy t+  transformBy t (IpeImage i)     = IpeImage     $ i&core %~ transformBy t+  transformBy t (IpeTextLabel i) = IpeTextLabel $ i&core %~ transformBy t+  transformBy t (IpeMiniPage i)  = IpeMiniPage  $ i&core %~ transformBy t+  transformBy t (IpeUse i)       = IpeUse       $ i&core %~ transformBy t+  transformBy t (IpePath i)      = IpePath      $ i&core %~ transformBy t -grrr :: IpeObject Int (IpeGroup ('[IpeUse '[Size]] :.:-                                      ('[] :: [GroupAttributeUniverse])-                                )-                      )-grrr = IpeObject grr  -points' :: forall gt r. Group gt r -> [Point 2 r]-points' = fmap (^.ipeObject.core.symbolPoint) . filterRec' -filterRec' :: forall gt r fld. (fld ~ IpeUse '[Size]) =>-              Rec (IpeObject r) gt -> [IpeObject r fld]-filterRec' = undefined--- filterRec' = filterRec (Proxy :: Proxy fld)--+commonAttributes :: Lens' (IpeObject r) (Attributes (AttrMapSym1 r) CommonAttributes)+commonAttributes = lens (Attrs . g) (\x (Attrs a) -> s x a)+  where+    select :: (CommonAttributes ⊆ AttributesOf g) =>+              Lens' (IpeObject' g r) (Rec (Attr (AttrMapSym1 r)) CommonAttributes)+    select = attributes.unAttrs.rsubset +    g (IpeGroup i)     = i^.select+    g (IpeImage i)     = i^.select+    g (IpeTextLabel i) = i^.select+    g (IpeMiniPage i)  = i^.select+    g (IpeUse i)       = i^.select+    g (IpePath i)      = i^.select +    s (IpeGroup i)     a = IpeGroup     $ i&select .~ a+    s (IpeImage i)     a = IpeImage     $ i&select .~ a+    s (IpeTextLabel i) a = IpeTextLabel $ i&select .~ a+    s (IpeMiniPage i)  a = IpeMiniPage  $ i&select .~ a+    s (IpeUse i)       a = IpeUse       $ i&select .~ a+    s (IpePath i)      a = IpePath      $ i&select .~ a  --------------------------------------------------------------------------------  --type XmlTree = Text---newtype Layer = Layer {_layerName :: Text } deriving (Show,Read,Eq,Ord,IsString)-- -- | The definition of a view -- make active layer into an index ?-data View = View { _layerNames      :: [Layer]-                 , _activeLayer     :: Layer+data View = View { _layerNames      :: [LayerName]+                 , _activeLayer     :: LayerName                  }           deriving (Eq, Ord, Show) makeLenses ''View +-- instance Default + -- | for now we pretty much ignore these data IpeStyle = IpeStyle { _styleName :: Maybe Text-                         , _styleData :: XmlTree+                         , _styleData :: Node Text Text                          }-              deriving (Eq,Show,Read,Ord)+              deriving (Eq,Show) makeLenses ''IpeStyle ++basicIpeStyle :: IpeStyle+basicIpeStyle = IpeStyle (Just "basic") (xmlLiteral [litFile|resources/basic.isy|])++ -- | The maybe string is the encoding data IpePreamble  = IpePreamble { _encoding     :: Maybe Text-                                , _preambleData :: XmlTree+                                , _preambleData :: Text                                 }                   deriving (Eq,Read,Show,Ord) makeLenses ''IpePreamble -type IpeBitmap = XmlTree+type IpeBitmap = Text    -------------------------------------------------------------------------------- -- Ipe Pages ------ -- | An IpePage is essentially a Group, together with a list of layers and a -- list of views.-data IpePage gs r = IpePage { _layers :: [Layer]-                            , _views  :: [View]-                            , _pages  :: Group gs r-                            }-              -- deriving (Eq, Show)+data IpePage r = IpePage { _layers  :: [LayerName]+                         , _views   :: [View]+                         , _content :: [IpeObject r]+                         }+              deriving (Eq,Show) makeLenses ''IpePage +-- | Creates a simple page with no views.+fromContent     :: [IpeObject r] -> IpePage r+fromContent obs = IpePage layers' [] obs+  where+    layers' = mapMaybe (^.commonAttributes.attrLens SLayer) obs +-- | A complete ipe file+data IpeFile r = IpeFile { _preamble :: Maybe IpePreamble+                         , _styles   :: [IpeStyle]+                         , _pages    :: NE.NonEmpty (IpePage r)+                         }+               deriving (Eq,Show)+makeLenses ''IpeFile +-- | Convenience function to construct an ipe file consisting of a single page.+singlePageFile   :: IpePage r -> IpeFile r+singlePageFile p = IpeFile Nothing [basicIpeStyle] (p NE.:| []) +-- | Create a single page ipe file from a list of IpeObjects+singlePageFromContent :: [IpeObject r] -> IpeFile r+singlePageFromContent = singlePageFile . fromContent --- pGr :: IpePage '[IpeUse '[Size]] Int-pGr = IpePage [] [] gr +-------------------------------------------------------------------------------- +-- | Takes and applies the ipe Matrix attribute of this item.+applyMatrix'              :: ( IsTransformable (i r)+                             , AT.Matrix ∈ AttributesOf i+                             , Dimension (i r) ~ 2, r ~ NumType (i r))+                          => IpeObject' i r -> IpeObject' i r+applyMatrix' o@(i :+ ats) = maybe o (\m -> transformBy (Transformation m) i :+ ats') mm+  where+    (mm,ats') = takeAttr (Proxy :: Proxy AT.Matrix) ats +-- | Applies the matrix to an ipe object if it has one.+applyMatrix                  :: Num r => IpeObject r -> IpeObject r+applyMatrix (IpeGroup i)     = IpeGroup . applyMatrix'+                             $ i&core.groupItems.traverse %~ applyMatrix+                             -- note that for a group we first (recursively)+                             -- apply the matrices, and then apply+                             -- the matrix of the group to its members.+applyMatrix (IpeImage i)     = IpeImage     $ applyMatrix' i+applyMatrix (IpeTextLabel i) = IpeTextLabel $ applyMatrix' i+applyMatrix (IpeMiniPage i)  = IpeMiniPage  $ applyMatrix' i+applyMatrix (IpeUse i)       = IpeUse       $ applyMatrix' i+applyMatrix (IpePath i)      = IpePath      $ applyMatrix' i -newtype Page r gs = Page { _unP :: IpePage gs r }-makeLenses ''Page+applyMatrices   :: Num r => IpeFile r -> IpeFile r+applyMatrices f = f&pages.traverse %~ applyMatricesPage +applyMatricesPage   :: Num r => IpePage r -> IpePage r+applyMatricesPage p = p&content.traverse %~ applyMatrix -ppGr = Page pGr -type IpePages gss r = Rec (Page r) gss-+--------------------------------------------------------------------------------  --- | A complete ipe file-data IpeFile gs r = IpeFile { _preamble :: Maybe IpePreamble-                            , _styles   :: [IpeStyle]-                            , _ipePages :: IpePages gs r-                            }-                  -- deriving (Eq,Show)---- ifP :: IpeFile '[ '[IpeUse '[Size]]] Int-ifP = IpeFile Nothing [] (ppGr :& RNil)+-- -- | Access a path as if it was a PolyLine+-- _PolyLine :: Prism' (IpeObject' Path r)+--                     (PolyLine 2 () r :+ IpeAttributes Path r)+-- _PolyLine = prism' build' access+--   where+--     build'  p         = p&core %~ Path . S2.l1Singleton . PolyLineSegment+--     access ~(p :+ a) = (:+ a) <$> p^?pathSegments.S2.headL1._PolyLineSegment -makeLenses ''IpeFile+-- -- | Access a path as if it was a SimplePolygon+-- _SimplePolygon :: Prism' (IpeObject' Path r)+--                          (SimplePolygon () r :+ IpeAttributes Path r)+-- _SimplePolygon = prism' build' access+--   where+--     build'  p         = p&core %~ Path . S2.l1Singleton . PolygonPath+--     access ~(p :+ a) = (:+ a) <$> p^?pathSegments.S2.headL1._PolygonPath
src/Data/Geometry/Ipe/Writer.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TupleSections #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE FlexibleInstances #-}@@ -6,32 +7,37 @@ module Data.Geometry.Ipe.Writer where  import           Control.Applicative hiding (Const(..))-import           Control.Lens((^.),(^..),(.~),(&), Prism', (#))+import           Control.Lens((^.),(^..),(.~),(&), to) import           Data.Ext+import           Data.Fixed import qualified Data.Foldable as F import           Data.Geometry.Ipe.Types import qualified Data.Geometry.Ipe.Types as IT import           Data.Geometry.LineSegment import           Data.Geometry.PolyLine+import           Data.Geometry.Polygon(SimplePolygon, outerBoundary) import qualified Data.Geometry.Transformation as GT import           Data.Geometry.Point+import           Data.Geometry.Box import           Data.Geometry.Vector import           Data.Maybe(catMaybes, mapMaybe, fromMaybe)-import           Data.Monoid+import           Data.Ratio+import           Data.Semigroup import           Data.Proxy import qualified Data.Traversable as Tr import           Data.Vinyl import           Data.Vinyl.Functor import           Data.Vinyl.TypeLevel-+import           Data.Singletons+import qualified Data.Geometry.Ipe.Attributes as IA import           Data.Geometry.Ipe.Attributes-import           GHC.Exts  import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as C import           Data.List(nub) import qualified Data.Seq2     as S2 import           Data.Text(Text)+import qualified Data.Text as Text  import           Text.XML.Expat.Tree import           Text.XML.Expat.Format(format')@@ -44,17 +50,21 @@  -- | Given a prism to convert something of type g into an ipe file, a file path, -- and a g. Convert the geometry and write it to file.-writeIpe        :: ( RecAll (Page r) gs IpeWrite-                   , IpeWriteText r-                   ) => Prism' (IpeFile gs r) g -> FilePath -> g -> IO ()-writeIpe p fp g = writeIpeFile (p # g) fp +-- writeIpe        :: ( RecAll (Page r) gs IpeWrite+--                    , IpeWriteText r+--                    ) => Prism' (IpeFile gs r) g -> FilePath -> g -> IO ()+-- writeIpe p fp g = writeIpeFile (p # g) fp+ -- | Write an IpeFiele to file.-writeIpeFile :: ( RecAll (Page r) gs IpeWrite-                , IpeWriteText r-                ) => IpeFile gs r -> FilePath -> IO ()-writeIpeFile = writeIpeFile'+writeIpeFile :: IpeWriteText r => FilePath -> IpeFile r -> IO ()+writeIpeFile = flip writeIpeFile' +-- | Creates a single page ipe file with the given page+writeIpePage    :: IpeWriteText r => FilePath -> IpePage r -> IO ()+writeIpePage fp = writeIpeFile fp . singlePageFile++ -- | Convert the input to ipeXml, and prints it to standard out in such a way -- that the copied text can be pasted into ipe as a geometry object. printAsIpeSelection :: IpeWrite t => t -> IO ()@@ -92,49 +102,24 @@   ipeWrite :: t -> Maybe (Node Text Text)  --- | Ipe write for Exts-ipeWriteExt             :: ( IpeWrite t-                           , RecAll a as IpeWriteText, AllSatisfy IpeAttrName as-                           ) => t :+ Rec a as -> Maybe (Node Text Text)-ipeWriteExt (x :+ arec) = ipeWrite x `mAddAtts` ipeWriteAttrs arec----- | For the types representing attribute values we can get the name/key to use--- when serializing to ipe.-class IpeAttrName a where-  attrName :: Proxy a -> Text---type Attr = (Text,Text)+instance IpeWriteText (Apply f at) => IpeWriteText (Attr f at) where+  ipeWriteText att = _getAttr att >>= ipeWriteText  -- | Functon to write all attributes in a Rec-ipeWriteAttrs :: ( RecAll f rs IpeWriteText-                 , AllSatisfy IpeAttrName rs-                 ) => Rec f rs -> [Attr]-ipeWriteAttrs rs = mapMaybe (\(x,my) -> (x,) <$> my) $ zip (writeAttrNames  rs)-                                                           (writeAttrValues rs)-+ipeWriteAttrs           :: ( AllSatisfy IpeAttrName rs+                           , RecAll (Attr f) rs IpeWriteText+                           ) => IA.Attributes f rs -> [(Text,Text)]+ipeWriteAttrs (Attrs r) = catMaybes . recordToList $ zipRecsWith f (writeAttrNames  r)+                                                                   (writeAttrValues r)+  where+    f (Const n) (Const mv) = Const $ (n,) <$> mv  -- | Writing the attribute values-writeAttrValues :: RecAll f rs IpeWriteText => Rec f rs -> [Maybe Text]-writeAttrValues = recordToList-                . rmap (\(Compose (Dict x)) -> Const $ ipeWriteText x)+writeAttrValues :: RecAll f rs IpeWriteText => Rec f rs -> Rec (Const (Maybe Text)) rs+writeAttrValues = rmap (\(Compose (Dict x)) -> Const $ ipeWriteText x)                 . reifyConstraint (Proxy :: Proxy IpeWriteText) --- | Writing Attribute names-writeAttrNames           :: AllSatisfy IpeAttrName rs => Rec f rs -> [Text]-writeAttrNames RNil      = []-writeAttrNames (x :& xs) = write'' x : writeAttrNames xs-  where-    write''   :: forall f s. IpeAttrName s => f s -> Text-    write'' _ = attrName (Proxy :: Proxy s) --- | Function that states that all elements in xs satisfy a given constraint c-type family AllSatisfy (c :: k -> Constraint) (xs :: [k]) :: Constraint where-  AllSatisfy c '[] = ()-  AllSatisfy c (x ': xs) = (c x, AllSatisfy c xs)-- instance IpeWriteText Text where   ipeWriteText = Just @@ -155,12 +140,19 @@ instance IpeWriteText Int where   ipeWriteText = writeByShow +instance HasResolution p => IpeWriteText (Fixed p) where+  ipeWriteText = writeByShow +-- | This instance converts the ratio to a Pico, and then displays that.+instance Integral a => IpeWriteText (Ratio a) where+  ipeWriteText = ipeWriteText . f . fromRational . toRational+    where+      f :: Pico -> Pico+      f = id+ writeByShow :: Show t => t -> Maybe Text writeByShow = ipeWriteText . T.pack . show -- unwords' :: [Maybe Text] -> Maybe Text unwords' = fmap T.unwords . sequence @@ -178,10 +170,43 @@   ipeWriteText (Named t)  = ipeWriteText t   ipeWriteText (Valued v) = ipeWriteText v +instance IpeWriteText TransformationTypes where+  ipeWriteText Affine       = Just "affine"+  ipeWriteText Rigid        = Just "rigid"+  ipeWriteText Translations = Just "translations"++instance IpeWriteText PinType where+  ipeWriteText No         = Nothing+  ipeWriteText Yes        = Just "yes"+  ipeWriteText Horizontal = Just "h"+  ipeWriteText Vertical   = Just "v"+ deriving instance IpeWriteText r => IpeWriteText (IpeSize  r) deriving instance IpeWriteText r => IpeWriteText (IpePen   r) deriving instance IpeWriteText IpeColor +instance IpeWriteText r => IpeWriteText (IpeDash r) where+  ipeWriteText (DashNamed t) = Just t+  ipeWriteText (DashPattern xs x) = (\ts t -> mconcat [ "["+                                                      , Text.intercalate " " ts+                                                      , "] ", t ])+                                    <$> Tr.mapM ipeWriteText xs+                                    <*> ipeWriteText x++instance IpeWriteText FillType where+  ipeWriteText Wind   = Just "wind"+  ipeWriteText EOFill = Just "eofill"++instance IpeWriteText r => IpeWriteText (IpeArrow r) where+  ipeWriteText (IpeArrow n s) = (\n' s' -> n' <> "/" <> s') <$> ipeWriteText n+                                                            <*> ipeWriteText s++instance IpeWriteText r => IpeWriteText (Path r) where+  ipeWriteText = fmap concat' . Tr.sequence . fmap ipeWriteText . _pathSegments+    where+      concat' = F.foldr1 (\t t' -> t <> "\n" <> t')++ -------------------------------------------------------------------------------- instance IpeWriteText r => IpeWrite (IpeSymbol r) where   ipeWrite (Symbol p n) = f <$> ipeWriteText p@@ -190,39 +215,11 @@                            , ("name", n)                            ] [] -instance IpeWriteText (SymbolAttrElf rs r) => IpeWriteText (SymbolAttribute r rs) where-  ipeWriteText (SymbolAttribute x) = ipeWriteText x+-- instance IpeWriteText (SymbolAttrElf rs r) => IpeWriteText (SymbolAttribute r rs) where+--   ipeWriteText (SymbolAttribute x) = ipeWriteText x  --- CommonAttributeUnivers-instance IpeAttrName Layer           where attrName _ = "layer"-instance IpeAttrName Matrix          where attrName _ = "matrix"-instance IpeAttrName Pin             where attrName _ = "pin"-instance IpeAttrName Transformations where attrName _ = "transformations" --- IpeSymbolAttributeUniversre-instance IpeAttrName SymbolStroke where attrName _ = "stroke"-instance IpeAttrName SymbolFill   where attrName _ = "fill"-instance IpeAttrName SymbolPen    where attrName _ = "pen"-instance IpeAttrName Size         where attrName _ = "size"---- PathAttributeUniverse-instance IpeAttrName Stroke     where attrName _ = "stroke"-instance IpeAttrName Fill       where attrName _ = "fill"-instance IpeAttrName Dash       where attrName _ = "dash"-instance IpeAttrName Pen        where attrName _ = "pen"-instance IpeAttrName LineCap    where attrName _ = "cap"-instance IpeAttrName LineJoin   where attrName _ = "join"-instance IpeAttrName FillRule   where attrName _ = "fillrule"-instance IpeAttrName Arrow      where attrName _ = "arrow"-instance IpeAttrName RArrow     where attrName _ = "rarrow"-instance IpeAttrName Opacity    where attrName _ = "opacity"-instance IpeAttrName Tiling     where attrName _ = "tiling"-instance IpeAttrName Gradient   where attrName _ = "gradient"---- GroupAttributeUniverse-instance IpeAttrName Clip     where attrName _ = "clip"- --------------------------------------------------------------------------------  instance IpeWriteText r => IpeWriteText (GT.Matrix 3 3 r) where@@ -251,53 +248,92 @@     (p : rest) -> unlines' . map ipeWriteText $ MoveTo p : map LineTo rest     -- the polyline type guarantees that there is at least one point +instance IpeWriteText r => IpeWriteText (SimplePolygon () r) where+  ipeWriteText pg = case pg^..outerBoundary.to F.toList.Tr.traverse.core of+    (p : rest) -> unlines' . map ipeWriteText $ MoveTo p : map LineTo rest ++ [ClosePath]+    _          -> Nothing+    -- TODO: We are not really guaranteed that there is at least one point, it would+    -- be nice if the type could guarantee that.  instance IpeWriteText r => IpeWriteText (PathSegment r) where   ipeWriteText (PolyLineSegment p) = ipeWriteText p+  ipeWriteText (PolygonPath     p) = ipeWriteText p   ipeWriteText (EllipseSegment  m) = ipeWriteText $ Ellipse m -instance IpeWriteText (PathAttrElf rs r) => IpeWriteText (PathAttribute r rs) where-  ipeWriteText (PathAttribute x) = ipeWriteText x- instance IpeWriteText r => IpeWrite (Path r) where-  ipeWrite (Path segs) = (\t -> Element "path" [] [Text t]) <$> mt-    where-      concat' = F.foldr1 (\t t' -> t <> "\n" <> t')-      mt      = fmap concat' . Tr.sequence . fmap ipeWriteText $ segs+  ipeWrite p = (\t -> Element "path" [] [Text t]) <$> ipeWriteText p  --------------------------------------------------------------------------------  -instance ( IpeObjectElF r fld  ~ (g :+ Rec f ats)+instance (IpeWriteText r) => IpeWrite (Group r) where+  ipeWrite (Group gs) = case mapMaybe ipeWrite gs of+                          [] -> Nothing+                          ns -> (Just $ Element "group" [] ns)+++instance ( AllSatisfy IpeAttrName rs+         , RecAll (Attr f) rs IpeWriteText          , IpeWrite g-         , RecAll f ats IpeWriteText, AllSatisfy IpeAttrName ats-         ) => IpeWrite (IpeObject r fld) where-  ipeWrite (IpeObject (g :+ ats)) = ipeWrite g `mAddAtts` ipeWriteAttrs ats+         ) => IpeWrite (g :+ IA.Attributes f rs) where+  ipeWrite (g :+ ats) = ipeWrite g `mAddAtts` ipeWriteAttrs ats  +instance IpeWriteText r => IpeWrite (MiniPage r) where+  ipeWrite (MiniPage t p w) = (\pt wt ->+                              Element "text" [ ("pos", pt)+                                             , ("type", "minipage")+                                             , ("width", wt)+                                             ] [Text t]+                              ) <$> ipeWriteText p+                                <*> ipeWriteText w++instance IpeWriteText r => IpeWrite (Image r) where+  ipeWrite (Image d (Box a b)) = (\dt p q ->+                                   Element "image" [("rect", p <> " " <> q)] [Text dt]+                                 )+                               <$> ipeWriteText d+                               <*> ipeWriteText (a^.core.(to getMin))+                               <*> ipeWriteText (b^.core.(to getMax))++-- TODO: Replace this one with s.t. that writes the actual image payload+instance IpeWriteText () where+  ipeWriteText () = Nothing++instance IpeWriteText r => IpeWrite (TextLabel r) where+  ipeWrite (Label t p) = (\pt ->+                         Element "text" [("pos", pt)+                                        ,("type", "label")+                                        ] [Text t]+                         ) <$> ipeWriteText p+++instance (IpeWriteText r) => IpeWrite (IpeObject r) where+    ipeWrite (IpeGroup     g) = ipeWrite g+    ipeWrite (IpeImage     i) = ipeWrite i+    ipeWrite (IpeTextLabel l) = ipeWrite l+    ipeWrite (IpeMiniPage  m) = ipeWrite m+    ipeWrite (IpeUse       s) = ipeWrite s+    ipeWrite (IpePath      p) = ipeWrite p++ ipeWriteRec :: RecAll f rs IpeWrite => Rec f rs -> [Node Text Text] ipeWriteRec = catMaybes . recordToList             . rmap (\(Compose (Dict x)) -> Const $ ipeWrite x)             . reifyConstraint (Proxy :: Proxy IpeWrite) -instance RecAll (IpeObject r) gt IpeWrite => IpeWrite (Group gt r) where-  -- basically the same implementation as ipeWriteAttrs: convert the rec to a Rec Const-  -- then turn that into a list. If the list is non-empty we construct a new group element.-  ipeWrite = fmap (Element "group" [])-           . wrap . ipeWriteRec-    where-      wrap [] = Nothing-      wrap xs = Just xs -instance IpeWriteText (GroupAttrElf rs r) => IpeWriteText (GroupAttribute r rs) where-  ipeWriteText (GroupAttribute x) = ipeWriteText x+-- instance IpeWriteText (GroupAttrElf rs r) => IpeWriteText (GroupAttribute r rs) where+--   ipeWriteText (GroupAttribute x) = ipeWriteText x   -------------------------------------------------------------------------------- -instance IpeWrite IT.Layer where-  ipeWrite (IT.Layer l) = Just $ Element "layer" [("name", l)] []+deriving instance IpeWriteText LayerName +instance IpeWrite LayerName where+  ipeWrite (LayerName n) = Just $ Element "layer" [("name",n)] []+ instance IpeWrite View where   ipeWrite (View lrs act) = Just $ Element "view" [ ("layers", ls)                                                   , ("active", _layerName act)@@ -305,41 +341,36 @@     where       ls = T.unwords .  map _layerName $ lrs -instance IpeWrite (Group gs r) => IpeWrite (IpePage gs r) where-  ipeWrite (IpePage lrs vs pgs) = Just . Element "page" [] . catMaybes . concat $+instance (IpeWriteText r)  => IpeWrite (IpePage r) where+  ipeWrite (IpePage lrs vs objs) = Just .+                                  Element "page" [] . catMaybes . concat $                                   [ map ipeWrite lrs                                   , map ipeWrite vs-                                  , [ipeWrite pgs]+                                  , map ipeWrite objs                                   ] -instance RecAll (Page r) gs IpeWrite => IpeWrite (IpeFile gs r) where-  ipeWrite (IpeFile p s pgs) = Just $ Element "ipe" ipeAtts chs-    where-    ipeAtts = [("version","70005"),("creator", "HGeometry")]-    -- TODO: Add preamble and styles-    chs = ipeWriteRec pgs +instance IpeWrite IpeStyle where+  ipeWrite (IpeStyle _ xml) = Just xml --------------------------------------------------------------------------------- -type Atts = [(Text,Text)]+instance IpeWrite IpePreamble where+  ipeWrite (IpePreamble _ latex) = Just $ Element "preamble" [] [Text latex]+  -- TODO: I probably want to do something with the encoding .... -ipeWritePolyLines     :: IpeWriteText r-                      => [(PolyLine 2 () r, Atts)] -> Node Text Text-ipeWritePolyLines pls = Element "ipe" ipeAtts [Element "page" [] chs]-  where-    chs     = layers pls ++ mapMaybe f pls-    ipeAtts = [("version","70005"),("creator", "HGeometry 0.4.0.0")]+instance (IpeWriteText r) => IpeWrite (IpeFile r) where+  ipeWrite (IpeFile mp ss pgs) = Just $ Element "ipe" ipeAtts chs+    where+      ipeAtts = [("version","70005"),("creator", "HGeometry")]+      chs = mconcat [ catMaybes [mp >>= ipeWrite]+                    , mapMaybe ipeWrite ss+                    , mapMaybe ipeWrite . F.toList $ pgs+                    ] -    f (pl,ats) = ipeWrite (mkPath pl) `mAddAtts` ats-    mkPath     = Path . S2.l1Singleton . PolyLineSegment-    layers     = map mkLayer . nub . mapMaybe (lookup "layer" . snd)-    mkLayer n  = Element "layer" [("name",n)] []  -writePolyLineFile :: IpeWriteText r => FilePath -> [(PolyLine 2 () r, Atts)] -> IO ()-writePolyLineFile fp = B.writeFile fp . format' . ipeWritePolyLines +--------------------------------------------------------------------------------  instance (IpeWriteText r, IpeWrite p) => IpeWrite (PolyLine 2 p r) where   ipeWrite p = ipeWrite path@@ -347,11 +378,12 @@       path = fromPolyLine $ p & points.Tr.traverse.extra .~ ()       -- TODO: Do something with the p's +fromPolyLine :: PolyLine 2 () r -> Path r fromPolyLine = Path . S2.l1Singleton . PolyLineSegment   instance (IpeWriteText r) => IpeWrite (LineSegment 2 p r) where-  ipeWrite (LineSegment p q) = ipeWrite . fromPolyLine . fromPoints . map (^.core) $ [p,q]+  ipeWrite (LineSegment' p q) = ipeWrite . fromPolyLine . fromPoints . map (extra .~ ()) $ [p,q]   instance IpeWrite () where@@ -387,21 +419,17 @@   -testPoly :: PolyLine 2 () Double-testPoly = fromPoints [origin, point2 0 10, point2 10 10, point2 100 100]+-- testPoly :: PolyLine 2 () Double+-- testPoly = fromPoints' [origin, point2 0 10, point2 10 10, point2 100 100]    -testWriteUse :: Maybe (Node Text Text)-testWriteUse = ipeWriteExt sym-  where-    sym :: IpeSymbol Double :+ (Rec (SymbolAttribute Double) [Size, SymbolStroke])-    sym = Symbol origin "mark" :+ (  SymbolAttribute (IpeSize  $ Named "normal")-                                  :& SymbolAttribute (IpeColor $ Named "green")-                                  :& RNil-                                  )----foo = ipeWrite grrr+-- testWriteUse :: Maybe (Node Text Text)+-- testWriteUse = ipeWriteExt sym+--   where+--     sym :: IpeSymbol Double :+ (Rec (SymbolAttribute Double) [Size, SymbolStroke])+--     sym = Symbol origin "mark" :+ (  SymbolAttribute (IpeSize  $ Named "normal")+--                                   :& SymbolAttribute (IpeColor $ Named "green")+--                                   :& RNil+--                                   )
src/Data/Geometry/Line.hs view
@@ -1,11 +1,15 @@-module Data.Geometry.Line(module I+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Data.Geometry.Line( module Data.Geometry.Line.Internal                          ) where -import Data.Geometry.Line.Internal as I+import Data.Geometry.Line.Internal import Data.Geometry.LineSegment-import           Data.Geometry.Transformation-import           Data.Geometry.Vector-+import Data.Geometry.Box+import Data.Geometry.Properties+import Data.Geometry.Point+import Data.Geometry.Boundary+import Data.Geometry.Transformation+import Data.Geometry.Vector  -- | Lines are transformable, via line segments instance (Num r, AlwaysTruePFT d) => IsTransformable (Line d r) where@@ -13,3 +17,23 @@     where       toLineSegment' :: (Num r, Arity d) => Line d r -> LineSegment d () r       toLineSegment' = toLineSegment+++type instance IntersectionOf (Line 2 r) (Boundary (Rectangle p r)) =+  [ NoIntersection, Point 2 r, (Point 2 r, Point 2 r) , LineSegment 2 () r]+++-- instance (Eq r, Fractional r)+--          => (Line 2 r) `IsIntersectableWith` (Boundary (Rectangle p r)) where+--   nonEmptyIntersection = defaultNonEmptyIntersection++--   _    `intersect` (Boundary Empty) = coRec NoIntersection+--   line `intersect` (Boundary rect)  = error "TODO"+--     where+--       (t,r,b,l) = sides' rect++--       ints = map (line `intersect`) [t,r,b,l]+++type instance IntersectionOf (Line 2 r) (Rectangle p r) =+  [ NoIntersection, Point 2 r, LineSegment 2 () r]
src/Data/Geometry/Line/Internal.hs view
@@ -1,21 +1,17 @@-{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TemplateHaskell  #-}+{-# LANGUAGE ScopedTypeVariables  #-} {-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE DeriveFunctor  #-} module Data.Geometry.Line.Internal where -import           Control.Applicative import           Control.Lens-import           Data.Ext import qualified Data.Foldable as F-import           Data.Geometry.Box-import           Data.Geometry.Interval import           Data.Geometry.Point import           Data.Geometry.Properties import           Data.Geometry.Vector-import           Linear.Affine(Affine(..))-import           Linear.Vector((*^))-+import           Data.Ord(comparing)+import qualified Data.Traversable as T+import           Data.Vinyl+import           Frames.CoRec  -------------------------------------------------------------------------------- -- * d-dimensional Lines@@ -27,9 +23,14 @@                      } makeLenses ''Line -deriving instance (Show r, Arity d) => Show    (Line d r)-deriving instance Arity d           => Functor (Line d)+instance (Show r, Arity d) => Show (Line d r) where+  show (Line p v) = concat [ "Line (", show p, ") (", show v, ")" ]+deriving instance (Eq r,   Arity d) => Eq            (Line d r)+deriving instance Arity d           => Functor       (Line d)+deriving instance Arity d           => F.Foldable    (Line d)+deriving instance Arity d           => T.Traversable (Line d) + type instance Dimension (Line d r) = d type instance NumType   (Line d r) = r @@ -45,8 +46,9 @@ horizontalLine   :: Num r => r -> Line 2 r horizontalLine y = Line (point2 0 y) (v2 1 0) -perpendicularTo                          :: Num r => Line 2 r -> Line 2 r-perpendicularTo (Line p (Vector2 vx vy)) = Line p (v2 (-vy) vx)+-- | Given a line l with anchor point p, get the line perpendicular to l that also goes through p.+perpendicularTo                           :: Num r => Line 2 r -> Line 2 r+perpendicularTo (Line p ~(Vector2 vx vy)) = Line p (v2 (-vy) vx)   @@ -83,20 +85,21 @@   -- TODO: Maybe use a specialize pragma for 2D with an implementation using ccw  +-- | The intersection of two lines is either: NoIntersection, a point or a line.+type instance IntersectionOf (Line 2 r) (Line 2 r) = [ NoIntersection+                                                     , Point 2 r+                                                     , Line 2 r+                                                     ]  instance (Eq r, Fractional r) => (Line 2 r) `IsIntersectableWith` (Line 2 r) where -  data Intersection (Line 2 r) (Line 2 r) = SameLine             !(Line 2 r)-                                          | LineLineIntersection !(Point 2 r)-                                          | ParallelLines -- ^ No intersection-                                            deriving (Show) -  nonEmptyIntersection ParallelLines = False-  nonEmptyIntersection _             = True+  nonEmptyIntersection = defaultNonEmptyIntersection -  l@(Line p (Vector2 ux uy)) `intersect` m@(Line q v@(Vector2 vx vy))-      | areParallel = if q `onLine` l then SameLine l else ParallelLines-      | otherwise   = LineLineIntersection r+  l@(Line p ~(Vector2 ux uy)) `intersect` (Line q ~v@(Vector2 vx vy))+      | areParallel = if q `onLine` l then coRec l+                                      else coRec NoIntersection+      | otherwise   = coRec r     where       r = q .+^ alpha *^ v @@ -110,15 +113,19 @@       Point2 px py = p       Point2 qx qy = q -instance (Eq r, Fractional r) => Eq (Intersection (Line 2 r) (Line 2 r)) where-  (SameLine l)             == (SameLine m)             = case (l `intersect` m) of-                                                           SameLine _ -> True-                                                           _          -> False-  (LineLineIntersection p) == (LineLineIntersection q) = p == q-  ParallelLines            == ParallelLines            = True-  _                        == _                        = False+-- | Squared distance from point p to line l+sqDistanceTo   :: (Fractional r, Arity d) => Point d r -> Line d r -> r+sqDistanceTo p = fst . sqDistanceToArg p  +-- | The squared distance between the point p and the line l, and the point m+-- realizing this distance.+sqDistanceToArg              :: (Fractional r, Arity d)+                             => Point d r -> Line d r -> (r, Point d r)+sqDistanceToArg p (Line q v) = let u = q .-. p+                                   t = (-1 * (u `dot` v)) / (v `dot` v)+                                   m = q .+^ (v ^* t)+                               in (qdA m p, m)  -------------------------------------------------------------------------------- -- * Supporting Lines@@ -129,3 +136,57 @@  instance HasSupportingLine (Line d r) where   supportingLine = id++--------------------------------------------------------------------------------+-- * Convenience functions on Two dimensional lines++-- | Create a line from the linear function ax + b+fromLinearFunction     :: Num r => r -> r -> Line 2 r+fromLinearFunction a b = Line (point2 0 b) (v2 1 a)++-- | get values a,b s.t. the input line is described by y = ax + b.+-- returns Nothing if the line is vertical+toLinearFunction                             :: forall r. (Fractional r, Eq r)+                                             => Line 2 r -> Maybe (r,r)+toLinearFunction l@(Line _ ~(Vector2 vx vy)) = match (l `intersect` verticalLine (0 :: r)) $+       (H $ \NoIntersection -> Nothing)    -- l is a vertical line+    :& (H $ \(Point2 _ b)   -> Just (vy / vx,b))+    :& (H $ \_              -> Nothing)    -- l is a vertical line (through x=0)+    :& RNil++-- | Result of a side test+data SideTest = Below | On | Above deriving (Show,Read,Eq,Ord)++-- | Given a point q and a line l, compute to which side of l q lies. For+-- vertical lines the left side of the line is interpeted as below.+--+-- >>> point2 10 10 `onSide` (lineThrough origin $ point2 10 5)+-- Above+-- >>> point2 10 10 `onSide` (lineThrough origin $ point2 (-10) 5)+-- Above+-- >>> point2 5 5 `onSide` (verticalLine 10)+-- Below+-- >>> point2 5 5 `onSide` (lineThrough origin $ point2 (-3) (-3))+-- On+onSide                :: (Ord r, Num r) => Point 2 r -> Line 2 r -> SideTest+q `onSide` (Line p v) = let r    =  p .+^ v+                            f z         = (z^.xCoord, -z^.yCoord)+                            minBy g a b = F.minimumBy (comparing g) [a,b]+                            maxBy g a b = F.maximumBy (comparing g) [a,b]+                        in case ccw (minBy f p r) (maxBy f p r) q of+                          CCW      -> Above+                          CW       -> Below+                          CoLinear -> On++++-- | Test if the query point q lies (strictly) above line l+liesAbove       :: (Ord r, Num r) => Point 2 r -> Line 2 r -> Bool+q `liesAbove` l = q `onSide` l == Above+++-- | Get the bisector between two points+bisector     :: Fractional r => Point 2 r -> Point 2 r -> Line 2 r+bisector p q = let v = q .-. p+                   h = p .+^ (v ^/ 2)+               in perpendicularTo (Line h v)
src/Data/Geometry/LineSegment.hs view
@@ -1,196 +1,265 @@-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE DeriveFunctor  #-}-module Data.Geometry.LineSegment where+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE UndecidableInstances #-}+module Data.Geometry.LineSegment( LineSegment+                                , pattern LineSegment+                                , pattern LineSegment'+                                , pattern ClosedLineSegment +                                , _SubLine+++                                , toLineSegment+                                , onSegment+                                , orderedEndPoints+                                , segmentLength+                                , sqDistanceToSeg, sqDistanceToSegArg+                                , flipSegment+                                ) where++import           Data.Ord(comparing)+import           Control.Arrow((&&&)) import           Control.Applicative import           Control.Lens+import           Data.Bifunctor+import           Data.Semigroup import           Data.Ext-import           Data.Geometry.Box+import           Data.Geometry.Box.Internal import           Data.Geometry.Interval-import           Data.Geometry.Point import           Data.Geometry.Line.Internal+import           Data.Geometry.Point import           Data.Geometry.Properties+import           Data.Geometry.SubLine import           Data.Geometry.Transformation import           Data.Geometry.Vector-import           Linear.Vector((*^))-import           Linear.Affine(Affine(..),distanceA)-import qualified Data.List as L-import           Data.Maybe(maybe)-import           Data.Ord(comparing)-import           Data.Semigroup+import           Data.Range+import           Data.Vinyl+import           Data.UnBounded+import           Frames.CoRec+import qualified Data.Foldable as F  -------------------------------------------------------------------------------- -- * d-dimensional LineSegments + -- | Line segments. LineSegments have a start and end point, both of which may--- contain additional data of type p.-newtype LineSegment d p r = LineSeg { _unLineSeg :: Interval p (Point d r) }-pattern LineSegment s t = LineSeg (Interval s t)+-- contain additional data of type p. We can think of a Line-Segment being defined as+--+--  data LineSegment d p r = LineSegment (EndPoint (Point d r :+ p))+--                                       (EndPoint (Point d r :+ p))+newtype LineSegment d p r = GLineSegment { _unLineSeg :: Interval p (Point d r)} +makeLenses ''LineSegment++-- | Pattern that essentially models the line segment as a:+--+-- data LineSegment d p r = LineSegment (EndPoint (Point d r :+ p))+--                                      (EndPoint (Point d r :+ p))+pattern LineSegment           :: EndPoint (Point d r :+ p)+                              -> EndPoint (Point d r :+ p)+                              -> LineSegment d p r+pattern LineSegment       s t = GLineSegment (Interval s t)++-- | Gets the start and end point, but forgetting if they are open or closed.+pattern LineSegment'          :: Point d r :+ p+                              -> Point d r :+ p+                              -> LineSegment d p r+pattern LineSegment'      s t <- ((^.start) &&& (^.end) -> (s,t))+++pattern ClosedLineSegment     :: Point d r :+ p+                              -> Point d r :+ p+                              -> LineSegment d p r+pattern ClosedLineSegment s t = GLineSegment (ClosedInterval s t)++type instance Dimension (LineSegment d p r) = d+type instance NumType   (LineSegment d p r) = r+ instance HasStart (LineSegment d p r) where   type StartCore  (LineSegment d p r) = Point d r   type StartExtra (LineSegment d p r) = p-  start = lens (_start . _unLineSeg) (\(LineSegment _ t) s -> LineSegment s t)+  start = unLineSeg.start  instance HasEnd (LineSegment d p r) where   type EndCore  (LineSegment d p r) = Point d r   type EndExtra (LineSegment d p r) = p-  end = lens (_end . _unLineSeg) (\(LineSegment s _) t -> LineSegment s t)+  end = unLineSeg.end +_SubLine :: (Fractional r, Eq r, Arity d) => Iso' (LineSegment d p r) (SubLine d p r)+_SubLine = iso segment2SubLine subLineToSegment++segment2SubLine    :: (Fractional r, Eq r, Arity d)+                   => LineSegment d p r -> SubLine d p r+segment2SubLine ss = SubLine l (Interval s e)+  where+    l = supportingLine ss+    f = flip toOffset l+    (Interval p q)  = ss^.unLineSeg++    s = p&unEndPoint.core %~ f+    e = q&unEndPoint.core %~ f++subLineToSegment    :: (Num r, Arity d) => SubLine d p r -> LineSegment d p r+subLineToSegment sl = let (Interval s' e') = (fixEndPoints sl)^.subRange+                          s = s'&unEndPoint %~ (^.extra)+                          e = e'&unEndPoint %~ (^.extra)+                      in LineSegment s e+ instance (Num r, Arity d) => HasSupportingLine (LineSegment d p r) where-  supportingLine (LineSegment (p :+ _) (q :+ _)) = lineThrough p q+  supportingLine s = lineThrough (s^.start.core) (s^.end.core) -deriving instance (Show r, Show p, Arity d) => Show (LineSegment d p r)+instance (Show r, Show p, Arity d) => Show (LineSegment d p r) where+  show ~(LineSegment p q) = concat ["LineSegment (", show p, ") (", show q, ")"]+ deriving instance (Eq r, Eq p, Arity d)     => Eq (LineSegment d p r)-deriving instance (Ord r, Ord p, Arity d)   => Ord (LineSegment d p r)+-- deriving instance (Ord r, Ord p, Arity d)   => Ord (LineSegment d p r) deriving instance Arity d                   => Functor (LineSegment d p)-type instance Dimension (LineSegment d p r) = d-type instance NumType   (LineSegment d p r) = r  instance PointFunctor (LineSegment d p) where-  pmap f (LineSegment s e) = LineSegment (f <$> s) (f <$> e)+  pmap f ~(LineSegment s e) = LineSegment (s&unEndPoint %~ first f)+                                          (e&unEndPoint %~ first f)  instance Arity d => IsBoxable (LineSegment d p r) where-  boundingBox l = boundingBoxList [l^.start.core, l^.end.core]+  boundingBox l = boundingBox (l^.start.core) <> boundingBox (l^.end.core)  instance (Num r, AlwaysTruePFT d) => IsTransformable (LineSegment d p r) where   transformBy = transformPointFunctor ----+instance Arity d => Bifunctor (LineSegment d) where+  bimap f g (GLineSegment i) = GLineSegment $ bimap f (fmap g) i    -- ** Converting between Lines and LineSegments +-- | Directly convert a line into a line segment. toLineSegment            :: (Monoid p, Num r, Arity d) => Line d r -> LineSegment d p r-toLineSegment (Line p v) = LineSegment (p       :+ mempty)-                                       (p .+^ v :+ mempty)+toLineSegment (Line p v) = ClosedLineSegment (p       :+ mempty)+                                             (p .+^ v :+ mempty)  -- *** Intersecting LineSegments +type instance IntersectionOf (LineSegment 2 p r) (LineSegment 2 p r) = [ NoIntersection+                                                                       , Point 2 r+                                                                       , LineSegment 2 p r+                                                                       ]++type instance IntersectionOf (LineSegment 2 p r) (Line 2 r) = [ NoIntersection+                                                              , Point 2 r+                                                              , LineSegment 2 p r+                                                              ]++ instance (Ord r, Fractional r) =>          (LineSegment 2 p r) `IsIntersectableWith` (LineSegment 2 p r) where+  nonEmptyIntersection = defaultNonEmptyIntersection -  data Intersection (LineSegment 2 p r) (LineSegment 2 p r) =-        OverlappingSegment         !(LineSegment 2 p r)-      | LineSegLineSegIntersection !(Point 2 r)-      | NoIntersection-      deriving (Show,Eq)+  a `intersect` b = match ((a^._SubLine) `intersect` (b^._SubLine)) $+         (H coRec)+      :& (H coRec)+      :& (H $ coRec . subLineToSegment)+      :& RNil -  nonEmptyIntersection NoIntersection = False-  nonEmptyIntersection _              = True -  a@(LineSegment p q) `intersect` b@(LineSegment s t) = case la `intersect` lb of-      SameLine _                                ->-          maybe NoIntersection OverlappingSegment $ overlap a b-      LineLineIntersection r | onBothSegments r -> LineSegLineSegIntersection r-      _                                         -> NoIntersection-    where-      la = supportingLine a-      lb = supportingLine b-      onBothSegments r = onSegment r a && onSegment r b- instance (Ord r, Fractional r) =>          (LineSegment 2 p r) `IsIntersectableWith` (Line 2 r) where-  data Intersection (LineSegment 2 p r) (Line 2 r) =-           LineContainsSegment !(LineSegment 2 p r)-         | LineLineSegmentIntersection !(Point 2 r)-         | NoLineLineSegmentIntersection-         deriving (Show,Eq)--  nonEmptyIntersection NoLineLineSegmentIntersection = False-  nonEmptyIntersection _                             = True--  s `intersect` l = case (supportingLine s) `intersect` l of-    SameLine _                               -> LineContainsSegment s-    LineLineIntersection p | p `onSegment` s -> LineLineSegmentIntersection p-    _                                        -> NoLineLineSegmentIntersection+  nonEmptyIntersection = defaultNonEmptyIntersection +  ~s@(LineSegment p q) `intersect` l = let f  = bimap (fmap Val) (const ())+                                           s' = LineSegment (p&unEndPoint %~ f)+                                                            (q&unEndPoint %~ f)+                                    in match ((s'^._SubLine) `intersect` (fromLine l)) $+         (H   coRec)+      :& (H $ coRec . fmap (_unUnBounded))+      :& (H $ const (coRec s))+      :& RNil  -- * Functions on LineSegments  -- | Test if a point lies on a line segment. ----- >>> (point2 1 0) `onSegment` (LineSegment (origin :+ ()) (point2 2 0 :+ ()))+-- >>> (point2 1 0) `onSegment` (ClosedLineSegment (origin :+ ()) (point2 2 0 :+ ())) -- True--- >>> (point2 1 1) `onSegment` (LineSegment (origin :+ ()) (point2 2 0 :+ ()))+-- >>> (point2 1 1) `onSegment` (ClosedLineSegment (origin :+ ()) (point2 2 0 :+ ())) -- False--- >>> (point2 5 0) `onSegment` (LineSegment (origin :+ ()) (point2 2 0 :+ ()))+-- >>> (point2 5 0) `onSegment` (ClosedLineSegment (origin :+ ()) (point2 2 0 :+ ())) -- False--- >>> (point2 (-1) 0) `onSegment` (LineSegment (origin :+ ()) (point2 2 0 :+ ()))+-- >>> (point2 (-1) 0) `onSegment` (ClosedLineSegment (origin :+ ()) (point2 2 0 :+ ())) -- False--- >>> (point2 1 1) `onSegment` (LineSegment (origin :+ ()) (point2 3 3 :+ ()))+-- >>> (point2 1 1) `onSegment` (ClosedLineSegment (origin :+ ()) (point2 3 3 :+ ())) -- True -- -- Note that the segments are assumed to be closed. So the end points lie on the segment. ----- >>> (point2 2 0) `onSegment` (LineSegment (origin :+ ()) (point2 2 0 :+ ()))+-- >>> (point2 2 0) `onSegment` (ClosedLineSegment (origin :+ ()) (point2 2 0 :+ ())) -- True--- >>> origin `onSegment` (LineSegment (origin :+ ()) (point2 2 0 :+ ()))+-- >>> origin `onSegment` (ClosedLineSegment (origin :+ ()) (point2 2 0 :+ ())) -- True -- -- -- This function works for arbitrary dimensons. ----- >>> (point3 1 1 1) `onSegment` (LineSegment (origin :+ ()) (point3 3 3 3 :+ ()))+-- >>> (point3 1 1 1) `onSegment` (ClosedLineSegment (origin :+ ()) (point3 3 3 3 :+ ())) -- True--- >>> (point3 1 2 1) `onSegment` (LineSegment (origin :+ ()) (point3 3 3 3 :+ ()))+-- >>> (point3 1 2 1) `onSegment` (ClosedLineSegment (origin :+ ()) (point3 3 3 3 :+ ())) -- False onSegment       :: (Ord r, Fractional r, Arity d)                 => Point d r -> LineSegment d p r -> Bool-p `onSegment` l = let s         = l^.start.core-                      t         = l^.end.core-                      inRange x = 0 <= x && x <= 1-                  in maybe False inRange $ scalarMultiple (p .-. s) (t .-. s)+p `onSegment` l = let s          = l^.start.core+                      t          = l^.end.core+                      inRange' x = 0 <= x && x <= 1+                  in maybe False inRange' $ scalarMultiple (p .-. s) (t .-. s)  --- | Compute the overlap between the two segments (if they overlap/intersect)-overlap     :: (Ord r, Fractional r, Arity d)-            => LineSegment d p r -> LineSegment d p r -> Maybe (LineSegment d p r)-overlap l m = mim >>= \im -> case il `intersect` im of-    IntervalIntersection (Interval s e) -> Just $ LineSegment (s^.extra) (e^.extra)-    NoOverlap                           -> Nothing+-- | The left and right end point (or left below right if they have equal x-coords)+orderedEndPoints   :: Ord r => LineSegment 2 p r -> (Point 2 r :+ p, Point 2 r :+ p)+orderedEndPoints s = if pc <= qc then (p, q) else (q,p)   where-    p = l^.start-    q = l^.end-    r = m^.start-    s = m^.end+    p@(pc :+ _) = s^.start+    q@(qc :+ _) = s^.end -    u = q^.core .-. p^.core -    -- lineseg l corresp to an interval from 0 1-    il = Interval (0 :+ p) (1 :+ q)+-- | Length of the line segment+segmentLength                     :: (Arity d, Floating r) => LineSegment d p r -> r+segmentLength ~(LineSegment' p q) = distanceA (p^.core) (q^.core)  -    -- let lambda x denote the scalar s.t. x = p + (lambda x) *^ u-    ---    -- lambda' computes lambda' and pairs it with the associated point.-    -- lambda  :: (Point d r :+ extra) -> Maybe (r :+ (Point d r :+ extra)-    lambda' x = (:+ x) <$> scalarMultiple u (x^.core .-. p^.core)+-- | Squared distance from the point to the Segment s. The same remark as for+-- the 'sqDistanceToSegArg' applies here.+sqDistanceToSeg   :: (Arity d, Fractional r, Ord r) => Point d r -> LineSegment d p r -> r+sqDistanceToSeg p = fst . sqDistanceToSegArg p -    -- lineseg m corresponds to an interval-    -- [min (lambda r, lambda s), max (lambda r, lambda s)]-    ---    -- mim denotes this interval, assuming it exists (i.e. that is,-    -- assuming r and s are indeed colinear with pq.-    mim = mapM lambda' [r, s] >>= (f . L.sortBy (comparing (^.core))) -    -- Make sure we have two elems, s.t. both r and s are colinear with pq-    f [a,b] = Just $ Interval a b-    f _     = Nothing+-- | Squared distance from the point to the Segment s, and the point on s+-- realizing it.  Note that if the segment is *open*, the closest point+-- returned may be one of the (open) end points, even though technically the+-- end point does not lie on the segment. (The true closest point then lies+-- arbitrarily close to the end point).+sqDistanceToSegArg     :: (Arity d, Fractional r, Ord r)+                       => Point d r -> LineSegment d p r -> (r, Point d r)+sqDistanceToSegArg p s = let m  = sqDistanceToArg p (supportingLine s)+                             xs = m : map (\(q :+ _) -> (qdA p q, q)) [s^.start, s^.end]+                         in   F.minimumBy (comparing fst)+                            . filter (flip onSegment s . snd) $ xs +-- | flips the start and end point of the segment+flipSegment   :: LineSegment d p r -> LineSegment d p r+flipSegment s = let p = s^.start+                    q = s^.end+                in (s&start .~ q)&end .~ p --- | The left and right end point (or left below right if they have equal x-coords)-orderedEndPoints   :: Ord r => LineSegment 2 p r -> (Point 2 r :+ p, Point 2 r :+ p)-orderedEndPoints s = if pc <= qc then (p, q) else (q,p)-  where-    p@(pc :+ _) = s^.start-    q@(qc :+ _) = s^.end+-- testSeg :: LineSegment 2 () Rational+-- testSeg = LineSegment (Open $ ext origin)  (Closed $ ext (point2 10 0)) +-- horL' :: Line 2 Rational+-- horL' = horizontalLine 0 --- | Length of the line segment-segmentLength                   :: (Arity d, Floating r) => LineSegment d p r -> r-segmentLength (LineSegment p q) = distanceA (p^.core) (q^.core)+-- testI = testSeg `intersect` horL'+++-- ff = bimap (fmap Val) (const ())++-- ss' = let (LineSegment p q) = testSeg in+--       LineSegment (p&unEndPoint %~ ff)+--                   (q&unEndPoint %~ ff)++-- ss'' = ss'^._SubLine
src/Data/Geometry/Point.hs view
@@ -1,35 +1,25 @@ {-# LANGUAGE ScopedTypeVariables  #-}-{-# LANGUAGE AutoDeriveTypeable #-} {-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE DeriveFunctor  #-} module Data.Geometry.Point where -import           Data.Proxy- import           Control.Applicative+import           Data.Monoid(mconcat) import           Control.Lens---- import Data.TypeLevel.Common-import           Data.Typeable-import           Data.Vinyl hiding (Nat)-import qualified Data.Vinyl as V-import           Data.Vinyl.Functor(Const(..))-import qualified Data.Vinyl.TypeLevel as TV--import           Data.Vinyl.TypeLevel hiding (Nat)+import           Data.Proxy +import qualified Data.Traversable as T+import qualified Data.Foldable as F import qualified Data.Vector.Fixed as FV--- import qualified Data.Vector.Fixed.Cont as C-+import qualified Data.List as L +import           Data.Ext import           Data.Geometry.Properties import           Data.Geometry.Vector import           GHC.TypeLits  import qualified Data.Geometry.Vector as Vec--import           Linear.Vector(Additive(..))-import           Linear.Affine hiding (Point(..), origin)+import qualified Data.CircularList as C+import qualified Data.CircularList.Util as CU  -------------------------------------------------------------------------------- @@ -48,17 +38,23 @@ -- | A d-dimensional point. newtype Point d r = Point { toVec :: Vector d r } -deriving instance (Show r, Arity d) => Show (Point d r)+instance (Show r, Arity d) => Show (Point d r) where+  show (Point (Vector v)) = mconcat [ "Point", show $ FV.length v , " "+                                    , show $ F.toList v+                                    ]+++ deriving instance (Eq r, Arity d)   => Eq (Point d r) deriving instance (Ord r, Arity d)  => Ord (Point d r) deriving instance Arity d           => Functor (Point d)+deriving instance Arity d           => F.Foldable (Point d)+deriving instance Arity d           => T.Traversable (Point d)   type instance NumType (Point d r) = r type instance Dimension (Point d r) = d -- instance Arity d =>  Affine (Point d) where   type Diff (Point d) = Vector d @@ -69,7 +65,7 @@ -- | Point representing the origin in d dimensions -- -- >>> origin :: Point 4 Int--- Point {toVec = Vector {_unV = fromList [0,0,0,0]}}+-- Point4 [0,0,0,0] origin :: (Arity d, Num r) => Point d r origin = Point $ pure 0 @@ -79,9 +75,9 @@ -- | Lens to access the vector corresponding to this point. -- -- >>> (point3 1 2 3) ^. vector--- Vector {_unV = fromList [1,2,3]}+-- Vector3 [1,2,3] -- >>> origin & vector .~ v3 1 2 3--- Point {toVec = Vector {_unV = fromList [1,2,3]}}+-- Point3 [1,2,3] vector :: Lens' (Point d r) (Vector d r) vector = lens toVec (const Point) @@ -101,9 +97,9 @@ -- >>> point3 1 2 3 ^. coord (C :: C 2) -- 2 -- >>> point3 1 2 3 & coord (C :: C 1) .~ 10--- Point {toVec = Vector {_unV = fromList [10,2,3]}}+-- Point3 [10,2,3] -- >>> point3 1 2 3 & coord (C :: C 3) %~ (+1)--- Point {toVec = Vector {_unV = fromList [1,2,4]}}+-- Point3 [1,2,4] coord   :: forall proxy i d r. (Index' (i-1) d, Arity d) => proxy i -> Lens' (Point d r) r coord _ = vector . Vec.element (Proxy :: Proxy (i-1)) @@ -126,6 +122,7 @@ -- 1 -- -- if we want.+pattern Point2       :: r -> r -> Point 2 r pattern Point2 x y   <- (_point2 -> (x,y))  -- | Similarly, we can write:@@ -137,12 +134,13 @@ --   in g myPoint -- :} -- 3+pattern Point3       :: r -> r -> r -> Point 3 r pattern Point3 x y z <- (_point3 -> (x,y,z))  -- | Construct a 2 dimensional point -- -- >>> point2 1 2--- Point {toVec = Vector {_unV = fromList [1,2]}}+-- Point2 [1,2] point2     :: r -> r -> Point 2 r point2 x y = Point $ v2 x y @@ -157,7 +155,7 @@ -- | Construct a 3 dimensional point -- -- >>> point3 1 2 3--- Point {toVec = Vector {_unV = fromList [1,2,3]}}+-- Point3 [1,2,3] point3       :: r -> r -> r -> Point 3 r point3 x y z = Point $ v3 x y z @@ -176,7 +174,7 @@ -- >>> point3 1 2 3 ^. xCoord -- 1 -- >>> point2 1 2 & xCoord .~ 10--- Point {toVec = Vector {_unV = fromList [10,2]}}+-- Point2 [10,2] xCoord :: (1 <=. d) => Lens' (Point d r) r xCoord = coord (C :: C 1) @@ -185,7 +183,7 @@ -- >>> point2 1 2 ^. yCoord -- 2 -- >>> point3 1 2 3 & yCoord %~ (+1)--- Point {toVec = Vector {_unV = fromList [1,3,3]}}+-- Point3 [1,3,3] yCoord :: (2 <=. d) => Lens' (Point d r) r yCoord = coord (C :: C 2) @@ -194,10 +192,12 @@ -- >>> point3 1 2 3 ^. zCoord -- 3 -- >>> point3 1 2 3 & zCoord %~ (+1)--- Point {toVec = Vector {_unV = fromList [1,2,4]}}+-- Point3 [1,2,4] zCoord :: (3 <=. d) => Lens' (Point d r) r zCoord = coord (C :: C 3) ++ -------------------------------------------------------------------------------- -- * Point Functors @@ -228,3 +228,120 @@        Vector2 ux uy = q .-. p        Vector2 vx vy = r .-. p        z             = ux * vy - uy * vx++-- | Sort the points arround the given point p in counter clockwise order with+-- respect to the rightward horizontal ray starting from p.  If two points q+-- and r are colinear with p, the closest one to p is reported first.+-- running time: O(n log n)+sortArround   :: (Ord r, Num r)+               => Point 2 r :+ p -> [Point 2 r :+ p] -> [Point 2 r :+ p]+sortArround c = L.sortBy (ccwCmpAround c)++-- sortArround       :: (Ord r, Num r)+--                   => Point 2 r :+ p -> [Point 2 r :+ p] -> [Point 2 r :+ p]+-- sortArround p pts = concatMap sortArround' [ topR, topL, bottomL, bottomR ]+--   where+--     (topL, topR, bottomL, bottomR) = partitionIntoQuadrants p pts+--     sortArround' = L.sortBy cmp++--     cmp q r = case ccw (p^.core) (q^.core) (r^.core) of+--                 CCW      -> LT+--                 CW       -> GT+--                 CoLinear -> qdA (p^.core) (q^.core) `compare` qdA (p^.core) (r^.core)++-- | Quadrants of two dimensional points. in CCW order+data Quadrant = TopRight | TopLeft | BottomLeft | BottomRight+              deriving (Show,Read,Eq,Ord,Enum,Bounded)++-- | Quadrants around point c; quadrants are closed on their "previous"+-- boundary (i..e the boundary with the previous quadrant in the CCW order),+-- open on next boundary. The origin itself is assigned the topRight quadrant+quadrantWith                   :: (Arity d, Ord r, 1 <=. d, 2 <=. d)+                               => Point d r :+ q -> Point d r :+ p -> Quadrant+quadrantWith (c :+ _) (p :+ _) = case ( (c^.xCoord) `compare` (p^.xCoord)+                                      , (c^.yCoord) `compare` (p^.yCoord) ) of+                                   (EQ, EQ) -> TopRight+                                   (LT, EQ) -> TopRight+                                   (LT, LT) -> TopRight+                                   (EQ, LT) -> TopLeft+                                   (GT, LT) -> TopLeft+                                   (GT, EQ) -> BottomLeft+                                   (GT, GT) -> BottomLeft+                                   (EQ, GT) -> BottomRight+                                   (LT, GT) -> BottomRight++-- | Quadrants with respect to the origin+quadrant :: (Arity d, Ord r, Num r, 1 <=. d, 2 <=. d) => Point d r :+ p -> Quadrant+quadrant = quadrantWith (ext origin)++-- | Given a center point c, and a set of points, partition the points into+-- quadrants around c (based on their x and y coordinates). The quadrants are+-- reported in the order topLeft, topRight, bottomLeft, bottomRight. The points+-- are in the same order as they were in the original input lists.+-- Points with the same x-or y coordinate as p, are "rounded" to above.+partitionIntoQuadrants       :: (Ord r, Arity d, 1 <=. d, 2 <=. d)+                             => Point d r :+ q+                             -> [Point d r :+ p]+                             -> ( [Point d r :+ p], [Point d r :+ p]+                                , [Point d r :+ p], [Point d r :+ p]+                                )+partitionIntoQuadrants c pts = (topL, topR, bottomL, bottomR)+  where+    (below',above')   = L.partition (on yCoord) pts+    (bottomL,bottomR) = L.partition (on xCoord) below'+    (topL,topR)       = L.partition (on xCoord) above'++    on l q       = q^.core.l < c^.core.l++-- | Counter clockwise ordering of the points around c. Points are ordered with+-- respect to the positive x-axis.+-- Points nearer to the center come before+-- points further away.+ccwCmpAround       :: (Num r, Ord r)+                   => Point 2 r :+ qc -> Point 2 r :+ p -> Point 2 r :+ q -> Ordering+ccwCmpAround c q r = case (quadrantWith c q `compare` quadrantWith c r) of+                       EQ -> case ccw (c^.core) (q^.core) (r^.core) of+                         CCW      -> LT+                         CW       -> GT+                         CoLinear -> qdA (c^.core) (q^.core)+                                     `compare`+                                     qdA (c^.core) (r^.core)+                       x -> x -- if the quadrant differs, use the order+                              -- specified by the quadrant.++-- | Clockwise ordering of the points around c. Points are ordered with+-- respect to the positive x-axis. Points nearer to the center come before+-- points further away.+cwCmpAround       :: (Num r, Ord r)+                  => Point 2 r :+ qc -> Point 2 r :+ p -> Point 2 r :+ q -> Ordering+cwCmpAround c q r = case (quadrantWith c q `compare` quadrantWith c r) of+                       EQ -> case ccw (c^.core) (q^.core) (r^.core) of+                         CCW      -> GT+                         CW       -> LT+                         CoLinear -> qdA (c^.core) (q^.core)+                                     `compare`+                                     qdA (c^.core) (r^.core)+                       LT -> GT+                       GT -> LT -- if the quadrant differs, use the order+                                -- specified by the quadrant.++++-- | Given a center c, a new point p, and a list of points ps, sorted in+-- counter clockwise order around c. Insert p into the cyclic order. The focus+-- of the returned cyclic list is the new point p.+--+-- running time: O(n)+insertIntoCyclicOrder   :: (Ord r, Num r)+                        => Point 2 r :+ q -> Point 2 r :+ p+                        -> C.CList (Point 2 r :+ p) -> C.CList (Point 2 r :+ p)+insertIntoCyclicOrder c = CU.insertOrdBy (ccwCmpAround c)+++-- | Squared Euclidean distance between two points+squaredEuclideanDist :: (Num r, Arity d) => Point d r -> Point d r -> r+squaredEuclideanDist = qdA++-- | Euclidean distance between two points+euclideanDist :: (Floating r, Arity d) => Point d r -> Point d r -> r+euclideanDist = distanceA
src/Data/Geometry/PolyLine.hs view
@@ -1,19 +1,23 @@ {-# LANGUAGE TemplateHaskell  #-} {-# LANGUAGE DeriveFunctor  #-}+{-# LANGUAGE UndecidableInstances  #-} module Data.Geometry.PolyLine where  import           Control.Applicative import           Control.Lens+import           Data.Bifunctor import           Data.Ext import qualified Data.Foldable as F import           Data.Geometry.Box-import           Data.Geometry.Interval+import           Data.Geometry.LineSegment import           Data.Geometry.Point import           Data.Geometry.Properties import           Data.Geometry.Transformation import           Data.Geometry.Vector import qualified Data.Seq2 as S2+import qualified Data.Sequence as Seq import           Data.Semigroup+import qualified Data.List.NonEmpty as NE  -------------------------------------------------------------------------------- -- * d-dimensional Polygonal Lines (PolyLines)@@ -25,7 +29,10 @@ deriving instance (Show r, Show p, Arity d) => Show    (PolyLine d p r) deriving instance (Eq r, Eq p, Arity d)     => Eq      (PolyLine d p r) deriving instance (Ord r, Ord p, Arity d)   => Ord     (PolyLine d p r)-deriving instance Arity d                   => Functor (PolyLine d p)++instance Arity d => Functor (PolyLine d p) where+  fmap f (PolyLine ps) = PolyLine $ fmap (first (fmap f)) ps+ type instance Dimension (PolyLine d p r) = d type instance NumType   (PolyLine d p r) = r @@ -33,15 +40,42 @@   (PolyLine pts) <> (PolyLine pts') = PolyLine $ pts <> pts'  instance Arity d => IsBoxable (PolyLine d p r) where-  boundingBox = boundingBoxList . toListOf (points.traverse.core)+  boundingBox = boundingBoxList . NE.fromList . toListOf (points.traverse.core)  instance (Num r, AlwaysTruePFT d) => IsTransformable (PolyLine d p r) where   transformBy = transformPointFunctor  instance PointFunctor (PolyLine d p) where-  pmap f = over points (fmap (fmap f))+  pmap f = over points (fmap (first f)) +instance Arity d => Bifunctor (PolyLine d) where+  bimap f g (PolyLine pts) = PolyLine $ fmap (bimap (fmap g) f) pts + -- | pre: The input list contains at least two points-fromPoints :: (Monoid p) => [Point d r] -> PolyLine d p r-fromPoints = PolyLine . S2.fromList . map (\p -> p :+ mempty)+fromPoints :: [Point d r :+ p] -> PolyLine d p r+fromPoints = PolyLine . S2.fromList++-- | pre: The input list contains at least two points. All extra vields are+-- initialized with mempty.+fromPoints' :: (Monoid p) => [Point d r] -> PolyLine d p r+fromPoints' = fromPoints . map (\p -> p :+ mempty)+++-- | We consider the line-segment as closed.+fromLineSegment                     :: LineSegment d p r -> PolyLine d p r+fromLineSegment ~(LineSegment' p q) = fromPoints [p,q]++-- | Convert to a closed line segment by taking the first two points.+asLineSegment                              :: PolyLine d p r -> LineSegment d p r+asLineSegment (PolyLine (S2.Seq2 p mid q)) = ClosedLineSegment p (f $ Seq.viewl mid)+  where+    f Seq.EmptyL    = q+    f (q' Seq.:< _) = q'++-- | Stricter version of asLineSegment that fails if the Polyline contains more+-- than two points.+asLineSegment'                            :: PolyLine d p r -> Maybe (LineSegment d p r)+asLineSegment' (PolyLine (S2.Seq2 p m q))+  | Seq.null m                            = Just $ ClosedLineSegment p q+  | otherwise                             = Nothing
src/Data/Geometry/Polygon.hs view
@@ -1,30 +1,50 @@-{-# LANGUAGE TemplateHaskell  #-} {-# LANGUAGE ScopedTypeVariables  #-} module Data.Geometry.Polygon where   import           Control.Applicative import           Control.Lens hiding (Simple)-import qualified Data.Foldable as F-+import           Data.Bifunctor import           Data.Ext+import           Data.Semigroup+import qualified Data.Foldable as F+import           Data.Geometry.Box+import           Data.Geometry.Vector+import           Data.Geometry.Boundary+import           Data.Geometry.LineSegment import           Data.Geometry.Point+import           Data.Geometry.Line import           Data.Geometry.Properties import           Data.Geometry.Transformation-import           Data.Geometry.Box--import qualified Data.CircularList as C+import           Data.Maybe(mapMaybe)+import           Data.Proxy+import           Data.Range+import           Frames.CoRec(asA)+import qualified Data.CircularSeq as C+-- import qualified Data.CircularList as C+import           Linear.Vector(Additive(..), (^*), (^/))  -------------------------------------------------------------------------------- -- * Polygons +{- $setup+>>> :{+let simplePoly :: SimplePolygon () Rational+    simplePoly = SimplePolygon . C.fromList . map ext $ [ point2 0 0+                                                        , point2 10 0+                                                        , point2 10 10+                                                        , point2 5 15+                                                        , point2 1 11+                                                        ]+:} -}+ -- | We distinguish between simple polygons (without holes) and Polygons with holes. data PolygonType = Simple | Multi   data Polygon (t :: PolygonType) p r where-  SimplePolygon :: C.CList (Point 2 r :+ p)                         -> Polygon Simple p r-  MultiPolygon  :: C.CList (Point 2 r :+ p) -> [Polygon Simple p r] -> Polygon Multi  p r+  SimplePolygon :: C.CSeq (Point 2 r :+ p)                         -> Polygon Simple p r+  MultiPolygon  :: C.CSeq (Point 2 r :+ p) -> [Polygon Simple p r] -> Polygon Multi  p r  type SimplePolygon = Polygon Simple @@ -34,35 +54,269 @@ type instance Dimension (Polygon t p r) = 2 type instance NumType   (Polygon t p r) = r +instance (Show p, Show r) => Show (Polygon t p r) where+  show (SimplePolygon vs)   = "SimplePolygon " <> show vs+  show (MultiPolygon vs hs) = "MultiPolygon " <> show vs <> " " <> show hs++instance (Eq p, Eq r) => Eq (Polygon t p r) where+  (SimplePolygon vs)   == (SimplePolygon vs')    = vs == vs'+  (MultiPolygon vs hs) == (MultiPolygon vs' hs') = vs == vs' && hs == hs'+  _                    == _                      = False++instance PointFunctor (Polygon t p) where+  pmap f (SimplePolygon vs)   = SimplePolygon (fmap (first f) vs)+  pmap f (MultiPolygon vs hs) = MultiPolygon  (fmap (first f) vs) (map (pmap f) hs)++instance Num r => IsTransformable (Polygon t p r) where+  transformBy = transformPointFunctor++ -- * Functions on Polygons -outerBoundary :: forall t p r. Lens' (Polygon t p r) (C.CList (Point 2 r :+ p))-outerBoundary = lens get set+outerBoundary :: forall t p r. Lens' (Polygon t p r) (C.CSeq (Point 2 r :+ p))+outerBoundary = lens g s   where-    get                     :: Polygon t p r -> C.CList (Point 2 r :+ p)-    get (SimplePolygon vs)  = vs-    get (MultiPolygon vs _) = vs+    g                     :: Polygon t p r -> C.CSeq (Point 2 r :+ p)+    g (SimplePolygon vs)  = vs+    g (MultiPolygon vs _) = vs -    set                           :: Polygon t p r -> C.CList (Point 2 r :+ p) -> Polygon t p r-    set (SimplePolygon _)      vs = SimplePolygon vs-    set (MultiPolygon  _   hs) vs = MultiPolygon vs hs+    s                           :: Polygon t p r -> C.CSeq (Point 2 r :+ p)+                                -> Polygon t p r+    s (SimplePolygon _)      vs = SimplePolygon vs+    s (MultiPolygon  _   hs) vs = MultiPolygon vs hs  holes :: forall p r. Lens' (Polygon Multi p r) [Polygon Simple p r]-holes = lens get set+holes = lens g s   where-    get :: Polygon Multi p r -> [Polygon Simple p r]-    get (MultiPolygon _ hs) = hs-    set :: Polygon Multi p r -> [Polygon Simple p r] -> Polygon Multi p r-    set (MultiPolygon vs _) hs = MultiPolygon vs hs+    g                     :: Polygon Multi p r -> [Polygon Simple p r]+    g (MultiPolygon _ hs) = hs+    s                     :: Polygon Multi p r -> [Polygon Simple p r]+                          -> Polygon Multi p r+    s (MultiPolygon vs _) = MultiPolygon vs  +-- | Access the i^th vertex on the outer boundary+outerVertex   :: Int -> Lens' (Polygon t p r) (Point 2 r :+ p)+outerVertex i = outerBoundary.C.item i++-- running time: $O(\log i)$+outerBoundaryEdge     :: Int -> Polygon t p r -> LineSegment 2 p r+outerBoundaryEdge i p = let u = p^.outerVertex i+                            v = p^.outerVertex (i+1)+                        in LineSegment (Closed u) (Open v)+++-- | Get all holes in a polygon+holeList                     :: Polygon t p r -> [Polygon Simple p r]+holeList (SimplePolygon _)   = []+holeList (MultiPolygon _ hs) = hs++ -- | The vertices in the polygon. No guarantees are given on the order in which -- they appear! vertices :: Polygon t p r -> [Point 2 r :+ p]-vertices (SimplePolygon vs)   = C.toList vs-vertices (MultiPolygon vs hs) = C.toList vs ++ concatMap vertices hs+vertices (SimplePolygon vs)   = F.toList vs+vertices (MultiPolygon vs hs) = F.toList vs ++ concatMap vertices hs    fromPoints :: [Point 2 r :+ p] -> SimplePolygon p r fromPoints = SimplePolygon . C.fromList++-- | The edges along the outer boundary of the polygon. The edges are half open.+outerBoundaryEdges :: Polygon t p r -> C.CSeq (LineSegment 2 p r)+outerBoundaryEdges = toEdges . (^.outerBoundary)++-- | Gets the i^th edge on the outer boundary of the polygon, that is the edge+-- with vertices i and i+1 with respect to the current focus. All indices+-- modulo n.+--++-- | Given the vertices of the polygon. Produce a list of edges. The edges are+-- half-open.+toEdges    :: C.CSeq (Point 2 r :+ p) -> C.CSeq (LineSegment 2 p r)+toEdges vs = let vs' = F.toList vs in+  C.fromList $ zipWith (\p q -> LineSegment (Closed p) (Open q)) vs' (tail vs' ++ vs')+++-- | Test if q lies on the boundary of the polygon. Running time: O(n)+--+-- >>> point2 1 1 `onBoundary` simplePoly+-- False+-- >>> point2 0 0 `onBoundary` simplePoly+-- True+-- >>> point2 10 0 `onBoundary` simplePoly+-- True+-- >>> point2 5 13 `onBoundary` simplePoly+-- False+-- >>> point2 5 10 `onBoundary` simplePoly+-- False+-- >>> point2 10 5 `onBoundary` simplePoly+-- True+-- >>> point2 20 5 `onBoundary` simplePoly+-- False+--+-- TODO: testcases multipolygon+onBoundary        :: (Fractional r, Ord r) => Point 2 r -> Polygon t p r -> Bool+q `onBoundary` pg = any (q `onSegment`) es+  where+    out = SimplePolygon $ pg^.outerBoundary+    es = concatMap (F.toList . outerBoundaryEdges) $ out : holeList pg++-- | Check if a point lies inside a polygon, on the boundary, or outside of the polygon.+-- Running time: O(n).+--+-- >>> point2 1 1 `inPolygon` simplePoly+-- Inside+-- >>> point2 0 0 `inPolygon` simplePoly+-- OnBoundary+-- >>> point2 10 0 `inPolygon` simplePoly+-- OnBoundary+-- >>> point2 5 13 `inPolygon` simplePoly+-- Inside+-- >>> point2 5 10 `inPolygon` simplePoly+-- Inside+-- >>> point2 10 5 `inPolygon` simplePoly+-- OnBoundary+-- >>> point2 20 5 `inPolygon` simplePoly+-- Outside+--+-- TODO: Add some testcases with multiPolygons+-- TODO: Add some more onBoundary testcases+inPolygon                                :: forall t p r. (Fractional r, Ord r)+                                         => Point 2 r -> Polygon t p r+                                         -> PointLocationResult+q `inPolygon` pg+    | q `onBoundary` pg                             = OnBoundary+    | odd kl && odd kr && not (any (q `inHole`) hs) = Inside+    | otherwise                                     = Outside+  where+    l = horizontalLine $ q^.yCoord++    -- Given a line segment, compute the intersection point (if a point) with the+    -- line l+    intersectionPoint = asA (Proxy :: Proxy (Point 2 r)) . (`intersect` l)++    -- Count the number of intersections that the horizontal line through q+    -- maxes with the polygon, that are strictly to the left and strictly to+    -- the right of q. If these numbers are both odd the point lies within the polygon.+    --+    --+    -- note that: - by the asA (Point 2 r) we ignore horizontal segments (as desired)+    --            - by the filtering, we effectively limit l to an open-half line, starting+    --               at the (open) point q.+    --            - by using half-open segments as edges we avoid double counting+    --               intersections that coincide with vertices.+    --            - If the point is outside, and on the same height as the+    --              minimum or maximum coordinate of the polygon. The number of+    --              intersections to the left or right may be one. Thus+    --              incorrectly classifying the point as inside. To avoid this,+    --              we count both the points to the left *and* to the right of+    --              p. Only if both are odd the point is inside.  so that if+    --              the point is outside, and on the same y-coordinate as one+    --              of the extermal vertices (one ofth)+    --+    -- See http://geomalgorithms.com/a03-_inclusion.html for more information.+    SP kl kr = count (\p -> (p^.xCoord) `compare` (q^.xCoord))+             . mapMaybe intersectionPoint . F.toList . outerBoundaryEdges $ pg++    -- For multi polygons we have to test if we do not lie in a hole .+    inHole = insidePolygon+    hs     = holeList pg++    count   :: (a -> Ordering) -> [a] -> SP Int Int+    count f = foldr (\x (SP lts gts) -> case f x of+                             LT -> SP (lts + 1) gts+                             EQ -> SP lts       gts+                             GT -> SP lts       (gts + 1)) (SP 0 0)+++++data SP a b = SP !a !b+++-- | Test if a point lies strictly inside the polgyon.+insidePolygon        :: (Fractional r, Ord r) => Point 2 r -> Polygon t p r -> Bool+q `insidePolygon` pg = q `inPolygon` pg == Inside+++-- testQ = map (`inPolygon` testPoly) [ point2 1 1    -- Inside+--                                    , point2 0 0    -- OnBoundary+--                                    , point2 5 14   -- Inside+--                                    , point2 5 10   -- Inside+--                                    , point2 10 5   -- OnBoundary+--                                    , point2 20 5   -- Outside+--                                    ]++-- testPoly :: SimplePolygon () Rational+-- testPoly = SimplePolygon . C.fromList . map ext $ [ point2 0 0+--                                                   , point2 10 0+--                                                   , point2 10 10+--                                                   , point2 5 15+--                                                   , point2 1 11+--                                                   ]++-- | Compute the area of a polygon+area                        :: Fractional r => Polygon t p r -> r+area poly@(SimplePolygon _) = abs $ signedArea poly+area (MultiPolygon vs hs)   = area (SimplePolygon vs) - sum [area h | h <- hs]+++-- | Compute the signed area of a simple polygon. The the vertices are in+-- clockwise order, the signed area will be negative, if the verices are given+-- in counter clockwise order, the area will be positive.+signedArea      :: Fractional r => SimplePolygon p r -> r+signedArea poly = x / 2+  where+    x = sum [ p^.core.xCoord * q^.core.yCoord - q^.core.xCoord * p^.core.yCoord+            | LineSegment' p q <- F.toList $ outerBoundaryEdges poly  ]+++-- | Compute the centroid of a simple polygon.+centroid      :: Fractional r => SimplePolygon p r -> Point 2 r+centroid poly = Point $ sum' xs ^/ (6 * signedArea poly)+  where+    xs = [ (toVec p ^+^ toVec q) ^* (p^.xCoord * q^.yCoord - q^.xCoord * p^.yCoord)+         | LineSegment' (p :+ _) (q :+ _) <- F.toList $ outerBoundaryEdges poly  ]++    sum' = F.foldl' (^+^) zero+++-- | Test if the outer boundary of the polygon is in clockwise or counter+-- clockwise order.+--+-- running time: $O(1)$+--+isCounterClockwise :: (Eq r, Fractional r) => Polygon t p r -> Bool+isCounterClockwise = (\x -> x == abs x) . signedArea+                   . fromPoints . take 3 . F.toList . (^.outerBoundary)+++-- | Orient the outer boundary to clockwise order+toClockwiseOrder   :: (Eq r, Fractional r) => Polygon t p r -> Polygon t p r+toClockwiseOrder p+  | isCounterClockwise p = p&outerBoundary %~ C.reverseDirection+  | otherwise            = p++-- | Convert a Polygon to a simple polygon by forgetting about any holes.+asSimplePolygon                        :: Polygon t p r -> SimplePolygon p r+asSimplePolygon poly@(SimplePolygon _) = poly+asSimplePolygon (MultiPolygon vs _)    = SimplePolygon vs+++-- | Comparison that compares which point is 'larger' in the direction given by+-- the vector u.+cmpExtreme       :: (Num r, Ord r)+                 => Vector 2 r -> Point 2 r :+ p -> Point 2 r :+ q -> Ordering+cmpExtreme u p q = u `dot` (p^.core .-. q^.core) `compare` 0+++-- | Finds the extreme points, minimum and maximum, in a given direction+--+-- running time: $O(n)$+extremesLinear     :: (Ord r, Num r) => Vector 2 r -> Polygon t p r+                   -> (Point 2 r :+ p, Point 2 r :+ p)+extremesLinear u p = let vs = p^.outerBoundary+                         f  = cmpExtreme u+                     in (F.minimumBy f vs, F.maximumBy f vs)
+ src/Data/Geometry/Polygon/Convex.hs view
@@ -0,0 +1,260 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-|+Module    : Data.Geometry.Polygon.Convex+Description: Convex Polygons+Copyright : (c) Frank Staals+License : See LICENCE file+-}+module Data.Geometry.Polygon.Convex( ConvexPolygon+                                   , merge+                                   , lowerTangent, upperTangent+                                   , isLeftOf, isRightOf++                                   , extremes+                                   , maxInDirection+                                   ) where++import           Control.Lens hiding ((:<), (:>))+import qualified Data.CircularSeq as C+import           Data.CircularSeq (focus,CSeq)+import           Data.Ext+import qualified Data.Foldable as F+import           Data.Function (on, )+import           Data.Geometry+import           Data.Geometry.Polygon (fromPoints, cmpExtreme)+import           Data.Maybe (fromJust)+import           Data.Ord (comparing)+import qualified Data.Sequence as S+import           Data.Sequence (viewl,viewr, ViewL(..), ViewR(..))++import           Data.Geometry.Ipe+import Debug.Trace+--------------------------------------------------------------------------------++type ConvexPolygon = SimplePolygon+++mainWith inFile outFile = do+    ePage <- readSinglePageFile inFile+    case ePage of+      Left err                         -> error "" -- err+      Right (page :: IpePage Rational) -> case page^..content.traverse._withAttrs _IpePath _asSimplePolygon.core of+        []         -> error "No points found"+        polies@(_:_) -> do+           -- let out  = [asIpe drawTriangulation dt, asIpe drawTree' emst]+           -- print $ length $ edges' dt+           -- print $ toPlaneGraph (Proxy :: Proxy DT) dt+           -- writeIpeFile outFile . singlePageFromContent $ out+           -- mapM_ (print . extremesNaive (v2 1 0)) polies+           pure $ map (maxInDirection (v2 (-1) 0)) polies+++++-- | Finds the extreme points, minimum and maximum, in a given direction+--+-- pre: The input polygon is strictly convex.+--+-- running time: $O(\log n)$+--+--+extremes     :: (Num r, Ord r) => Vector 2 r -> ConvexPolygon p r+             -> (Point 2 r :+ p, Point 2 r :+ p)+extremes u p = (maxInDirection ((-1) *^ u) p, maxInDirection u p)++-- | Finds the extreme maximum point in the given direction. Based on+-- http://geomalgorithms.com/a14-_extreme_pts.html+--+--+-- pre: The input polygon is strictly convex.+--+-- running time: $O(\log^2 n)$+maxInDirection     :: (Num r, Ord r) => Vector 2 r -> ConvexPolygon p r -> Point 2 r :+ p+maxInDirection u p = findMaxStart . C.rightElements $ p^.outerBoundary+  where+    findMaxStart s@(viewl -> (a:<r))+      | isLocalMax r a r = a+      | otherwise        = findMax s+    findMaxStart _       = error "absurd"++    findMax s = let i         = F.length s `div` 2+                    (ac,cb')  = S.splitAt i s+                    (c :< cb) = viewl cb'+                in findMax' ac c cb++    findMax' ac c cb+      | isLocalMax ac c cb = c+      | otherwise          = binSearch ac c cb++    -- | Given the vertices [a..] c [..b] find the exteral vtx+    binSearch ac@(viewl -> a:<r) c cb = case (isUpwards a r, isUpwards c cb, a >=. c) of+        (True,False,_)      -> findMax (ac |> c)+        (True,True,True)    -> findMax (ac |> c)+        (True,True,False)   -> findMax (c <| cb)++        (False,True,_)      -> findMax (c <| cb)+        (False,False,False) -> findMax (ac |> c)+        (False,False,True)  -> findMax (c <| cb)+    binSearch _                  _ _ = error "maxInDirection, binSearch: empty chain"++    isLocalMax (viewr -> _ :> l) c (viewl -> r :< _) = c >=. l && c >=. r+    isLocalMax (viewr -> _ :> l) c _                 = c >=. l+    isLocalMax _                 c (viewl -> r :< _) = c >=. r+    isLocalMax _                 _ _                 = True++    -- the Edge from a to b is upwards w.r.t b if a is not larger than b+    isUpwards a (viewl -> b :< _) = cmpExtreme u a b /= GT+    isUpwards _ _                 = error "isUpwards: no edge endpoint"++    p' >=. q = cmpExtreme u p' q /= LT+++--  | Given a convex polygon poly, and a point outside the polygon, find the+--  right tangent of q and the polygon, i.e. the vertex v of the convex polygon+--  s.t. the polygon lies completely to the right of the line from q to v.+--+-- running time: $O(\log^2 n)$.+-- rightTangent        :: ConvexPolygon p r -> Point 2 r -> Point 2 r :+ p+-- rightTangent poly q = undefined+-- TODO: same as maxInDirection, but with a slightly different ordering+++++++++-- * Merging Two convex Hulls+++-- | Rotating Right <-> rotate clockwise+--+-- Merging two convex hulls, based on the paper:+--+-- Two Algorithms for Constructing a Delaunay Triangulation+-- Lee and Schachter+-- International Journal of Computer and Information Sciences, Vol 9, No. 3, 1980+--+-- : (combined hull, lower tangent that was added, upper tangent thtat was+-- added)++-- pre: - lp and rp are disjoint, and there is a vertical line separating+--        the two polygons.+--      - The vertices of the polygons are given in clockwise order+--+-- Running time: O(n+m), where n and m are the sizes of the two polygons respectively+merge       :: (Num r, Ord r) => ConvexPolygon p r  -> ConvexPolygon p r+            -> (ConvexPolygon p r, LineSegment 2 p r, LineSegment 2 p r)+merge lp rp = (fromPoints $ r' ++ l', lt, ut)+  where+    lt@(ClosedLineSegment a b) = lowerTangent lp rp+    ut@(ClosedLineSegment c d) = upperTangent lp rp+++    takeUntil p xs = let (xs',x:_) = break p xs in xs' ++ [x]+    rightElems  = F.toList . C.rightElements++    r' = takeUntil (coreEq b) . rightElems . rotateTo' d $ rp^.outerBoundary+    l' = takeUntil (coreEq c) . rightElems . rotateTo' a $ lp^.outerBoundary+++rotateTo'   :: Eq a => (a :+ b) -> CSeq (a :+ b) -> CSeq (a :+ b)+rotateTo' x = fromJust . C.findRotateTo (coreEq x)+++coreEq :: Eq a => (a :+ b) -> (a :+ b) -> Bool+coreEq = (==) `on` (^.core)++-- | Compute the lower tangent of the two polgyons+--+--   pre: - polygons lp and rp have at least 1 vertex+--        - lp and rp are disjoint, and there is a vertical line separating+--          the two polygons.+--        - The vertices of the polygons are given in clockwise order+--+-- Running time: O(n+m), where n and m are the sizes of the two polygons respectively+lowerTangent                                     :: (Num r, Ord r)+                                                 => ConvexPolygon p r+                                                 -> ConvexPolygon p r+                                                 -> LineSegment 2 p r+lowerTangent (SimplePolygon l) (SimplePolygon r) = rotate xx yy zz zz''+  where+    xx = rightMost l+    yy = leftMost r++    zz   = pred' yy+    zz'' = succ' xx++    rotate x y z z''+      | focus z   `isRightOf` (focus x, focus y) = rotate x   z (pred' z) z''+                                                      -- rotate the right polygon CCW+      | focus z'' `isRightOf` (focus x, focus y) = rotate z'' y z         (succ' z'')+                                                      -- rotate the left polygon CW+      | otherwise                                = ClosedLineSegment (focus x)+                                                                     (focus y)++succ' :: CSeq a -> CSeq a+succ' = C.rotateR++pred' :: CSeq a -> CSeq a+pred' = C.rotateL++-- | Compute the upper tangent of the two polgyons+--+--   pre: - polygons lp and rp have at least 1 vertex+--        - lp and rp are disjoint, and there is a vertical line separating+--          the two polygons.+--        - The vertices of the polygons are given in clockwise order+--+-- Running time: O(n+m), where n and m are the sizes of the two polygons respectively+upperTangent                                     :: (Num r, Ord r)+                                                 => ConvexPolygon p r+                                                 -> ConvexPolygon p r+                                                 -> LineSegment 2 p r+upperTangent (SimplePolygon l) (SimplePolygon r) = rotate xx yy zz zz'+  where+    xx = rightMost l+    yy = leftMost r++    zz  = succ' yy+    zz' = pred' xx++    rotate x y z z'+      | focus z  `isLeftOf` (focus x, focus y) = rotate x  z (succ' z) z'+                                                    -- rotate the right polygon CW+      | focus z' `isLeftOf` (focus x, focus y) = rotate z' y z        (pred' z')+                                                    -- rotate the left polygon CCW+      | otherwise                              = ClosedLineSegment (focus x)+                                                                   (focus y)++isRightOf           :: (Num r, Ord r)+                    => Point 2 r :+ p -> (Point 2 r :+ p', Point 2 r :+ p'') -> Bool+a `isRightOf` (b,c) = ccw (b^.core) (c^.core) (a^.core) == CW++isLeftOf            :: (Num r, Ord r)+                    => Point 2 r :+ p -> (Point 2 r :+ p', Point 2 r :+ p'') -> Bool+a `isLeftOf` (b,c) = ccw (b^.core) (c^.core) (a^.core) == CCW+++--------------------------------------------------------------------------------++-- | Rotate to the rightmost point+rightMost    :: Ord r => CSeq (Point 2 r :+ p) -> CSeq (Point 2 r :+ p)+rightMost xs = let m = F.maximumBy (comparing (^.core.xCoord)) xs in rotateTo' m xs++-- | Rotate to the leftmost point+leftMost    :: Ord r => CSeq (Point 2 r :+ p) -> CSeq (Point 2 r :+ p)+leftMost xs = let m = F.minimumBy (comparing (^.core.xCoord)) xs in rotateTo' m xs+++-- -- | rotate right while p 'current' 'rightNeibhour' is true+-- rotateRWhile      :: (a -> a -> Bool) -> C.CList a -> C.CList a+-- rotateRWhile p lst+--   | C.isEmpty lst = lst+--   | otherwise     = go lst+--     where+--       go xs = let cur = focus xs+--                   xs' = C.rotR xs+--                   nxt = focus' xs'+--               in if p cur nxt then go xs' else xs
src/Data/Geometry/Properties.hs view
@@ -1,18 +1,45 @@-{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ImpredicativeTypes #-}+{-# LANGUAGE UnicodeSyntax #-}+{-# LANGUAGE DefaultSignatures #-} module Data.Geometry.Properties where +import           Control.Applicative+import           Data.Maybe(isJust)+import           Data.Proxy+import           Data.Vinyl.Core+import           Data.Vinyl.Functor+import           Data.Vinyl.Lens+import           Frames.CoRec+import           GHC.TypeLits+++ ---------------------------------------------------------------------------------import GHC.TypeLits --- | A type family for types that are associated with a dimension.+-- | A type family for types that are associated with a dimension. The+-- dimension is the dimension of the geometry they are embedded in. type family Dimension t :: Nat  -- | A type family for types that have an associated numeric type. type family NumType t :: * +-- | A simple data type expressing that there are no intersections+data NoIntersection = NoIntersection deriving (Show,Read,Eq,Ord) +-- | The result of interesecting two geometries is a CoRec,+type Intersection g h = CoRec Identity (IntersectionOf g h)++-- | The type family specifying the list of possible result types of an+-- intersection.+type family IntersectionOf g h :: [*]++-- | Helper to produce a corec+coRec :: (a ∈ as) => a -> CoRec Identity as+coRec = Col . Identity++ class IsIntersectableWith g h where-  data Intersection g h   intersect :: g -> h -> Intersection g h    -- | g `intersects` h  <=> The intersection of g and h is non-empty.@@ -21,12 +48,53 @@   -- and uses nonEmptyIntersection to determine if the intersection is   -- non-empty.   intersects :: g -> h -> Bool-  g `intersects` h = nonEmptyIntersection $ g `intersect` h+  g `intersects` h = nonEmptyIntersection (Identity g) (Identity h) $ g `intersect` h    -- | Helper to implement `intersects`.-  nonEmptyIntersection :: Intersection g h -> Bool+  nonEmptyIntersection :: proxy g -> proxy h -> Intersection g h -> Bool   {-# MINIMAL intersect , nonEmptyIntersection #-} +  default nonEmptyIntersection :: ( NoIntersection ∈ IntersectionOf g h+                                  , RecApplicative (IntersectionOf g h)+                                  )+                                  => proxy g -> proxy h -> Intersection g h -> Bool+  nonEmptyIntersection = defaultNonEmptyIntersection+++-- | When using IntersectionOf we may need some constraints that are always+-- true anyway.+type AlwaysTrueIntersection g h = RecApplicative (IntersectionOf g h)+++-- | Returns True iff the resultt is a NoIntersection+defaultNonEmptyIntersection :: forall g h proxy.+                            ( NoIntersection ∈ IntersectionOf g h+                            , RecApplicative (IntersectionOf g h)+                            )+                            => proxy g -> proxy h -> Intersection g h -> Bool+defaultNonEmptyIntersection _ _ = isJust . asA (Proxy :: Proxy NoIntersection)+++-- type IsAlwaysTrueFromEither a b = (VTL.RIndex b [a,b] ~ ((VTL.S VTL.Z)))+--                                   -- VTL.RIndex b [a,b] ~ ((VTL.S VTL.Z))(b  ∈ [a,b])++-- -- | Convert an either to a CoRec. The type class constraint is silly, and is+-- -- triviall true. Somehow GHC does not see that though.+-- fromEither           :: IsAlwaysTrueFromEither a b => Either a b -> CoRec Identity [a,b]+-- fromEither (Left x)  = coRec x+-- fromEither (Right x) = coRec x+++-- -- fromEither'           :: ( RElem b [a,b] ((VTL.S VTL.Z))+-- --                          ) => Either a b -> CoRec Identity [a,b]++-- fromEither'           :: ( VTL.RIndex b [a,b] ~ ((VTL.S VTL.Z))+-- --                           VTL.RIndex b '[b] ~ VTL.Z+--                          ) => Either a b -> CoRec Identity [a,b]+-- fromEither' (Left x)  = coRec x+-- fromEither' (Right x) = coRec x++type family Union g h :: *+ class IsUnionableWith g h where-  data Union g h   union :: g -> h -> Union g h
+ src/Data/Geometry/Slab.hs view
@@ -0,0 +1,137 @@+{-# Language ScopedTypeVariables #-}+{-# Language TemplateHaskell #-}+module Data.Geometry.Slab where+++import           Control.Applicative+import           Control.Lens(makeLenses, (^.),(%~),(.~),(&), Lens', both)+import           Data.Bitraversable+import           Data.Ext+import qualified Data.Foldable as F+import           Data.Geometry.Properties+import           Data.Geometry.Interval+import           Data.Geometry.Point+import           Data.Geometry.Box.Internal+import           Data.Geometry.LineSegment+import           Data.Geometry.Line+import           Data.Geometry.SubLine+import           Data.Range+import           Data.Semigroup+import qualified Data.Traversable as T+import           Data.Vinyl+import           Frames.CoRec+import           Data.Bifunctor++--------------------------------------------------------------------------------++data Orthogonal = Horizontal | Vertical+                deriving (Show,Eq,Read)++++newtype Slab (o :: Orthogonal) a r = Slab { _unSlab :: Interval a r }+                                     deriving (Show,Eq)+makeLenses ''Slab++-- | Smart consturctor for creating a horizontal slab+horizontalSlab     :: (r :+ a) -> (r :+ a) -> Slab Horizontal a r+horizontalSlab l h = Slab $ ClosedInterval l h++-- | Smart consturctor for creating a vertical slab+verticalSlab :: (r :+ a) -> (r :+ a) -> Slab Vertical a r+verticalSlab l r = Slab $ ClosedInterval l r++instance Functor (Slab o a) where+  fmap = T.fmapDefault++instance F.Foldable (Slab o a) where+  foldMap = T.foldMapDefault++instance T.Traversable (Slab o a) where+  traverse f (Slab i) = Slab <$> T.traverse f i+++instance Bifunctor (Slab o) where+  bimap f g (Slab i) = Slab $ bimap f g i+++type instance IntersectionOf (Slab o a r)          (Slab o a r) =+  [NoIntersection, Slab o a r]+type instance IntersectionOf (Slab Horizontal a r) (Slab Vertical a r) =+  '[Rectangle (a,a) r]+++instance Ord r => (Slab o a r) `IsIntersectableWith` (Slab o a r) where+  nonEmptyIntersection = defaultNonEmptyIntersection++  (Slab i) `intersect` (Slab i') = match (i `intersect` i') $+        (H $ \NoIntersection -> coRec NoIntersection)+     :& (H $ \i''            -> coRec (Slab i'' :: Slab o a r))+     :& RNil++instance (Slab Horizontal a r) `IsIntersectableWith` (Slab Vertical a r) where+  nonEmptyIntersection _ _ _ = True++  (Slab h) `intersect` (Slab v) = coRec $ fromCornerPoints low high+    where+      low  = point2 (v^.start.core) (h^.start.core) :+ (v^.start.extra, h^.start.extra)+      high = point2 (v^.end.core)   (h^.end.core)   :+ (v^.end.extra,   h^.end.extra)++++class HasBoundingLines (o :: Orthogonal) where+  -- | The two bounding lines of the slab, first the lower one, then the higher one:+  --+  boundingLines :: Num r => Slab o a r -> (Line 2 r :+ a, Line 2 r :+ a)++  inSlab :: Ord r => Point 2 r -> Slab o a r -> Bool+++instance HasBoundingLines Horizontal where+  boundingLines (Slab i) = (i^.start, i^.end)&both.core %~ horizontalLine++  p `inSlab` (Slab i) = (p^.yCoord) `inInterval` i+++instance HasBoundingLines Vertical where+  boundingLines (Slab i) = (i^.start, i^.end)&both.core %~ verticalLine++  p `inSlab` (Slab i) = (p^.xCoord) `inInterval` i+++type instance IntersectionOf (Line 2 r) (Slab o a r) =+  [NoIntersection, Line 2 r, LineSegment 2 a r]++instance (Fractional r, Ord r, HasBoundingLines o) =>+         Line 2 r `IsIntersectableWith` (Slab o a r) where+  nonEmptyIntersection = defaultNonEmptyIntersection++  l@(Line p _) `intersect` s = match (l `intersect` a) $+         (H $ \NoIntersection -> if p `inSlab` s then coRec l else coRec NoIntersection)+      :& (H $ \pa             -> match (l `intersect` b) $+            (H $ \NoIntersection -> coRec NoIntersection)+         :& (H $ \pb             -> coRec $ lineSegment' pa pb)+         :& (H $ \_              -> coRec l)+         :& RNil+         )+      :& (H $ \_              -> coRec l)+      :& RNil+    where+      (a :+ _,b :+ _) = boundingLines s++      -- note that this maintains the open/closedness of the slab+      lineSegment' pa pb = let Interval a' b' = s^.unSlab+                           in LineSegment (a'&unEndPoint.core .~ pa)+                                          (b'&unEndPoint.core .~ pb)++++type instance IntersectionOf (SubLine 2 p r) (Slab o a r) =+  [NoIntersection, SubLine 2 a r, LineSegment 2 a r]++instance (Fractional r, Ord r, HasBoundingLines o) =>+         SubLine 2 a r `IsIntersectableWith` (Slab o a r) where++  nonEmptyIntersection = defaultNonEmptyIntersection++  (SubLine l r) `intersect` (Slab i) = undefined
+ src/Data/Geometry/SubLine.hs view
@@ -0,0 +1,102 @@+{-# LANGUAGE TemplateHaskell  #-}+module Data.Geometry.SubLine where++import           Control.Applicative+import qualified Data.Foldable as F+import qualified Data.Traversable as T+import Control.Lens+import Data.Ext+import Data.Geometry.Interval+import Data.Geometry.Line.Internal+import Data.Geometry.Point+import Data.Geometry.Properties+import Data.Geometry.Vector+import Data.Range+import Data.UnBounded+import           Frames.CoRec+import Data.Vinyl++--------------------------------------------------------------------------------++-- | Part of a line. The interval is ranged based on the unit-vector of the+-- line l, and s.t.t zero is the anchorPoint of l.+data SubLine d p r = SubLine { _line     :: Line d r+                             , _subRange :: Interval p r+                             }+++makeLenses ''SubLine++type instance Dimension (SubLine d p r) = d+type instance NumType   (SubLine d p r) = r++deriving instance (Show r, Show p, Arity d) => Show (SubLine d p r)+deriving instance (Eq r, Eq p, Arity d)     => Eq (SubLine d p r)+deriving instance Arity d                   => Functor (SubLine d p)+deriving instance Arity d                   => F.Foldable (SubLine d p)+deriving instance Arity d                   => T.Traversable (SubLine d p)+++-- | Get the point at the given position along line, where 0 corresponds to the+-- anchorPoint of the line, and 1 to the point anchorPoint .+^ directionVector+pointAt              :: (Num r, Arity d) => r -> Line d r -> Point d r+pointAt a (Line p v) = p .+^ (a *^ v)++-- | Annotate the subRange with the actual ending points+fixEndPoints    :: (Num r, Arity d) => SubLine d p r -> SubLine d (Point d r :+ p) r+fixEndPoints sl = sl&subRange %~ f+  where+    ptAt              = flip pointAt (sl^.line)+    label (c :+ e)    = (c :+ (ptAt c :+ e))+    f ~(Interval l u) = Interval (l&unEndPoint %~ label)+                                 (u&unEndPoint %~ label)+++-- | given point p on line (Line q v), Get the scalar lambda s.t.+-- p = q + lambda v+toOffset              :: (Eq r, Fractional r, Arity d) => Point d r -> Line d r -> r+toOffset p (Line q v) = fromJust' $ scalarMultiple (p .-. q) v+  where+    fromJust' (Just x) = x+    fromJust' _        = error "toOffset: Nothing"++type instance IntersectionOf (SubLine 2 p r) (SubLine 2 q r) = [ NoIntersection+                                                               , Point 2 r+                                                               , SubLine 2 p r+                                                               ]+++instance (Ord r, Fractional r) =>+         (SubLine 2 p r) `IsIntersectableWith` (SubLine 2 p r) where++  nonEmptyIntersection = defaultNonEmptyIntersection++  (SubLine l r) `intersect` (SubLine m s) = match (l `intersect` m) $+         (H $ \NoIntersection -> coRec NoIntersection)+      :& (H $ \p@(Point _)    -> if (toOffset p l) `inInterval` r+                                    &&+                                    (toOffset p m) `inInterval` s+                                 then coRec p+                                 else coRec NoIntersection)+      :& (H $ \_             -> match (r `intersect` s') $+                                      (H $ \NoIntersection -> coRec NoIntersection)+                                   :& (H $ \i              -> coRec $ SubLine l i)+                                   :& RNil+           )+      :& RNil+    where+      s' = shiftLeft' (toOffset (m^.anchorPoint) l) s+++fromLine   :: Arity d => Line d r -> SubLine d () (UnBounded r)+fromLine l = SubLine (fmap Val l) (OpenInterval (ext MinInfinity) (ext MaxInfinity))+++-- testL :: SubLine 2 () (UnBounded Rational)+-- testL = SubLine (horizontalLine 0) (Interval (Closed (only 0)) (Open $ only 10))++-- horL :: SubLine 2 () (UnBounded Rational)+-- horL = fromLine $ horizontalLine 0+++-- test = (testL^.subRange) `intersect` (horL^.subRange)
src/Data/Geometry/Transformation.hs view
@@ -17,13 +17,10 @@  import           GHC.TypeLits import           Linear.Matrix((!*),(!*!))-import           Linear.Vector(zero)  import qualified Data.Vector.Fixed as FV import qualified Data.Geometry.Vector as V -import           Data.Vinyl.TypeLevel hiding (Nat)- -------------------------------------------------------------------------------- -- * Matrices @@ -87,7 +84,7 @@          ) => IsTransformable (Point d r) where   transformBy (Transformation m) (Point v) = Point . V.init $ m `mult` v'     where-      v'    = snoc v 0+      v'    = snoc v 1   --------------------------------------------------------------------------------
src/Data/Geometry/Triangle.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE DeriveFunctor #-} module Data.Geometry.Triangle where +import Data.Bifunctor import Control.Lens import Data.Ext import Data.Geometry.Point@@ -11,8 +12,12 @@ data Triangle p r = Triangle (Point 2 r :+ p)                              (Point 2 r :+ p)                              (Point 2 r :+ p)-                    deriving (Show,Eq,Functor)+                    deriving (Show,Eq) +instance Functor (Triangle p) where+  fmap f (Triangle p q r) = let f' = first (fmap f) in Triangle (f' p) (f' q) (f' r)++ type instance NumType   (Triangle p r) = r type instance Dimension (Triangle p r) = 2 @@ -39,7 +44,7 @@     Point2 cx cy = c^.core  --- | get the inscribed circle. Returns Nothing if the triangle is degenerate,+-- | get the inscribed disk. Returns Nothing if the triangle is degenerate, -- i.e. if the points are colinear.-inscribedCircle                  :: (Eq r, Fractional r) => Triangle p r -> Maybe (Circle () r)-inscribedCircle (Triangle p q r) = circle (p^.core) (q^.core) (r^.core)+inscribedDisk                  :: (Eq r, Fractional r) => Triangle p r -> Maybe (Disk () r)+inscribedDisk (Triangle p q r) = disk (p^.core) (q^.core) (r^.core)
src/Data/Geometry/Vector.hs view
@@ -1,12 +1,21 @@-module Data.Geometry.Vector( module GV-                           , module FV+module Data.Geometry.Vector( module Data.Geometry.Vector.VectorFixed+                           , module LV+                           , Affine(..)+                           , qdA, distanceA+                           , dot, norm                            , isScalarMultipleOf                            , scalarMultiple                            ) where -import qualified Data.Vector.Fixed                as FV+import           Data.Monoid import qualified Data.Foldable                    as F+import           Data.Geometry.Vector.VectorFixed import           Data.Geometry.Vector.VectorFixed as GV+import           Data.Maybe+import qualified Data.Vector.Fixed                as FV+import           Linear.Affine(Affine(..), qdA, distanceA)+import           Linear.Metric(dot,norm)+import           Linear.Vector as LV   -- | Test if v is a scalar multiple of u.@@ -23,26 +32,59 @@ -- False -- >>> v2 2 1 `isScalarMultipleOf` v2 4 2 -- True+-- >>> v2 2 1 `isScalarMultipleOf` v2 4 0+-- False isScalarMultipleOf       :: (Eq r, Fractional r, GV.Arity d)                          => Vector d r -> Vector d r -> Bool-u `isScalarMultipleOf` v = fst $  scalarMultiple' u v+u `isScalarMultipleOf` v = isJust $ scalarMultiple u v  -- | Get the scalar labmda s.t. v = lambda * u (if it exists) scalarMultiple     :: (Eq r, Fractional r, GV.Arity d)                    => Vector d r -> Vector d r -> Maybe r-scalarMultiple u v = case scalarMultiple' u v of-    (False,_)             -> Nothing-    (_, mm@(Just lambda)) -> mm-    (_, _)                -> Just . fromIntegral $ 0+scalarMultiple u v+      | allZero u || allZero v = Just 0+      | otherwise              = scalarMultiple' u v --- | Helper function for computing the scalar multiple. The result is a pair--- (b,mm), where b indicates if v is a scalar multiple of u, and mm is a Maybe--- scalar multiple. If the result is Nothing, the scalar multiple is zero.-scalarMultiple'     :: (Eq r, Fractional r, GV.Arity d)-                    => Vector d r -> Vector d r -> (Bool,Maybe r)-scalarMultiple' u v = F.foldr allLambda (True,Nothing) $ FV.zipWith f u v++-- -- | Helper function for computing the scalar multiple. The result is a pair+-- -- (b,mm), where b indicates if v is a scalar multiple of u, and mm is a Maybe+-- -- scalar multiple. If the result is Nothing, the scalar multiple is zero.+-- scalarMultiple'     :: (Eq r, Fractional r, GV.Arity d)+--                     => Vector d r -> Vector d r -> (Bool,Maybe r)+-- scalarMultiple' u v = F.foldr allLambda (True,Nothing) $ FV.zipWith f u v+--   where+--     f ui vi = (ui == 0 && vi == 0, ui / vi)+--     allLambda (True,_)      x               = x+--     allLambda (_, myLambda) (b,Nothing)     = (b,Just myLambda) -- no lambda yet+--     allLambda (_, myLambda) (b,Just lambda) = (myLambda == lambda && b, Just lambda)+++allZero :: (GV.Arity d, Eq r, Num r) => Vector d r -> Bool+allZero = F.all (== 0)+++data ScalarMultiple r = No | Maybe | Yes r deriving (Eq,Show)++instance Eq r => Monoid (ScalarMultiple r) where+  mempty = Maybe++  No      `mappend` _       = No+  _       `mappend` No      = No+  Maybe   `mappend` x       = x+  x       `mappend` Maybe   = x+  (Yes x) `mappend` (Yes y)+     | x == y               = Yes x+     | otherwise            = No+++scalarMultiple'      :: (Eq r, Fractional r, GV.Arity d)+                     => Vector d r -> Vector d r -> Maybe r+scalarMultiple' u v = g . F.foldr mappend mempty $ FV.zipWith f u v   where-    f ui vi = (ui == 0 && vi == 0, ui / vi)-    allLambda (True,_)      x               = x-    allLambda (_, myLambda) (b,Nothing)     = (b,Just myLambda) -- no lambda yet-    allLambda (_, myLambda) (b,Just lambda) = (myLambda == lambda && b, Just lambda)+    f 0  0  = Maybe -- we don't know lambda yet, but it may still be a scalar mult.+    f _  0  = No      -- Not a scalar multiple+    f ui vi = Yes $ ui / vi -- can still be a scalar multiple++    g No      = Nothing+    g Maybe   = error "scalarMultiple': found a Maybe, which means the vectors either have length zero, or one of them is all Zero!"+    g (Yes x) = Just x
src/Data/Geometry/Vector/VectorFixed.hs view
@@ -3,17 +3,14 @@ {-# LANGUAGE UndecidableInstances #-} module Data.Geometry.Vector.VectorFixed where +import           Data.Monoid import           Control.Applicative- import           Control.Lens--import           Data.Proxy- import           Data.Foldable import           Data.Traversable  import           Data.Vector.Fixed.Boxed-import           Data.Vector.Fixed.Cont(Z(..),S(..),ToPeano(..),ToNat(..))+import           Data.Vector.Fixed.Cont(Z, S, ToPeano)  import           GHC.TypeLits @@ -47,12 +44,26 @@  type Index' i d = V.Index (ToPeano i) (ToPeano d) ++-- | Lens into the i th element element   :: forall proxy i d r. (Arity d, Index' i d) => proxy i -> Lens' (Vector d r) r element _ = V.elementTy (undefined :: (ToPeano i)) +-- | Similar to 'element' above. Except that we don't have a static guarantee+-- that the index is in bounds. Hence, we can only return a Traversal+element'   :: forall d r. (KnownNat d, Arity d) => Int -> Traversal' (Vector d r) r+element' i f v+  | 0 <= i && i < fromInteger (natVal (C :: C d)) = f (v V.! i)+                                                 <&> \a -> (v&V.element i .~ a)+       -- Implementation based on that of Ixed Vector in Control.Lens.At+  | otherwise                                     = pure v  -deriving instance (Show r, Arity d) => Show (Vector d r)+instance (Show r, Arity d) => Show (Vector d r) where+  show (Vector v) = mconcat [ "Vector", show $ V.length v , " "+                            , show $ toList v+                            ]+ deriving instance (Eq r, Arity d)   => Eq (Vector d r) deriving instance (Ord r, Arity d)  => Ord (Vector d r) deriving instance Arity d  => Functor (Vector d)@@ -103,8 +114,8 @@ --------------------------------------------------------------------------------  -- | Conversion to a Linear.V3-toV3   :: Vector 3 a -> L3.V3 a-toV3 (Vector3 a b c) = L3.V3 a b c+toV3                  :: Vector 3 a -> L3.V3 a+toV3 ~(Vector3 a b c) = L3.V3 a b c  -- | Conversion from a Linear.V3 fromV3               :: L3.V3 a -> Vector 3 a@@ -162,5 +173,8 @@   -- | Pattern synonym for two and three dim vectors+pattern Vector2       :: r -> r -> Vector 2 r pattern Vector2 x y   <- (_unV2 -> (x,y))++pattern Vector3       :: r -> r -> r -> Vector 3 r pattern Vector3 x y z <- (_unV3 -> (x,y,z))
+ src/Data/Permutation.hs view
@@ -0,0 +1,114 @@+{-# LANGUAGE TemplateHaskell #-}+module Data.Permutation where++import           Control.Lens+import           Control.Monad (forM)+import           Control.Monad.ST (runST)+import qualified Data.Foldable as F+import           Data.Maybe (catMaybes)+import qualified Data.Traversable as T+import qualified Data.Vector as V+import qualified Data.Vector.Generic as GV+import qualified Data.Vector.Unboxed as UV+import qualified Data.Vector.Unboxed.Mutable as UMV++--------------------------------------------------------------------------------++-- | Orbits (Cycles) are represented by vectors+type Orbit a = V.Vector a++-- | Cyclic representation of a permutation.+data Permutation a = Permutation { _orbits  :: V.Vector (Orbit a)+                                 , _indexes :: UV.Vector (Int,Int)+                                               -- ^ idxes (fromEnum a) = (i,j)+                                               -- implies that a is the j^th+                                               -- item in the i^th orbit+                                 }+                   deriving (Show,Eq)+makeLenses ''Permutation++instance Functor Permutation where+  fmap = T.fmapDefault++instance F.Foldable Permutation where+  foldMap = T.foldMapDefault++instance T.Traversable Permutation where+  traverse f (Permutation os is) = flip Permutation is <$> T.traverse (T.traverse f) os+++elems :: Permutation a -> V.Vector a+elems = GV.concat . GV.toList . _orbits++size      :: Permutation a -> Int+size perm = GV.length (perm^.indexes)++-- | The cycle containing a given item+cycleOf        :: Enum a => Permutation a -> a -> Orbit a+cycleOf perm x = perm^.orbits.ix' (perm^.indexes.ix' (fromEnum x)._1)+++-- | Next item in a cyclic permutation+next     :: GV.Vector v a => v a -> Int -> a+next v i = let n = GV.length v in v GV.! ((i+1) `mod` n)++-- | Lookup the indices of an element, i.e. in which orbit the item is, and the+-- index within the orbit.+lookupIdx        :: Enum a => Permutation a -> a -> (Int,Int)+lookupIdx perm x = perm^.indexes.ix' (fromEnum x)++-- | Apply the permutation, i.e. consider the permutation as a function.+apply        :: Enum a => Permutation a -> a -> a+apply perm x = let (c,i) = lookupIdx perm x+               in next (perm^.orbits.ix' c) i+++-- | Find the cycle in the permutation starting at element s+orbitFrom     :: Eq a => a -> (a -> a) -> [a]+orbitFrom s p = s : (takeWhile (/= s) . tail $ iterate p s)++-- Given a vector with items in the permutation, and a permutation (by its+-- functional representation) construct the cyclic representation of the+-- permutation.+cycleRep        :: (GV.Vector v a, Enum a, Eq a) => v a -> (a -> a) -> Permutation a+cycleRep v perm = toCycleRep n $ runST $ do+    bv    <- UMV.replicate n False -- bit vector of marks+    morbs <- forM [0..(n - 1)] $ \i -> do+               m <- UMV.read bv (fromEnum $ v GV.! i)+               if m then pure Nothing -- already visited+                    else do+                      let xs = orbitFrom (v GV.! i) perm+                      markAll bv $ map fromEnum xs+                      pure . Just $ xs+    pure . catMaybes $ morbs+  where+    n  = GV.length v++    mark    bv i = UMV.write bv i True+    markAll bv   = mapM_ (mark bv)+++-- | Given the size n, and a list of Cycles, turns the cycles into a+-- cyclic representation of the Permutation.+toCycleRep      :: Enum a => Int -> [[a]] -> Permutation a+toCycleRep n os = Permutation (V.fromList . map V.fromList $ os) (genIndexes n os)+++genIndexes      :: Enum a => Int -> [[a]] -> UV.Vector (Int,Int)+genIndexes n os = UV.create $ do+                                v <- UMV.new n+                                mapM_ (uncurry $ UMV.write v) ixes'+                                pure v+  where+    f i c = zipWith (\x j -> (fromEnum x,(i,j))) c [0..]+    ixes' = concat $ zipWith f [0..] os++++--------------------------------------------------------------------------------+-- * Helper stuff++-- | lens indexing into a vector+ix'   :: (GV.Vector v a, Index (v a) ~ Int, IxValue (v a) ~ a, Ixed (v a))+      => Int -> Lens' (v a) a+ix' i = singular (ix i)
+ src/Data/PlanarGraph.hs view
@@ -0,0 +1,492 @@+{-# LANGUAGE TemplateHaskell #-}+module Data.PlanarGraph( Arc(..)+                       , Direction(..), rev++                       , Dart(..), arc, direction+                       , twin, isPositive++                       , World(..)++                       , Dual++                       , VertexId(..)++                       , PlanarGraph+                       , embedding, vertexData, dartData, faceData+                       , edgeData++                       , planarGraph, planarGraph'++                       , numVertices, numDarts, numEdges, numFaces+                       , darts', darts, edges', edges, vertices', vertices, faces', faces++                       , tailOf, headOf, endPoints+                       , incidentEdges, incomingEdges, outgoingEdges, neighboursOf++                       , vDataOf, eDataOf, fDataOf, endPointDataOf, endPointData+++                       , dual++                       , FaceId(..)+                       , leftFace, rightFace, boundary+                       ) where++import           Control.Lens+import           Control.Monad (forM_)+import           Data.Permutation+import qualified Data.Vector as V+import qualified Data.Foldable as F+import qualified Data.Vector.Mutable as MV++--------------------------------------------------------------------------------++--------------------------------------------------------------------------------+-- $setup+-- >>> :{+-- let dart i s = Dart (Arc i) (read s)+--     (aA:aB:aC:aD:aE:aG:_) = take 6 [Arc 0..]+--     myGraph :: PlanarGraph Test Primal_ () String ()+--     myGraph = planarGraph' [ [ (Dart aA Negative, "a-")+--                              , (Dart aC Positive, "c+")+--                              , (Dart aB Positive, "b+")+--                              , (Dart aA Positive, "a+")+--                              ]+--                            , [ (Dart aE Negative, "e-")+--                              , (Dart aB Negative, "b-")+--                              , (Dart aD Negative, "d-")+--                              , (Dart aG Positive, "g+")+--                              ]+--                            , [ (Dart aE Positive, "e+")+--                              , (Dart aD Positive, "d+")+--                              , (Dart aC Negative, "c-")+--                              ]+--                            , [ (Dart aG Negative, "g-")+--                              ]+--                            ]+-- :}++-- TODO: Add a fig. of the Graph+++--------------------------------------------------------------------------------++-- | An Arc is a directed edge in a planar graph. The type s is used to tie+-- this arc to a particular graph.+newtype Arc s = Arc { _unArc :: Int } deriving (Eq,Ord,Enum,Bounded)++instance Show (Arc s) where+  show (Arc i) = "Arc " ++ show i+++data Direction = Negative | Positive deriving (Eq,Ord,Bounded,Enum)++instance Show Direction where+  show Positive = "+1"+  show Negative = "-1"++instance Read Direction where+  readsPrec _ "-1" = [(Negative,"")]+  readsPrec _ "+1" = [(Positive,"")]+  readsPrec _ _    = []++-- | Reverse the direcion+rev          :: Direction -> Direction+rev Negative = Positive+rev Positive = Negative++-- | A dart represents a bi-directed edge. I.e. a dart has a direction, however+-- the dart of the oposite direction is always present in the planar graph as+-- well.+data Dart s = Dart { _arc       :: !(Arc s)+                   , _direction :: !Direction+                   } deriving (Eq,Ord)+makeLenses ''Dart++++instance Show (Dart s) where+  show (Dart a d) = "Dart (" ++ show a ++ ") " ++ show d++-- | Get the twin of this dart (edge)+--+-- >>> twin (dart 0 "+1")+-- Dart (Arc 0) -1+-- >>> twin (dart 0 "-1")+-- Dart (Arc 0) +1+twin            :: Dart s -> Dart s+twin (Dart a d) = Dart a (rev d)++-- | test if a dart is Positive+isPositive   :: Dart s -> Bool+isPositive d = d^.direction == Positive+++instance Enum (Dart s) where+  toEnum x+    | even x    = Dart (Arc $ x `div` 2)       Positive+    | otherwise = Dart (Arc $ (x `div` 2) + 1) Negative+  -- get the back edge by adding one++  fromEnum (Dart (Arc i) d) = case d of+                                Positive -> 2*i+                                Negative -> 2*i + 1+++-- | The world in which the graph lives+data World = Primal_ | Dual_ deriving (Show,Eq)++type family Dual (sp :: World) where+  Dual Primal_ = Dual_+  Dual Dual_   = Primal_+++-- | A vertex in a planar graph. A vertex is tied to a particular planar graph+-- by the phantom type s, and to a particular world w.+newtype VertexId s (w :: World) = VertexId { _unVertexId :: Int } deriving (Eq,Ord,Enum)+-- VertexId's are in the range 0...|orbits|-1++instance Show (VertexId s w) where+  show (VertexId i) = "VertexId " ++ show i+++--------------------------------------------------------------------------------+-- * The graph type itself++-- | A *connected* Planar graph with bidirected edges. I.e. the edges (darts) are+-- directed, however, for every directed edge, the edge in the oposite+-- direction is also in the graph.+--+-- The types v, e, and f are the are the types of the data associated with the+-- vertices, edges, and faces, respectively.+--+-- The orbits in the embedding are assumed to be in counterclockwise order.+data PlanarGraph s (w :: World) v e f = PlanarGraph { _embedding  :: Permutation (Dart s)+                                                    , _vertexData :: V.Vector v+                                                    , _rawDartData :: V.Vector e+                                                    , _faceData    :: V.Vector f+                                                    }+                                      deriving (Show,Eq)+makeLenses ''PlanarGraph+++-- | lens to access the Dart Data+dartData :: Lens (PlanarGraph s w v e f) (PlanarGraph s w v e' f)+                 (V.Vector (Dart s, e)) (V.Vector (Dart s, e'))+dartData = lens darts (\g xs -> g&rawDartData .~ reorderEdgeData xs)++-- | edgeData is just an alias for 'dartData'+edgeData :: Lens (PlanarGraph s w v e f) (PlanarGraph s w v e' f)+                 (V.Vector (Dart s, e)) (V.Vector (Dart s, e'))+edgeData = dartData+++-- | Reorders the edge data to be in the right order to set edgeData+reorderEdgeData    :: Foldable f => f (Dart s, e) -> V.Vector e+reorderEdgeData ds = V.create $ do+                                  v <- MV.new (F.length ds)+                                  forM_ (F.toList ds) $ \(d,x) ->+                                    MV.write v (fromEnum d) x+                                  pure v+++-- | Construct a planar graph+planarGraph      :: Permutation (Dart s) -> PlanarGraph s Primal_ () () ()+planarGraph perm = PlanarGraph perm vData eData fData+  where+    d = size perm+    e = d `div` 2+    v = V.length (perm^.orbits)+    f = e - v + 2++    vData  = V.replicate v ()+    eData  = V.replicate d ()+    fData  = V.replicate f ()++++-- | Construct a planar graph.+--+planarGraph'    :: [[(Dart s,e)]] -> PlanarGraph s Primal_ () e ()+planarGraph' ds = (planarGraph perm)&dartData .~ (V.fromList . concat $ ds)+  where+    n     = sum . map length $ ds+    perm  = toCycleRep n $ map (map fst) ds++++-- | Get the number of vertices+--+-- >>> numVertices myGraph+-- 4+numVertices :: PlanarGraph s w v e f -> Int+numVertices g = V.length (g^.embedding.orbits)++-- | Get the number of Darts+--+-- >>> numDarts myGraph+-- 12+numDarts :: PlanarGraph s w v e f -> Int+numDarts g = size (g^.embedding)++-- | Get the number of Edges+--+-- >>> numEdges myGraph+-- 6+numEdges :: PlanarGraph s w v e f -> Int+numEdges g = numDarts g `div` 2++-- | Get the number of faces+--+-- >>> numFaces myGraph+-- 4+numFaces   :: PlanarGraph s w v e f -> Int+numFaces g = numEdges g - numVertices g + 2+++-- | Enumerate all vertices+--+-- >>> vertices' myGraph+-- [VertexId 0,VertexId 1,VertexId 2,VertexId 3]+vertices'   :: PlanarGraph s w v e f -> V.Vector (VertexId s w)+vertices' g = VertexId <$> V.enumFromN 0 (V.length (g^.embedding.orbits))++-- | Enumerate all vertices, together with their vertex data++-- >>> vertices myGraph+-- [(VertexId 0,()),(VertexId 1,()),(VertexId 2,()),(VertexId 3,())]+vertices   :: PlanarGraph s w v e f -> V.Vector (VertexId s w, v)+vertices g = V.zip (vertices' g) (g^.vertexData)+++-- | Enumerate all darts+darts' :: PlanarGraph s w v e f -> V.Vector (Dart s)+darts' = elems . _embedding++-- | Get all darts together with their data+--+-- >>> mapM_ print $ darts myGraph+-- (Dart (Arc 0) -1,"a-")+-- (Dart (Arc 2) +1,"c+")+-- (Dart (Arc 1) +1,"b+")+-- (Dart (Arc 0) +1,"a+")+-- (Dart (Arc 4) -1,"e-")+-- (Dart (Arc 1) -1,"b-")+-- (Dart (Arc 3) -1,"d-")+-- (Dart (Arc 5) +1,"g+")+-- (Dart (Arc 4) +1,"e+")+-- (Dart (Arc 3) +1,"d+")+-- (Dart (Arc 2) -1,"c-")+-- (Dart (Arc 5) -1,"g-")+darts   :: PlanarGraph s w v e f -> V.Vector (Dart s, e)+darts g = (\d -> (d,g^.eDataOf d)) <$> darts' g++-- | Enumerate all edges. We report only the Positive darts+edges' :: PlanarGraph s w v e f -> V.Vector (Dart s)+edges' = V.filter isPositive . darts'++-- | Enumerate all edges with their edge data. We report only the Positive+-- darts.+--+-- >>> mapM_ print $ edges myGraph+-- (Dart (Arc 2) +1,"c+")+-- (Dart (Arc 1) +1,"b+")+-- (Dart (Arc 0) +1,"a+")+-- (Dart (Arc 5) +1,"g+")+-- (Dart (Arc 4) +1,"e+")+-- (Dart (Arc 3) +1,"d+")+edges :: PlanarGraph s w v e f -> V.Vector (Dart s, e)+edges = V.filter (isPositive . fst) . darts+++++++-- | The tail of a dart, i.e. the vertex this dart is leaving from+--+tailOf     :: Dart s -> PlanarGraph s w v e f -> VertexId s w+tailOf d g = VertexId . fst $ lookupIdx (g^.embedding) d++-- | The vertex this dart is heading in to+headOf   :: Dart s -> PlanarGraph s w v e f -> VertexId s w+headOf d = tailOf (twin d)++-- | endPoints d g = (tailOf d g, headOf d g)+endPoints :: Dart s -> PlanarGraph s w v e f -> (VertexId s w, VertexId s w)+endPoints d g = (tailOf d g, headOf d g)+++-- | All edges incident to vertex v, in counterclockwise order around v.+incidentEdges                :: VertexId s w -> PlanarGraph s w v e f+                             -> V.Vector (Dart s)+incidentEdges (VertexId v) g = g^.embedding.orbits.ix' v++-- | All incoming edges incident to vertex v, in counterclockwise order around v.+incomingEdges     :: VertexId s w -> PlanarGraph s w v e f -> V.Vector (Dart s)+incomingEdges v g = V.filter (not . isPositive) $ incidentEdges v g++-- | All outgoing edges incident to vertex v, in counterclockwise order around v.+outgoingEdges     :: VertexId s w -> PlanarGraph s w v e f -> V.Vector (Dart s)+outgoingEdges v g = V.filter isPositive $ incidentEdges v g+++-- | Gets the neighbours of a particular vertex, in counterclockwise order+-- around the vertex.+neighboursOf     :: VertexId s w -> PlanarGraph s w v e f -> V.Vector (VertexId s w)+neighboursOf v g = otherVtx <$> incidentEdges v g+  where+    otherVtx d = let u = tailOf d g in if u == v then headOf d g else u++-- outgoingNeighbours :: VertexId s w -> PlanarGraph s w v e f -> V.Vector (VertexId s w)+-- outgoingNeighbours = undefined++-- incomingNeighbours :: VertexId s w -> PlanarGraph s w v e f -> V.Vector (VertexId s w)+-- incomingNeighbours = undefined+++--------------------------------------------------------------------------------+-- * Access data++-- | Get the vertex data associated with a node. Note that updating this data may be+-- expensive!!+vDataOf              :: VertexId s w -> Lens' (PlanarGraph s w v e f) v+vDataOf (VertexId i) = vertexData.ix' i++-- | Edge data of a given dart+eDataOf   :: Dart s -> Lens' (PlanarGraph s w v e f) e+eDataOf d = rawDartData.ix' (fromEnum d)++-- | Data of a face of a given face+fDataOf                       :: FaceId s w -> Lens' (PlanarGraph s w v e f) f+fDataOf (FaceId (VertexId i)) = faceData.ix' i+++-- | Data corresponding to the endpoints of the dart+endPointDataOf   :: Dart s -> Getter (PlanarGraph s w v e f) (v,v)+endPointDataOf d = to $ endPointData d+++-- | Data corresponding to the endpoints of the dart+endPointData     :: Dart s -> PlanarGraph s w v e f -> (v,v)+endPointData d g = let (u,v) = endPoints d g in (g^.vDataOf u, g^.vDataOf v)++--------------------------------------------------------------------------------+-- * The Dual graph++-- | The dual of this graph+--+-- >>> :{+--  let fromList = V.fromList+--      answer = fromList [ fromList [dart 0 "-1"]+--                        , fromList [dart 2 "+1",dart 4 "+1",dart 1 "-1",dart 0 "+1"]+--                        , fromList [dart 1 "+1",dart 3 "-1",dart 2 "-1"]+--                        , fromList [dart 4 "-1",dart 3 "+1",dart 5 "+1",dart 5 "-1"]+--                        ]+--  in (dual myGraph)^.embedding.orbits == answer+-- :}+-- True+dual   :: PlanarGraph s w v e f -> PlanarGraph s (Dual w) f e v+dual g = let perm = g^.embedding+         in PlanarGraph (cycleRep (elems perm) (apply perm . twin))+                        (g^.faceData)+                        (g^.rawDartData)+                        (g^.vertexData)++-- | A face+newtype FaceId s w = FaceId { _unFaceId :: VertexId s (Dual w) } deriving (Eq,Ord)++instance Show (FaceId s w) where+  show (FaceId (VertexId i)) = "FaceId " ++ show i++-- | Enumerate all faces in the planar graph+faces' :: PlanarGraph s w v e f -> V.Vector (FaceId s w)+faces' = fmap FaceId . vertices' . dual++-- | All faces with their face data.+faces   :: PlanarGraph s w v e f -> V.Vector (FaceId s w, f)+faces g = V.zip (faces' g) (g^.faceData)++-- | The face to the left of the dart+--+-- >>> leftFace (dart 1 "+1") myGraph+-- FaceId 1+-- >>> leftFace (dart 1 "-1") myGraph+-- FaceId 2+-- >>> leftFace (dart 2 "+1") myGraph+-- FaceId 2+-- >>> leftFace (dart 0 "+1") myGraph+-- FaceId 0+leftFace     :: Dart s -> PlanarGraph s w v e f -> FaceId s w+leftFace d g = FaceId . headOf d $ dual g+++-- | The face to the right of the dart+--+-- >>> rightFace (dart 1 "+1") myGraph+-- FaceId 2+-- >>> rightFace (dart 1 "-1") myGraph+-- FaceId 1+-- >>> rightFace (dart 2 "+1") myGraph+-- FaceId 1+-- >>> rightFace (dart 0 "+1") myGraph+-- FaceId 1+rightFace     :: Dart s -> PlanarGraph s w v e f -> FaceId s w+rightFace d g = FaceId . tailOf d $ dual g+++-- | The darts bounding this face, for internal faces in clockwise order, for+-- the outer face in counter clockwise order.+--+--+boundary     :: FaceId s w -> PlanarGraph s w v e f -> V.Vector (Dart s)+boundary (FaceId v) g = incidentEdges v $ dual g+++++--------------------------------------------------------------------------------+-- Testing stuff++testPerm :: Permutation (Dart s)+testPerm = let (a:b:c:d:e:g:_) = take 6 [Arc 0..]+           in toCycleRep 12 [ [ Dart a Negative+                              , Dart c Positive+                              , Dart b Positive+                              , Dart a Positive+                              ]+                            , [ Dart e Negative+                              , Dart b Negative+                              , Dart d Negative+                              , Dart g Positive+                              ]+                            , [ Dart e Positive+                              , Dart d Positive+                              , Dart c Negative+                              ]+                            , [ Dart g Negative+                              ]+                            ]++data Test++testG :: PlanarGraph Test Primal_ () String ()+testG = planarGraph' [ [ (Dart aA Negative, "a-")+                       , (Dart aC Positive, "c+")+                       , (Dart aB Positive, "b+")+                       , (Dart aA Positive, "a+")+                       ]+                     , [ (Dart aE Negative, "e-")+                       , (Dart aB Negative, "b-")+                       , (Dart aD Negative, "d-")+                       , (Dart aG Positive, "g+")+                       ]+                     , [ (Dart aE Positive, "e+")+                       , (Dart aD Positive, "d+")+                       , (Dart aC Negative, "c-")+                       ]+                     , [ (Dart aG Negative, "g-")+                       ]+                     ]+  where+    (aA:aB:aC:aD:aE:aG:_) = take 6 [Arc 0..]
+ src/Data/PlaneGraph.hs view
@@ -0,0 +1,24 @@+module Data.PlaneGraph( module Data.PlanarGraph+                      , PlaneGraph++                      , withEdgeDistances+                      ) where++import Data.Ext+import Control.Lens+import Data.PlanarGraph+import Data.Geometry.Point+++--------------------------------------------------------------------------------++type PlaneGraph s w v e f r = PlanarGraph s w (Point 2 r :+ v) e f+++-- | Labels the edges of a plane graph with their distances, as specified by+-- the distance function.+withEdgeDistances     :: (Point 2 r ->  Point 2 r -> a)+                      -> PlaneGraph s w p e f r -> PlaneGraph s w p (a :+ e) f r+withEdgeDistances f g = g&dartData %~ fmap (\(d,x) -> (d,len d :+ x))+  where+    len d = uncurry f . over both (^.core) $ endPointData d g
+ src/Data/Range.hs view
@@ -0,0 +1,202 @@+{-# LANGUAGE TemplateHaskell   #-}+module Data.Range( EndPoint(..)+                 , isOpen, isClosed+                 , unEndPoint+                 , Range(..)+                 , prettyShow+                 , lower, upper+                 , pattern OpenRange, pattern ClosedRange, pattern Range'+                 , inRange, width, clipLower, clipUpper, midPoint+                 , isValid++                 , shiftLeft, shiftRight+                 ) where++import           Control.Applicative+import           Control.Arrow((&&&))+import           Control.Lens+import qualified Data.Foldable as F+import           Data.Geometry.Properties+import qualified Data.Traversable as T++--------------------------------------------------------------------------------+++data EndPoint a = Open   a+                | Closed a+                deriving (Show,Read,Eq,Functor,F.Foldable,T.Traversable)++_unEndPoint            :: EndPoint a -> a+_unEndPoint (Open a)   = a+_unEndPoint (Closed a) = a++unEndPoint :: Lens (EndPoint a) (EndPoint b) a b+unEndPoint = lens _unEndPoint f+  where+    f (Open _) a   = Open a+    f (Closed _) a = Closed a+++isOpen          :: EndPoint a -> Bool+isOpen (Open _) = True+isOpen _        = False++isClosed :: EndPoint a -> Bool+isClosed = not . isOpen+++--------------------------------------------------------------------------------++data Range a = Range { _lower :: EndPoint a+                     , _upper :: EndPoint a+                     }+               deriving (Show,Read,Eq,Functor,F.Foldable,T.Traversable)++makeLenses ''Range++pattern OpenRange   l u = Range (Open l)   (Open u)+pattern ClosedRange l u = Range (Closed l) (Closed u)++-- | A range from l to u, ignoring/forgetting the type of the enpoints+pattern Range' l u <- (_lower &&& _upper -> (l,u))++++prettyShow             :: Show a => Range a -> String+prettyShow (Range l u) = concat [ lowerB, show (l^.unEndPoint), ", "+                                , show (u^.unEndPoint), upperB+                                ]+  where+    lowerB = if isOpen l then "(" else "["+    upperB = if isOpen u then ")" else "]"++++-- | Test if a value lies in a range.+--+-- >>> 1 `inRange` (OpenRange 0 2)+-- True+-- >>> 1 `inRange` (OpenRange 0 1)+-- False+-- >>> 1 `inRange` (ClosedRange 0 1)+-- True+-- >>> 1 `inRange` (ClosedRange 1 1)+-- True+-- >>> 10 `inRange` (OpenRange 1 10)+-- False+-- >>> 10 `inRange` (ClosedRange 0 1)+-- False+inRange                 :: Ord a => a -> Range a -> Bool+x `inRange` (Range l u) = case ((l^.unEndPoint) `compare` x, x `compare` (u^.unEndPoint)) of+    (_, GT) -> False+    (GT, _) -> False+    (LT,LT) -> True+    (LT,EQ) -> include u -- depends on only u+    (EQ,LT) -> include l -- depends on only l+    (EQ,EQ) -> include l || include u -- depends on l and u+  where+    include = isClosed++type instance IntersectionOf (Range a) (Range a) = [ NoIntersection, Range a]++instance Ord a => (Range a) `IsIntersectableWith` (Range a) where++  nonEmptyIntersection = defaultNonEmptyIntersection++  -- The intersection is empty, if after clipping, the order of the end points is inverted+  -- or if the endpoints are the same, but both are open.+  (Range l u) `intersect` s = let i = clipLower' l . clipUpper' u $ s+                              in if isValid i then coRec i else coRec NoIntersection++-- | Get the width of the interval+--+-- >>> width $ ClosedRange 1 10+-- 9+-- >>> width $ OpenRange 5 10+-- 5+width   :: Num r => Range r -> r+width i = i^.upper.unEndPoint - i^.lower.unEndPoint++midPoint   :: Fractional r => Range r -> r+midPoint r = let w = width r in r^.lower.unEndPoint + (w / 2)+++--------------------------------------------------------------------------------+-- * Helper functions++-- | Clip the interval from below. I.e. intersect with the interval {l,infty),+-- where { is either open, (, orr closed, [.+clipLower     :: Ord a => EndPoint a -> Range a -> Maybe (Range a)+clipLower l r = let r' = clipLower' l r in if isValid r' then Just r' else Nothing++-- | Clip the interval from above. I.e. intersect with (-\infty, u}, where } is+-- either open, ), or closed, ],+clipUpper     :: Ord a => EndPoint a -> Range a -> Maybe (Range a)+clipUpper u r = let r' = clipUpper' u r in if isValid r' then Just r' else Nothing+++-- | Check if the range is valid and nonEmpty, i.e. if the lower endpoint is+-- indeed smaller than the right endpoint. Note that we treat empty open-ranges+-- as invalid as well.+isValid             :: Ord a => Range a -> Bool+isValid (Range l u) = case (_unEndPoint l) `compare` (_unEndPoint u) of+                          LT                            -> True+                          EQ | isClosed l || isClosed u -> True+                          _                             -> False++-- operation is unsafe, as it may produce an invalid range (where l > u)+clipLower'                  :: Ord a => EndPoint a -> Range a -> Range a+clipLower' l' r@(Range l u) = case l' `cmpLower` l of+                                GT -> Range l' u+                                _  -> r+-- operation is unsafe, as it may produce an invalid range (where l > u)+clipUpper'                  :: Ord a => EndPoint a -> Range a -> Range a+clipUpper' u' r@(Range l u) = case u' `cmpUpper` u of+                                LT -> Range l u'+                                _  -> r++-- | Compare end points, Closed < Open+cmpLower     :: Ord a => EndPoint a -> EndPoint a -> Ordering+cmpLower a b = case (_unEndPoint a) `compare` (_unEndPoint b) of+                 LT -> LT+                 GT -> GT+                 EQ -> case (a,b) of+                         (Open _,   Open _)   -> EQ  -- if both are same type, report EQ+                         (Closed _, Closed _) -> EQ+                         (Open _,  _)         -> GT  -- otherwise, choose the Closed one+                         (Closed _,_)         -> LT  -- is the *smallest*+++-- | Compare the end points, Open < Closed+cmpUpper     :: Ord a => EndPoint a -> EndPoint a -> Ordering+cmpUpper a b = case (_unEndPoint a) `compare` (_unEndPoint b) of+                 LT -> LT+                 GT -> GT+                 EQ -> case (a,b) of+                         (Open _,   Open _)   -> EQ  -- if both are same type, report EQ+                         (Closed _, Closed _) -> EQ+                         (Open _,  _)         -> LT  -- otherwise, choose the Closed one+                         (Closed _,_)         -> GT  -- is the *largest*+++++--------------------------------------------------------------------------------++-- | Shift a range x units to the left+--+-- >>> prettyShow $ shiftLeft 10 (ClosedRange 10 20)+-- "[0, 10]"+-- >>> prettyShow $ shiftLeft 10 (OpenRange 15 25)+-- "(5, 15)"+shiftLeft   :: Num r => r -> Range r -> Range r+shiftLeft x = shiftRight (-x)++-- | Shifts the range to the right+--+-- >>> prettyShow $ shiftRight 10 (ClosedRange 10 20)+-- "[20, 30]"+-- >>> prettyShow $ shiftRight 10 (OpenRange 15 25)+-- "(25, 35)"+shiftRight   :: Num r => r -> Range r -> Range r+shiftRight x = fmap (+x)
src/Data/Seq2.hs view
@@ -1,16 +1,20 @@ module Data.Seq2 where -import           Prelude hiding (foldr,foldl,head,tail,last)- import           Control.Applicative-import           Data.List.NonEmpty+import           Control.Lens ((%~), (&), (<&>), (^?), Lens', lens)+import           Control.Lens.At (Ixed(..), Index, IxValue)+import qualified Data.List.NonEmpty as NonEmpty+import           Data.Maybe (fromJust) import           Data.Semigroup+import           Prelude hiding (foldr,foldl,head,tail,last,length)   import qualified Data.Traversable as T import qualified Data.Foldable as F import qualified Data.Sequence as S +--------------------------------------------------------------------------------+ -- | Basically Data.Sequence but with the guarantee that the list contains at -- least two elements. data Seq2 a = Seq2 a (S.Seq a) a@@ -26,29 +30,36 @@  instance F.Foldable Seq2 where   foldMap = T.foldMapDefault+  length ~(Seq2 _ s _) = 2 + S.length s  instance Semigroup (Seq2 a) where   l <> r = l >< r +type instance Index (Seq2 a)   = Int+type instance IxValue (Seq2 a) = a+instance Ixed (Seq2 a) where+  ix i f s@(Seq2 l m r)+    | i == 0      = f l                 <&> \a -> Seq2 a m r+    | i < 1 + mz  = f (S.index m (i-1)) <&> \a -> Seq2 l (S.update (i-1) a m) r+    | i == mz + 1 = f r                 <&> \a -> Seq2 l m a+    | otherwise   = pure s+      where+        mz = S.length m+ duo     :: a -> a -> Seq2 a duo a b = Seq2 a S.empty b -length               :: Seq2 a -> Int-length ~(Seq2 _ s _) = 2 + S.length s-- -- | get the element with index i, counting from the left and starting at 0. -- O(log(min(i,n-i)))-index                 :: Seq2 a -> Int -> a-index ~(Seq2 l s r) i-  | i == 0      = l-  | i < 1 + sz  = S.index s (i+1)-  | i == sz + 1 = r-  | otherwise   = error "index: index out of bounds."-    where-      sz = S.length s+index     :: Seq2 a -> Int -> a+index s i = fromJust $ s^?ix i +adjust       :: (a -> a) -> Int -> Seq2 a -> Seq2 a+adjust f i s = s&ix i %~ f +++ (<|) :: a -> Seq2 a -> Seq2 a x <| ~(Seq2 l s r) = Seq2 x (l S.<| s) r @@ -58,7 +69,8 @@   -- | Concatenate two sequences. O(log(min(n1,n2)))-~(Seq2 ll ls lr) >< ~(Seq2 rl rs rr) = Seq2 ll ((ls S.|> lr) S.>< (rl S.<| rs)) rr+(><) :: Seq2 a -> Seq2 a -> Seq2 a+s >< l = fromSeqUnsafe $ toSeq s S.>< toSeq l   -- | pre: the list contains at least two elements@@ -66,6 +78,36 @@ fromList (a:b:xs) = F.foldl' (\s x -> s |> x) (duo a b) xs fromList _        = error "Seq2.fromList: Not enough values" +++-- | fmap but with an index+mapWithIndex                  :: (Int -> a -> b) -> Seq2 a -> Seq2 b+mapWithIndex f s@(Seq2 a m b) = Seq2 (f 0 a) (S.mapWithIndex f' m) (f l b)+  where+    l    = F.length s - 1+    f' i = f (i+1)+++take   :: Int -> Seq2 a -> S.Seq a+take i = S.take i . toSeq+++drop   :: Int -> Seq2 a -> S.Seq a+drop i = S.drop i . toSeq+++toSeq               :: Seq2 a -> S.Seq a+toSeq ~(Seq2 a m b) = ((a S.<| m) S.|> b)+++-- | Convert a Seq into a Seq2. It is not checked that the length is at least two+fromSeqUnsafe   :: S.Seq a -> Seq2 a+fromSeqUnsafe s = Seq2 a m b+  where+    ~(a S.:< s') = S.viewl s+    ~(m S.:> b)  = S.viewr s'++ -------------------------------------------------------------------------------- -- | Left views @@ -80,8 +122,12 @@  instance F.Foldable ViewL2 where   foldMap = T.foldMapDefault+  length ~(_ :<< s) = 1 + F.length s  +++ -- | At least one element data ViewL1 a = a :< S.Seq a deriving (Show,Read,Eq,Ord) @@ -93,16 +139,25 @@  instance F.Foldable ViewL1 where   foldMap = T.foldMapDefault+  length ~(_ :< s) = 1 + S.length s  -- | We throw away information here; namely that the combined list contains two elements. instance Semigroup (ViewL1 a) where   ~(a :< s) <> ~(b :< t) = a :< (s <> S.singleton b <> t)  -toNonEmpty          :: ViewL1 a -> NonEmpty a-toNonEmpty ~(a :< s) = (a :| F.toList s)+headL1 :: Lens' (ViewL1 a) a+headL1 = lens (\(l :< _) -> l) (\(_ :< s) l -> l :< s)  ++toNonEmpty           :: ViewL1 a -> NonEmpty.NonEmpty a+toNonEmpty ~(a :< s) = (a NonEmpty.:| F.toList s)++viewL1FromNonEmpty                     :: NonEmpty.NonEmpty a -> ViewL1 a+viewL1FromNonEmpty ~(x NonEmpty.:| xs) = x :< S.fromList xs++ -- | O(1) get a left view viewl                 :: Seq2 a -> ViewL2 a viewl ~(Seq2 l s r) = l :<< (s :> r)@@ -111,6 +166,10 @@ l1Singleton :: a -> ViewL1 a l1Singleton = (:< S.empty) +viewL1toR1           :: ViewL1 a -> ViewR1 a+viewL1toR1 ~(l :< s) = let (s' S.:> r) = S.viewr (l S.<| s) in s' :> r++ -------------------------------------------------------------------------------- -- | Right views @@ -126,7 +185,7 @@  instance F.Foldable ViewR2 where   foldMap = T.foldMapDefault-+  length (s :>> _) = 1 + F.length s  -- | A view of the right end of the sequence, with the guarantee that it has at -- least one element.@@ -140,6 +199,7 @@  instance F.Foldable ViewR1 where   foldMap = T.foldMapDefault+  length (s :> _) = 1 + S.length s   -- | O(1) get a right view
+ src/Data/Sequence/Util.hs view
@@ -0,0 +1,57 @@+module Data.Sequence.Util where++import Data.Sequence(Seq, ViewL(..),ViewR(..))+import qualified Data.Sequence as S++--------------------------------------------------------------------------------++-- | Get the index h such that everything strictly smaller than h has: p i =+-- False, and all i >= h, we have p h = True+--+-- returns Nothing if no element satisfies p+--+-- running time: $O(\log^2 n + T*\log n)$, where $T$ is the time to execute the+-- predicate.+binarySearchSeq     :: (a -> Bool) -> Seq a -> Maybe Int+binarySearchSeq p s = case S.viewr s of+                       EmptyR                 -> Nothing+                       (_ :> x)   | p x       -> Just $ case S.viewl s of+                         (y :< _) | p y          -> 0+                         _                       -> binarySearch p' 0 u+                                  | otherwise -> Nothing+  where+    p' = p . S.index s+    u  = S.length s - 1+++-- | Partition the seq s given a monotone predicate p into (xs,ys) such that+--+-- all elements in xs do *not* satisfy the predicate p+-- all elements in ys do       satisfy the predicate p+--+-- all elements in s occur in either xs or ys.+--+-- running time: $O(\log^2 n + T*\log n)$, where $T$ is the time to execute the+-- predicate.+splitMonotone     :: (a -> Bool) -> Seq a -> (Seq a, Seq a)+splitMonotone p s = case binarySearchSeq p s of+                      Nothing -> (s,S.empty)+                      Just i  -> S.splitAt i s+++-- | Given a monotonic predicate p, a lower bound l, and an upper bound u, with:+--  p l = False+--  p u = True+--  l < u.+--+-- Get the index h such that everything strictly smaller than h has: p i =+-- False, and all i >= h, we have p h = True+--+-- running time: $O(\log(u - l))$+{-# SPECIALIZE binarySearch :: (Int -> Bool) -> Int -> Int -> Int #-}+binarySearch       :: Integral a => (a -> Bool) -> a -> a -> a+binarySearch p l u = let d = u - l+                         m = l + (d `div` 2)+                     in if d == 1 then u else+                          if p m then binarySearch p l m+                                 else binarySearch p m u
+ src/Data/UnBounded.hs view
@@ -0,0 +1,114 @@+{-# LANGUAGE TemplateHaskell   #-}+module Data.UnBounded( Top, topToMaybe+                     , pattern ValT, pattern Top++                     , Bottom, bottomToMaybe+                     , pattern Bottom, pattern ValB++                     , UnBounded(..)+                     , unUnBounded+                     , unBoundedToMaybe+                     ) where++import           Control.Applicative+import           Control.Lens+import qualified Data.Foldable as F+import qualified Data.Traversable as T+++--------------------------------------------------------------------------------+-- * Top and Bottom++-- | `Top a` represents the type a, together with a 'Top' element, i.e. an element+-- that is greater than any other element. We can think of `Top a` being defined as:+--+-- >>> data Top a = ValT a | Top+newtype Top a = GTop { topToMaybe :: Maybe a }+                deriving (Eq,Functor,F.Foldable,T.Traversable,Applicative,Monad)++pattern ValT x = GTop (Just x)+pattern Top    = GTop Nothing++instance Ord a => Ord (Top a) where+  Top       `compare` Top      = EQ+  _         `compare` Top      = LT+  Top       `compare` _        = GT+  ~(ValT x) `compare` ~(ValT y) = x `compare` y++instance Show a => Show (Top a) where+  show Top       = "Top"+  show ~(ValT x) = "ValT " ++ show x++--------------------------------------------------------------------------------++-- | `Bottom a` represents the type a, together with a 'Bottom' element,+-- i.e. an element that is smaller than any other element. We can think of+-- `Bottom a` being defined as:+--+-- >>> data Bottom a = ValB+newtype Bottom a = GBottom { bottomToMaybe :: Maybe a }+                 deriving (Eq,Ord,Functor,F.Foldable,T.Traversable,Applicative,Monad)++pattern Bottom = GBottom Nothing+pattern ValB x = GBottom (Just x)++instance Show a => Show (Bottom a) where+  show Bottom    = "Bottom"+  show ~(ValB x) = "ValB " ++ show x++--------------------------------------------------------------------------------++-- | `UnBounded a` represents the type a, together with an element+-- `MaxInfinity` larger than any other element, and an element `MinInfinity`,+-- smaller than any other element.+data UnBounded a = MinInfinity | Val { _unUnBounded :: a }  | MaxInfinity+                 deriving (Eq,Ord,Functor,F.Foldable,T.Traversable)++makeLenses ''UnBounded++instance Show a => Show (UnBounded a) where+  show MinInfinity = "MinInfinity"+  show (Val x)     = "Val " ++ show x+  show MaxInfinity = "MaxInfinity"++instance Num a => Num (UnBounded a) where+  MinInfinity + _           = MinInfinity+  _           + MinInfinity = MinInfinity+  (Val x)     + (Val y)     = Val $ x + y+  _           + MaxInfinity = MaxInfinity+  MaxInfinity + _           = MaxInfinity+++  MinInfinity * _           = MinInfinity+  _           * MinInfinity = MinInfinity++  (Val x)     * (Val y)     = Val $ x * y+  _           * MaxInfinity = MaxInfinity+  MaxInfinity * _           = MaxInfinity++  abs MinInfinity = MinInfinity+  abs (Val x)     = Val $ abs x+  abs MaxInfinity = MaxInfinity++  signum MinInfinity = -1+  signum (Val x)     = Val $ signum x+  signum MaxInfinity = 1++  fromInteger = Val . fromInteger++  negate MinInfinity = MaxInfinity+  negate (Val x)     = Val $ negate x+  negate MaxInfinity = MinInfinity++instance Fractional a => Fractional (UnBounded a) where+  MinInfinity / _       = MinInfinity+  (Val x)     / (Val y) = Val $ x / y+  (Val _)     / _       = 0+  MaxInfinity / _       = MaxInfinity++  fromRational = Val . fromRational+++unBoundedToMaybe         :: UnBounded a -> Maybe a+unBoundedToMaybe (Val x) = Just x+unBoundedToMaybe _       = Nothing
+ test/Algorithms/Geometry/DelaunayTriangulation/DTSpec.hs view
@@ -0,0 +1,118 @@+{-# LANGUAGE LambdaCase #-}+module Algorithms.Geometry.DelaunayTriangulation.DTSpec where++import Util++import Test.Hspec+import Control.Lens+import Data.Geometry+import Data.Maybe(mapMaybe, fromJust)+import Algorithms.Geometry.DelaunayTriangulation.Types+import qualified Algorithms.Geometry.DelaunayTriangulation.DivideAndConqueror as DC+import qualified Algorithms.Geometry.DelaunayTriangulation.Naive              as Naive+import qualified Data.List.NonEmpty as NonEmpty+import Data.Geometry.Ipe+import Data.Ext+import Data.Traversable(traverse)+import qualified Data.CircularList.Util as CU+import qualified Data.Map as M+import qualified Data.Vector as V+++dtEdges :: (Fractional r, Ord r)+        => NonEmpty.NonEmpty (Point 2 r :+ p) -> [(VertexID, VertexID)]+dtEdges = tEdges . DC.delaunayTriangulation++take'   :: Int -> NonEmpty.NonEmpty a -> NonEmpty.NonEmpty a+take' i = NonEmpty.fromList . NonEmpty.take i++--------------------------------------------------------------------------------++spec :: Spec+spec = do+  describe "Testing Divide and Conqueror Algorithm for Delaunay Triangulation" $ do+    it "singleton " $ do+      dtEdges (take' 1 myPoints) `shouldBe` []+    toSpec (TestCase "myPoints" myPoints)+    toSpec (TestCase "myPoints'" myPoints')+    ipeSpec++ipeSpec :: Spec+ipeSpec = testCases "test/Algorithms/Geometry/SmallestEnclosingDisk/manual.ipe"++testCases    :: FilePath -> Spec+testCases fp = (runIO $ readInput fp) >>= \case+    Left e    -> it "reading Delaunay Triangulation disk file" $+                   expectationFailure $ "Failed to read ipe file " ++ show e+    Right tcs -> mapM_ toSpec tcs+++-- | Point sets per color, Crosses form the solution+readInput    :: FilePath -> IO (Either ConversionError [TestCase Rational])+readInput fp = fmap f <$> readSinglePageFile fp+  where+    f page = [ TestCase "?" $ fmap (\p -> p^.core.symbolPoint :+ ()) pSet+             | pSet <- byStrokeColour' syms+             ]+      where+        syms = page^..content.traverse._IpeUse+        byStrokeColour' = mapMaybe NonEmpty.nonEmpty . byStrokeColour++++data TestCase r = TestCase { _color    :: String+                           , _pointSet :: NonEmpty.NonEmpty (Point 2 r :+ ())+                           } deriving (Show,Eq)+++toSpec                    :: (Fractional r, Ord r, Show r) => TestCase r -> Spec+toSpec (TestCase c pts) = describe ("testing on " ++ c ++ " points") $ do+                            sameAsNaive c pts++sameAsNaive       :: (Fractional r, Ord r, Show p, Show r)+                  => String -> NonEmpty.NonEmpty (Point 2 r :+ p) -> Spec+sameAsNaive s pts = it ("Divide And Conqueror same answer as Naive on " ++ s) $+                      (Naive.delaunayTriangulation pts+                       `sameEdges`+                       DC.delaunayTriangulation pts) `shouldBe` True+++sameEdges             :: Triangulation p r -> Triangulation p r -> Bool+triA `sameEdges` triB = all sameAdj . M.assocs $ mapping'+  where+    sameAdj (a, b) = (f $ adjA V.! a) `CU.isShiftOf` (adjB V.! b)++    adjA = triA^.neighbours+    adjB = triB^.neighbours++    mapping' = M.fromList $ zip (M.elems $ triA^.vertexIds) (M.elems $ triB^.vertexIds)++    f = fmap (fromJust . flip M.lookup mapping')++myPoints :: NonEmpty.NonEmpty (Point 2 Rational :+ ())+myPoints = NonEmpty.fromList . map ext $+           [ point2 1  3+           , point2 4  26+           , point2 5  17+           , point2 6  7+           , point2 12 16+           , point2 19 4+           , point2 20 0+           , point2 20 11+           , point2 23 23+           , point2 31 14+           , point2 33 5+           ]++myPoints' :: NonEmpty.NonEmpty (Point 2 Rational :+ ())+myPoints' = NonEmpty.fromList . map ext $+            [ point2 64  736+            , point2 96 688+            , point2 128 752+            , point2 160 704+            , point2 128 672+            , point2 64 656+            , point2 192 736+            , point2 208 704+            , point2 192 672+            ]
+ test/Algorithms/Geometry/SmallestEnclosingDisk/RISpec.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE LambdaCase #-}+module Algorithms.Geometry.SmallestEnclosingDisk.RISpec where++import Util++import Control.Monad(when)+import System.Random(mkStdGen)+import Control.Lens+import Data.Ext+import Data.Maybe+import Data.Proxy+import Test.Hspec+import Data.Geometry+import Data.Geometry.Ball(fromDiameter, disk, Disk)+import Data.Geometry.Ipe++import Algorithms.Geometry.SmallestEnclosingBall.Types+import qualified Algorithms.Geometry.SmallestEnclosingBall.RandomizedIncrementalConstruction as RIC+import qualified Algorithms.Geometry.SmallestEnclosingBall.Naive as Naive+++spec :: Spec+spec = testCases "test/Algorithms/Geometry/SmallestEnclosingDisk/manual.ipe"++testCases    :: FilePath -> Spec+testCases fp = (runIO $ readInput fp) >>= \case+    Left e    -> it "reading Smallest enclosing disk file" $+                   expectationFailure $ "Failed to read ipe file " ++ show e+    Right tcs -> mapM_ toSpec tcs+++data TestCase r = TestCase { _pointSet :: [Point 2 r :+ ()]+                           , _solution :: Maybe (TwoOrThree (Point 2 r))+                           }+                  deriving (Show,Eq)+++toSpec                    :: (Fractional r, Ord r, Show r) => TestCase r -> Spec+toSpec (TestCase pts sol) =+    describe ("testing point set with solution " ++ show sol) $ do+      it "comparing with naive solution" $+        ((RIC.smallestEnclosingDisk (mkStdGen 2123) pts)^.enclosingDisk)+        `shouldBe`+        ((Naive.smallestEnclosingDisk pts)^.enclosingDisk)+      when (isJust sol) $+        it "manal solution" $+          ((RIC.smallestEnclosingDisk (mkStdGen 5) pts)^.enclosingDisk)+          `shouldBe`+          (diskOf $ fromJust sol)+++diskOf               :: (Fractional r, Eq r)+                     => TwoOrThree (Point 2 r) -> Disk () r+diskOf (Two p q)     = fromDiameter p q+diskOf (Three p q r) = fromMaybe (error "Wrong manual disk") $ disk p q r+++-- | Point sets per color, Crosses form the solution+readInput    :: FilePath -> IO (Either ConversionError [TestCase Rational])+readInput fp = fmap f <$> readSinglePageFile fp+  where+    f page = [ TestCase [p^.core.symbolPoint :+ () | p <- pSet] (solutionOf pSet)+             | pSet <- byStrokeColour syms+             ]+      where+        syms = page^..content.traverse._IpeUse++        -- | Crosses form a solution+        isInSolution s = s^.core.symbolName == "mark/cross(sx)"++        right = either (const Nothing) Just+        solutionOf = right . fromList . map (^.core.symbolPoint) . filter isInSolution
+ test/Algorithms/Geometry/SmallestEnclosingDisk/manual.ipe view
@@ -0,0 +1,369 @@+<?xml version="1.0"?>+<!DOCTYPE ipe SYSTEM "ipe.dtd">+<ipe version="70107" creator="Ipe 7.1.7">+<info created="D:20151023214323" modified="D:20160123195914"/>+<ipestyle name="basic">+<symbol name="arrow/arc(spx)">+<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">+0 0 m+-1 0.333 l+-1 -0.333 l+h+</path>+</symbol>+<symbol name="arrow/farc(spx)">+<path stroke="sym-stroke" fill="white" pen="sym-pen">+0 0 m+-1 0.333 l+-1 -0.333 l+h+</path>+</symbol>+<symbol name="arrow/ptarc(spx)">+<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">+0 0 m+-1 0.333 l+-0.8 0 l+-1 -0.333 l+h+</path>+</symbol>+<symbol name="arrow/fptarc(spx)">+<path stroke="sym-stroke" fill="white" pen="sym-pen">+0 0 m+-1 0.333 l+-0.8 0 l+-1 -0.333 l+h+</path>+</symbol>+<symbol name="mark/circle(sx)" transformations="translations">+<path fill="sym-stroke">+0.6 0 0 0.6 0 0 e+0.4 0 0 0.4 0 0 e+</path>+</symbol>+<symbol name="mark/disk(sx)" transformations="translations">+<path fill="sym-stroke">+0.6 0 0 0.6 0 0 e+</path>+</symbol>+<symbol name="mark/fdisk(sfx)" transformations="translations">+<group>+<path fill="sym-fill">+0.5 0 0 0.5 0 0 e+</path>+<path fill="sym-stroke" fillrule="eofill">+0.6 0 0 0.6 0 0 e+0.4 0 0 0.4 0 0 e+</path>+</group>+</symbol>+<symbol name="mark/box(sx)" transformations="translations">+<path fill="sym-stroke" fillrule="eofill">+-0.6 -0.6 m+0.6 -0.6 l+0.6 0.6 l+-0.6 0.6 l+h+-0.4 -0.4 m+0.4 -0.4 l+0.4 0.4 l+-0.4 0.4 l+h+</path>+</symbol>+<symbol name="mark/square(sx)" transformations="translations">+<path fill="sym-stroke">+-0.6 -0.6 m+0.6 -0.6 l+0.6 0.6 l+-0.6 0.6 l+h+</path>+</symbol>+<symbol name="mark/fsquare(sfx)" transformations="translations">+<group>+<path fill="sym-fill">+-0.5 -0.5 m+0.5 -0.5 l+0.5 0.5 l+-0.5 0.5 l+h+</path>+<path fill="sym-stroke" fillrule="eofill">+-0.6 -0.6 m+0.6 -0.6 l+0.6 0.6 l+-0.6 0.6 l+h+-0.4 -0.4 m+0.4 -0.4 l+0.4 0.4 l+-0.4 0.4 l+h+</path>+</group>+</symbol>+<symbol name="mark/cross(sx)" transformations="translations">+<group>+<path fill="sym-stroke">+-0.43 -0.57 m+0.57 0.43 l+0.43 0.57 l+-0.57 -0.43 l+h+</path>+<path fill="sym-stroke">+-0.43 0.57 m+0.57 -0.43 l+0.43 -0.57 l+-0.57 0.43 l+h+</path>+</group>+</symbol>+<symbol name="arrow/fnormal(spx)">+<path stroke="sym-stroke" fill="white" pen="sym-pen">+0 0 m+-1 0.333 l+-1 -0.333 l+h+</path>+</symbol>+<symbol name="arrow/pointed(spx)">+<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">+0 0 m+-1 0.333 l+-0.8 0 l+-1 -0.333 l+h+</path>+</symbol>+<symbol name="arrow/fpointed(spx)">+<path stroke="sym-stroke" fill="white" pen="sym-pen">+0 0 m+-1 0.333 l+-0.8 0 l+-1 -0.333 l+h+</path>+</symbol>+<symbol name="arrow/linear(spx)">+<path stroke="sym-stroke" pen="sym-pen">+-1 0.333 m+0 0 l+-1 -0.333 l+</path>+</symbol>+<symbol name="arrow/fdouble(spx)">+<path stroke="sym-stroke" fill="white" pen="sym-pen">+0 0 m+-1 0.333 l+-1 -0.333 l+h+-1 0 m+-2 0.333 l+-2 -0.333 l+h+</path>+</symbol>+<symbol name="arrow/double(spx)">+<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">+0 0 m+-1 0.333 l+-1 -0.333 l+h+-1 0 m+-2 0.333 l+-2 -0.333 l+h+</path>+</symbol>+<pen name="heavier" value="0.8"/>+<pen name="fat" value="1.2"/>+<pen name="ultrafat" value="2"/>+<symbolsize name="large" value="5"/>+<symbolsize name="small" value="2"/>+<symbolsize name="tiny" value="1.1"/>+<arrowsize name="large" value="10"/>+<arrowsize name="small" value="5"/>+<arrowsize name="tiny" value="3"/>+<color name="red" value="1 0 0"/>+<color name="green" value="0 1 0"/>+<color name="blue" value="0 0 1"/>+<color name="yellow" value="1 1 0"/>+<color name="orange" value="1 0.647 0"/>+<color name="gold" value="1 0.843 0"/>+<color name="purple" value="0.627 0.125 0.941"/>+<color name="gray" value="0.745"/>+<color name="brown" value="0.647 0.165 0.165"/>+<color name="navy" value="0 0 0.502"/>+<color name="pink" value="1 0.753 0.796"/>+<color name="seagreen" value="0.18 0.545 0.341"/>+<color name="turquoise" value="0.251 0.878 0.816"/>+<color name="violet" value="0.933 0.51 0.933"/>+<color name="darkblue" value="0 0 0.545"/>+<color name="darkcyan" value="0 0.545 0.545"/>+<color name="darkgray" value="0.663"/>+<color name="darkgreen" value="0 0.392 0"/>+<color name="darkmagenta" value="0.545 0 0.545"/>+<color name="darkorange" value="1 0.549 0"/>+<color name="darkred" value="0.545 0 0"/>+<color name="lightblue" value="0.678 0.847 0.902"/>+<color name="lightcyan" value="0.878 1 1"/>+<color name="lightgray" value="0.827"/>+<color name="lightgreen" value="0.565 0.933 0.565"/>+<color name="lightyellow" value="1 1 0.878"/>+<dashstyle name="dashed" value="[4] 0"/>+<dashstyle name="dotted" value="[1 3] 0"/>+<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>+<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>+<textsize name="large" value="\large"/>+<textsize name="small" value="\small"/>+<textsize name="tiny" value="\tiny"/>+<textsize name="Large" value="\Large"/>+<textsize name="LARGE" value="\LARGE"/>+<textsize name="huge" value="\huge"/>+<textsize name="Huge" value="\Huge"/>+<textsize name="footnote" value="\footnotesize"/>+<textstyle name="center" begin="\begin{center}" end="\end{center}"/>+<textstyle name="itemize" begin="\begin{itemize}" end="\end{itemize}"/>+<textstyle name="item" begin="\begin{itemize}\item{}" end="\end{itemize}"/>+<gridsize name="4 pts" value="4"/>+<gridsize name="8 pts (~3 mm)" value="8"/>+<gridsize name="16 pts (~6 mm)" value="16"/>+<gridsize name="32 pts (~12 mm)" value="32"/>+<gridsize name="10 pts (~3.5 mm)" value="10"/>+<gridsize name="20 pts (~7 mm)" value="20"/>+<gridsize name="14 pts (~5 mm)" value="14"/>+<gridsize name="28 pts (~10 mm)" value="28"/>+<gridsize name="56 pts (~20 mm)" value="56"/>+<anglesize name="90 deg" value="90"/>+<anglesize name="60 deg" value="60"/>+<anglesize name="45 deg" value="45"/>+<anglesize name="30 deg" value="30"/>+<anglesize name="22.5 deg" value="22.5"/>+<tiling name="falling" angle="-60" step="4" width="1"/>+<tiling name="rising" angle="30" step="4" width="1"/>+</ipestyle>+<ipestyle name="frank">+<arrowsize name="normal" value="5"/>+<arrowsize name="large" value="8"/>+<arrowsize name="small" value="3"/>+<arrowsize name="tiny" value="1"/>+<arrowsize name="huge" value="10"/>+<dashstyle name="dashed" value="[2 2] 0"/>+<dashstyle name="dotted" value="[0.5 1] 0"/>+<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>+<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>+<gridsize name="1 pts" value="1"/>+<gridsize name="2 pts" value="2"/>+<opacity name="10%" value="0.1"/>+<opacity name="20%" value="0.2"/>+<opacity name="30%" value="0.3"/>+<opacity name="40%" value="0.4"/>+<opacity name="50%" value="0.5"/>+<opacity name="60%" value="0.6"/>+<opacity name="70%" value="0.7"/>+<opacity name="80%" value="0.8"/>+<opacity name="90%" value="0.9"/>+</ipestyle>+<page>+<layer name="alpha"/>+<layer name="beta"/>+<layer name="gamma"/>+<layer name="delta"/>+<view layers="alpha beta gamma delta" active="delta"/>+<use layer="alpha" name="mark/cross(sx)" pos="112 736" size="normal" stroke="black"/>+<use name="mark/cross(sx)" pos="336 640" size="normal" stroke="black"/>+<use name="mark/cross(sx)" pos="336 816" size="normal" stroke="black"/>+<use matrix="1 0 0 1 6.13784 -3.9059" name="mark/disk(sx)" pos="304 784" size="normal" stroke="black"/>+<use matrix="1 0 0 1 4.44908 -4.21815" name="mark/disk(sx)" pos="320 736" size="normal" stroke="black"/>+<use matrix="1 0 0 1 0.586099 6.64103" name="mark/disk(sx)" pos="352 736" size="normal" stroke="black"/>+<path stroke="black">+129.39 0 0 129.39 241.143 728 e+</path>+<use name="mark/disk(sx)" pos="264.671 689.288" size="normal" stroke="black"/>+<use name="mark/disk(sx)" pos="222.264 731.137" size="normal" stroke="black"/>+<use name="mark/disk(sx)" pos="289.78 722.767" size="normal" stroke="black"/>+<use name="mark/disk(sx)" pos="215.01 683.15" size="normal" stroke="black"/>+<use name="mark/disk(sx)" pos="197.154 741.181" size="normal" stroke="black"/>+<use name="mark/disk(sx)" pos="194.365 766.848" size="normal" stroke="black"/>+<use name="mark/disk(sx)" pos="230.076 767.406" size="normal" stroke="black"/>+<use name="mark/disk(sx)" pos="155.864 708.26" size="normal" stroke="black"/>+<use name="mark/disk(sx)" pos="238.445 743.413" size="normal" stroke="black"/>+<use name="mark/disk(sx)" pos="280.294 767.964" size="normal" stroke="black"/>+<use name="mark/disk(sx)" pos="274.715 659.157" size="normal" stroke="black"/>+<use name="mark/disk(sx)" pos="221.148 609.496" size="normal" stroke="black"/>+<use name="mark/disk(sx)" pos="310.984 638.512" size="normal" stroke="black"/>+<use name="mark/disk(sx)" pos="223.38 649.671" size="normal" stroke="black"/>+<use name="mark/disk(sx)" pos="169.813 689.846" size="normal" stroke="black"/>+<use name="mark/disk(sx)" pos="201.618 709.934" size="normal" stroke="black"/>+<use layer="beta" name="mark/disk(sx)" pos="368 608" size="normal" stroke="red"/>+<use matrix="1 0 0 1 2.78993 -9.48576" name="mark/disk(sx)" pos="336 608" size="normal" stroke="red"/>+<use name="mark/disk(sx)" pos="352 592" size="normal" stroke="red"/>+<use matrix="1 0 0 1 10.0437 -7.8118" name="mark/disk(sx)" pos="368 576" size="normal" stroke="red"/>+<use matrix="1 0 0 1 2.78993 1.11597" name="mark/disk(sx)" pos="320 576" size="normal" stroke="red"/>+<use matrix="1 0 0 1 -8.36979 -11.1597" name="mark/disk(sx)" pos="352 576" size="normal" stroke="red"/>+<use matrix="1 0 0 1 30.6892 -15.0656" name="mark/disk(sx)" pos="400 608" size="normal" stroke="red"/>+<use name="mark/disk(sx)" pos="400 576" size="normal" stroke="red"/>+<use name="mark/disk(sx)" pos="304 560" size="normal" stroke="red"/>+<use name="mark/disk(sx)" pos="368 544" size="normal" stroke="red"/>+<use name="mark/cross(sx)" pos="288.737 510.098" size="normal" stroke="red"/>+<use name="mark/disk(sx)" pos="379.689 503.96" size="normal" stroke="red"/>+<use name="mark/disk(sx)" pos="409.82 541.903" size="normal" stroke="red"/>+<use name="mark/disk(sx)" pos="363.507 541.345" size="normal" stroke="red"/>+<use name="mark/disk(sx)" pos="437.719 527.954" size="normal" stroke="red"/>+<use name="mark/cross(sx)" pos="476.778 591.564" size="normal" stroke="red"/>+<use name="mark/disk(sx)" pos="403.682 564.223" size="normal" stroke="red"/>+<use name="mark/disk(sx)" pos="409.82 562.549" size="normal" stroke="red"/>+<use name="mark/disk(sx)" pos="414.284 563.107" size="normal" stroke="red"/>+<use name="mark/disk(sx)" pos="462.271 545.809" size="normal" stroke="red"/>+<use name="mark/disk(sx)" pos="450.553 567.013" size="normal" stroke="red"/>+<use name="mark/disk(sx)" pos="343.978 524.606" size="normal" stroke="red"/>+<path stroke="red">+102.465 0 0 102.465 382.758 550.831 e+</path>+<use layer="gamma" matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="368 608" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 9.39047 -5.35868" name="mark/disk(sx)" pos="336 608" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="352 592" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 16.6443 -3.68472" name="mark/disk(sx)" pos="368 576" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 9.39047 5.24305" name="mark/disk(sx)" pos="320 576" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 -1.76925 -7.03264" name="mark/disk(sx)" pos="352 576" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 37.2898 -10.9385" name="mark/disk(sx)" pos="400 608" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="400 576" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="304 560" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="368 544" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="379.689 503.96" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="409.82 541.903" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="363.507 541.345" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="437.719 527.954" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="403.682 564.223" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="409.82 562.549" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="414.284 563.107" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="462.271 545.809" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="450.553 567.013" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 6.60054 4.12708" name="mark/disk(sx)" pos="343.978 524.606" size="normal" stroke="blue"/>+<use layer="delta" name="mark/disk(sx)" pos="71.4684 472.509" size="normal" stroke="blue"/>+<use name="mark/disk(sx)" pos="90.3283 458.001" size="normal" stroke="blue"/>+<use name="mark/disk(sx)" pos="75.0953 453.649" size="normal" stroke="blue"/>+<use name="mark/disk(sx)" pos="101.209 426.084" size="normal" stroke="blue"/>+<use name="mark/disk(sx)" pos="45.3547 421.732" size="normal" stroke="blue"/>+<use name="mark/disk(sx)" pos="76.5461 421.007" size="normal" stroke="blue"/>+<use name="mark/disk(sx)" pos="56.9608 430.437" size="normal" stroke="blue"/>+<use name="mark/disk(sx)" pos="85.2506 433.338" size="normal" stroke="blue"/>+<use name="mark/disk(sx)" pos="113.541 435.514" size="normal" stroke="blue"/>+<use name="mark/disk(sx)" pos="75.0953 453.649" size="normal" stroke="blue"/>+<use name="mark/disk(sx)" pos="46.8054 454.374" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 39.896 -3.62691" name="mark/disk(sx)" pos="75.0953 453.649" size="normal" stroke="blue"/>+<use name="mark/disk(sx)" pos="105.561 473.96" size="normal" stroke="blue"/>+<use name="mark/disk(sx)" pos="107.012 458.727" size="normal" stroke="blue"/>+<use name="mark/disk(sx)" pos="135.302 418.105" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 4.52803 -3.42357" name="mark/disk(sx)" pos="101.209 426.084" size="normal" stroke="blue"/>+<use name="mark/disk(sx)" pos="90.2821 422.711" size="normal" stroke="blue"/>+<use name="mark/disk(sx)" pos="118.434 461.101" size="normal" stroke="blue"/>+<use matrix="1 0 0 1 5.59154 0.698943" name="mark/disk(sx)" pos="57.5231 446.257" size="normal" stroke="blue"/>+<use name="mark/disk(sx)" pos="68.9116 432.574" size="normal" stroke="blue"/>+</page>+</ipe>
+ test/Data/Geometry/Ipe/ReaderSpec.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE OverloadedStrings #-}+module Data.Geometry.Ipe.ReaderSpec where++import Test.Hspec+import Data.Ext+import Data.Geometry+import Data.Geometry.Ipe+import Data.ByteString(ByteString)+import Data.Proxy+++-- | specializes to use Double as Numtype+fromIpeXML' :: IpeRead (t Double) => ByteString -> Either ConversionError (t Double)+fromIpeXML' = fromIpeXML+++spec :: Spec+spec = do+    describe "IpeReadText" $ do+      it "parses a polyline into a Path" $+        ipeReadText "\n128 656 m\n224 768 l\n304 624 l\n432 752 l\n"+        `shouldBe` Right ops+    describe "IpeReadAttrs" $ do+      it "parses a symbols attributes" $+        (show $ readXML useTxt+                >>= ipeReadAttrs (Proxy :: Proxy IpeSymbol) (Proxy :: Proxy Double))+        `shouldBe`+        "Right (Attrs {_unAttrs = {GAttr {_getAttr = Nothing}, GAttr {_getAttr = Nothing}, GAttr {_getAttr = Nothing}, GAttr {_getAttr = Nothing}, GAttr {_getAttr = Just (IpeColor (Valued \"black\"))}, GAttr {_getAttr = Nothing}, GAttr {_getAttr = Nothing}, GAttr {_getAttr = Just (IpeSize (Named \"normal\"))}}})"++    describe "IpeRead" $ do+      it "parses a Symbol" $+        fromIpeXML' useTxt+        `shouldBe` Right useSym+    -- it "parses a path" $+      --   (show $ fromIpeXML'+          -- "<use name=\"mark/disk(sx)\" pos=\"320 736\" size=\"normal\" stroke=\"black\"/>"+-- )+      --   `shouldBe`+-- "Right (Path {_pathSegments = PolyLineSegment (PolyLine {_points = Seq2 (Point2 [128.0,656.0] :+ ()) (fromList [Point2 [224.0,768.0] :+ (),Point2 [304.0,624.0] :+ ()]) (Point2 [432.0,752.0] :+ ())}) :< fromList []})"+++  where+    useTxt = "<use name=\"mark/disk(sx)\" pos=\"320 736\" size=\"normal\" stroke=\"black\"/>"+    useSym = Symbol (point2 320 736) "mark/disk(sx)"+--     symAttrs =++    translatedUse = "<use matrix=\"1 0 0 1 4.44908 -4.21815\" name=\"mark/disk(sx)\" pos=\"320 736\" size=\"normal\" stroke=\"black\"/>"+++++    ops = [ MoveTo $ point2 128 (656 :: Double)+          , LineTo $ point2 224 768+          , LineTo $ point2 304 624+          , LineTo $ point2 432 752+          ]
+ test/Data/Geometry/PointSpec.hs view
@@ -0,0 +1,87 @@+module Data.Geometry.PointSpec where++import Data.Ext+import Data.Geometry.Point+import Test.Hspec+import qualified Data.CircularList as C+++spec :: Spec+spec = do+  describe "Sort Arround a Point test" $ do+    it "Sort around origin" $+      sortArround (ext origin) (map ext [ point2 (-3) (-3)+                                        , point2 (-1) (-5)+                                        , point2 5    5+                                        , point2 6    (-4)+                                        , point2 (-5) 3+                                        , point2 10   1+                                        , point2 20   0+                                        , point2 0    (-6)+                                        , point2 5    7+                                        , point2 5    5+                                        , point2 2    2+                                        , point2 26   (-2)+                                        , point2 0    (-5)+                                        ])+      `shouldBe` map ext [ point2 20   0+                         , point2 10   1+                         , point2 2    2+                         , point2 5    5+                         , point2 5    5+                         , point2 5    7+                         , point2 (-5) 3+                         , point2 (-3) (-3)+                         , point2 (-1) (-5)+                         , point2 0    (-5)+                         , point2 0    (-6)+                         , point2 6    (-4)+                         , point2 26   (-2)+                         ]+    it "degenerate points on horizontal line" $+      sortArround (ext origin) (map ext [ point2 2    0+                                        , point2 (-1) 0+                                        , point2 10   0+                                        ])+      `shouldBe` map ext [ point2 2 0, point2 10 0, point2 (-1) 0 ]+    it "degenerate points on vertical line" $+      sortArround (ext origin) (map ext [ point2 0 2+                                        , point2 0 (-1)+                                        , point2 0 10+                                        ])+      `shouldBe` map ext [ point2 0 2, point2 0 10, point2 0 (-1) ]+++  describe "Insert point in ciclically ordered list" $ do+    it "insert" $+      insertIntoCyclicOrder (ext origin) (ext $ point2 (-4) (-5))  (+        C.fromList $ map ext [ point2 20   0+                             , point2 10   1+                             , point2 2    2+                             , point2 5    5+                             , point2 5    5+                             , point2 5    7+                             , point2 (-5) 3+                             , point2 (-3) (-3)+                             , point2 (-1) (-5)+                             , point2 0    (-5)+                             , point2 0    (-6)+                             , point2 6    (-4)+                             , point2 26   (-2)+                             ])+      `shouldBe`+        (C.fromList $ map ext [ point2 20   0+                              , point2 10   1+                              , point2 2    2+                              , point2 5    5+                              , point2 5    5+                              , point2 5    7+                              , point2 (-5) 3+                              , point2 (-3) (-3)+                              , point2 (-4) (-5)+                              , point2 (-1) (-5)+                              , point2 0    (-5)+                              , point2 0    (-6)+                              , point2 6    (-4)+                              , point2 26   (-2)+                              ])
+ test/Data/Geometry/Polygon/Convex/ConvexSpec.hs view
@@ -0,0 +1,59 @@+module Data.Geometry.Polygon.Convex.ConvexSpec where++import           Control.Applicative+import           Control.Arrow ((&&&))+import           Control.Lens+import           Data.Ext+import qualified Data.Foldable as F+import           Data.Geometry+import           Data.Geometry.Ipe+import           Data.Geometry.Polygon (extremesLinear)+import           Data.Geometry.Polygon.Convex+import           Data.Traversable (traverse)+import           Test.Hspec++++spec :: Spec+spec = testCases "test/Data/Geometry/Polygon/Convex/convexTests.ipe"++testCases    :: FilePath -> Spec+testCases fp = runIO (readInputFromFile fp) >>= \case+    Left e    -> it "reading ConvexTests file" $+                   expectationFailure . unwords $+                     [ "Failed to read ipe file", show fp, ":", show e]+    Right tcs -> mapM_ toSpec tcs+++data TestCase r = TestCase { _polygon    :: ConvexPolygon () r+                           }+                  deriving (Show)++toSingleSpec        :: (Num r, Ord r, Show r)+                    => ConvexPolygon q r -> Vector 2 r -> SpecWith ()+toSingleSpec poly u = it msg $+  -- test that the reported extremes are equally far in direction u+    F.all allEq (unzip [extremes u poly, extremesLinear u poly]) `shouldBe` True+  where+    allEq (p:ps) = all (\q -> cmpExtreme u p q == EQ) ps+    msg = "Extremes test with direction " ++ show u++-- | generates 360 vectors "equally" spaced/angled+directions :: Num r => [Vector 2 r]+directions = map (fmap toRat . uncurry v2 . (cos &&& sin) . toRad) ([0..359] :: [Double])+  where+    toRad i = i * (pi / 180)+    toRat x = fromIntegral . round $ 100000 * x++toSpec                 :: (Num r, Ord r, Show r) => TestCase r -> SpecWith ()+toSpec (TestCase poly) = do+                           describe "Extreme points; binsearch same as linear" $+                             mapM_ (toSingleSpec poly) directions+++readInputFromFile    :: FilePath -> IO (Either ConversionError [TestCase Rational])+readInputFromFile fp = fmap f <$> readSinglePageFile fp+  where+    f page = [ TestCase poly | (poly :+ _) <- polies ]+      where+        polies = page^..content.traverse._withAttrs _IpePath _asSimplePolygon
+ test/Data/Geometry/Polygon/Convex/convexTests.ipe view
@@ -0,0 +1,308 @@+<?xml version="1.0"?>+<!DOCTYPE ipe SYSTEM "ipe.dtd">+<ipe version="70107" creator="Ipe 7.2.2">+<info created="D:20160608222645" modified="D:20160611094216"/>+<ipestyle name="basic">+<symbol name="arrow/arc(spx)">+<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">+0 0 m+-1 0.333 l+-1 -0.333 l+h+</path>+</symbol>+<symbol name="arrow/farc(spx)">+<path stroke="sym-stroke" fill="white" pen="sym-pen">+0 0 m+-1 0.333 l+-1 -0.333 l+h+</path>+</symbol>+<symbol name="arrow/ptarc(spx)">+<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">+0 0 m+-1 0.333 l+-0.8 0 l+-1 -0.333 l+h+</path>+</symbol>+<symbol name="arrow/fptarc(spx)">+<path stroke="sym-stroke" fill="white" pen="sym-pen">+0 0 m+-1 0.333 l+-0.8 0 l+-1 -0.333 l+h+</path>+</symbol>+<symbol name="mark/circle(sx)" transformations="translations">+<path fill="sym-stroke">+0.6 0 0 0.6 0 0 e+0.4 0 0 0.4 0 0 e+</path>+</symbol>+<symbol name="mark/disk(sx)" transformations="translations">+<path fill="sym-stroke">+0.6 0 0 0.6 0 0 e+</path>+</symbol>+<symbol name="mark/fdisk(sfx)" transformations="translations">+<group>+<path fill="sym-fill">+0.5 0 0 0.5 0 0 e+</path>+<path fill="sym-stroke" fillrule="eofill">+0.6 0 0 0.6 0 0 e+0.4 0 0 0.4 0 0 e+</path>+</group>+</symbol>+<symbol name="mark/box(sx)" transformations="translations">+<path fill="sym-stroke" fillrule="eofill">+-0.6 -0.6 m+0.6 -0.6 l+0.6 0.6 l+-0.6 0.6 l+h+-0.4 -0.4 m+0.4 -0.4 l+0.4 0.4 l+-0.4 0.4 l+h+</path>+</symbol>+<symbol name="mark/square(sx)" transformations="translations">+<path fill="sym-stroke">+-0.6 -0.6 m+0.6 -0.6 l+0.6 0.6 l+-0.6 0.6 l+h+</path>+</symbol>+<symbol name="mark/fsquare(sfx)" transformations="translations">+<group>+<path fill="sym-fill">+-0.5 -0.5 m+0.5 -0.5 l+0.5 0.5 l+-0.5 0.5 l+h+</path>+<path fill="sym-stroke" fillrule="eofill">+-0.6 -0.6 m+0.6 -0.6 l+0.6 0.6 l+-0.6 0.6 l+h+-0.4 -0.4 m+0.4 -0.4 l+0.4 0.4 l+-0.4 0.4 l+h+</path>+</group>+</symbol>+<symbol name="mark/cross(sx)" transformations="translations">+<group>+<path fill="sym-stroke">+-0.43 -0.57 m+0.57 0.43 l+0.43 0.57 l+-0.57 -0.43 l+h+</path>+<path fill="sym-stroke">+-0.43 0.57 m+0.57 -0.43 l+0.43 -0.57 l+-0.57 0.43 l+h+</path>+</group>+</symbol>+<symbol name="arrow/fnormal(spx)">+<path stroke="sym-stroke" fill="white" pen="sym-pen">+0 0 m+-1 0.333 l+-1 -0.333 l+h+</path>+</symbol>+<symbol name="arrow/pointed(spx)">+<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">+0 0 m+-1 0.333 l+-0.8 0 l+-1 -0.333 l+h+</path>+</symbol>+<symbol name="arrow/fpointed(spx)">+<path stroke="sym-stroke" fill="white" pen="sym-pen">+0 0 m+-1 0.333 l+-0.8 0 l+-1 -0.333 l+h+</path>+</symbol>+<symbol name="arrow/linear(spx)">+<path stroke="sym-stroke" pen="sym-pen">+-1 0.333 m+0 0 l+-1 -0.333 l+</path>+</symbol>+<symbol name="arrow/fdouble(spx)">+<path stroke="sym-stroke" fill="white" pen="sym-pen">+0 0 m+-1 0.333 l+-1 -0.333 l+h+-1 0 m+-2 0.333 l+-2 -0.333 l+h+</path>+</symbol>+<symbol name="arrow/double(spx)">+<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">+0 0 m+-1 0.333 l+-1 -0.333 l+h+-1 0 m+-2 0.333 l+-2 -0.333 l+h+</path>+</symbol>+<pen name="heavier" value="0.8"/>+<pen name="fat" value="1.2"/>+<pen name="ultrafat" value="2"/>+<symbolsize name="large" value="5"/>+<symbolsize name="small" value="2"/>+<symbolsize name="tiny" value="1.1"/>+<arrowsize name="large" value="10"/>+<arrowsize name="small" value="5"/>+<arrowsize name="tiny" value="3"/>+<color name="red" value="1 0 0"/>+<color name="green" value="0 1 0"/>+<color name="blue" value="0 0 1"/>+<color name="yellow" value="1 1 0"/>+<color name="orange" value="1 0.647 0"/>+<color name="gold" value="1 0.843 0"/>+<color name="purple" value="0.627 0.125 0.941"/>+<color name="gray" value="0.745"/>+<color name="brown" value="0.647 0.165 0.165"/>+<color name="navy" value="0 0 0.502"/>+<color name="pink" value="1 0.753 0.796"/>+<color name="seagreen" value="0.18 0.545 0.341"/>+<color name="turquoise" value="0.251 0.878 0.816"/>+<color name="violet" value="0.933 0.51 0.933"/>+<color name="darkblue" value="0 0 0.545"/>+<color name="darkcyan" value="0 0.545 0.545"/>+<color name="darkgray" value="0.663"/>+<color name="darkgreen" value="0 0.392 0"/>+<color name="darkmagenta" value="0.545 0 0.545"/>+<color name="darkorange" value="1 0.549 0"/>+<color name="darkred" value="0.545 0 0"/>+<color name="lightblue" value="0.678 0.847 0.902"/>+<color name="lightcyan" value="0.878 1 1"/>+<color name="lightgray" value="0.827"/>+<color name="lightgreen" value="0.565 0.933 0.565"/>+<color name="lightyellow" value="1 1 0.878"/>+<dashstyle name="dashed" value="[4] 0"/>+<dashstyle name="dotted" value="[1 3] 0"/>+<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>+<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>+<textsize name="large" value="\large"/>+<textsize name="Large" value="\Large"/>+<textsize name="LARGE" value="\LARGE"/>+<textsize name="huge" value="\huge"/>+<textsize name="Huge" value="\Huge"/>+<textsize name="small" value="\small"/>+<textsize name="footnote" value="\footnotesize"/>+<textsize name="tiny" value="\tiny"/>+<textstyle name="center" begin="\begin{center}" end="\end{center}"/>+<textstyle name="itemize" begin="\begin{itemize}" end="\end{itemize}"/>+<textstyle name="item" begin="\begin{itemize}\item{}" end="\end{itemize}"/>+<gridsize name="4 pts" value="4"/>+<gridsize name="8 pts (~3 mm)" value="8"/>+<gridsize name="16 pts (~6 mm)" value="16"/>+<gridsize name="32 pts (~12 mm)" value="32"/>+<gridsize name="10 pts (~3.5 mm)" value="10"/>+<gridsize name="20 pts (~7 mm)" value="20"/>+<gridsize name="14 pts (~5 mm)" value="14"/>+<gridsize name="28 pts (~10 mm)" value="28"/>+<gridsize name="56 pts (~20 mm)" value="56"/>+<anglesize name="90 deg" value="90"/>+<anglesize name="60 deg" value="60"/>+<anglesize name="45 deg" value="45"/>+<anglesize name="30 deg" value="30"/>+<anglesize name="22.5 deg" value="22.5"/>+<opacity name="10%" value="0.1"/>+<opacity name="30%" value="0.3"/>+<opacity name="50%" value="0.5"/>+<opacity name="75%" value="0.75"/>+<tiling name="falling" angle="-60" step="4" width="1"/>+<tiling name="rising" angle="30" step="4" width="1"/>+</ipestyle>+<ipestyle name="frank">+<arrowsize name="normal" value="5"/>+<arrowsize name="large" value="8"/>+<arrowsize name="huge" value="10"/>+<arrowsize name="small" value="3"/>+<arrowsize name="tiny" value="1"/>+<dashstyle name="dashed" value="[2 2] 0"/>+<dashstyle name="dotted" value="[0.5 1] 0"/>+<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>+<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>+<gridsize name="1 pts" value="1"/>+<gridsize name="2 pts" value="2"/>+<opacity name="10%" value="0.1"/>+<opacity name="30%" value="0.3"/>+<opacity name="50%" value="0.5"/>+<opacity name="20%" value="0.2"/>+<opacity name="40%" value="0.4"/>+<opacity name="60%" value="0.6"/>+<opacity name="70%" value="0.7"/>+<opacity name="80%" value="0.8"/>+<opacity name="90%" value="0.9"/>+</ipestyle>+<page>+<layer name="alpha"/>+<layer name="beta"/>+<view layers="alpha beta" active="beta"/>+<path layer="alpha" stroke="black">+144 720 m+224 768 l+368 672 l+320 592 l+240 544 l+112 528 l+96 640 l+h+</path>+<path stroke="black">+224 656 m+352 784 l+416 816 l+544 800 l+576 752 l+592 720 l+592 672 l+576 640 l+544 608 l+480 560 l+400 544 l+320 544 l+272 560 l+240 608 l+h+</path>+</page>+</ipe>
+ test/Data/Geometry/PolygonSpec.hs view
@@ -0,0 +1,87 @@+{-# LANGUAGE OverloadedStrings #-}+module Data.Geometry.PolygonSpec where++import Data.Traversable(traverse)+import Data.Ext+import Control.Lens+import Control.Applicative+import Data.Geometry+import Data.Geometry.Boundary+import Data.Geometry.Ipe+import Data.Proxy+import Test.Hspec++spec :: Spec+spec = testCases "test/Data/Geometry/pointInPolygon.ipe"+++testCases    :: FilePath -> Spec+testCases fp = runIO (readInputFromFile fp) >>= \case+    Left e    -> it "reading point in polygon file" $+                   expectationFailure $ "Failed to read ipe file " ++ show e+    Right tcs -> mapM_ toSpec tcs+++--   ipeF <- beforeAll $ readInputFromFile "tests/Data/Geometry/pointInPolygon.ipe"+--   describe "Point in Polygon tests" $ do+--     it "returns the first element of a list" $ do+--       head [23 ..] `shouldBe` (23 :: Int)+++data TestCase r = TestCase { _polygon    :: SimplePolygon () r+                           , _inside     :: [Point 2 r]+                           , _onBoundary :: [Point 2 r]+                           , _outside    :: [Point 2 r]+                           }+                  deriving (Show)+++toSingleSpec poly r q = it msg $ (q `inPolygon` poly) `shouldBe` r+  where+    msg = "Point in polygon test with " ++ show q+++toSpec (TestCase poly is bs os) = do+                                    describe "inside tests" $+                                      mapM_ (toSingleSpec poly Inside) is+                                    describe "on boundary tests" $+                                      mapM_ (toSingleSpec poly OnBoundary) bs+                                    describe "outside tests" $+                                      mapM_ (toSingleSpec poly Outside) os++readInputFromFile    :: FilePath -> IO (Either ConversionError [TestCase Rational])+readInputFromFile fp = fmap f <$> readSinglePageFile fp+  where+    f page = [ TestCase poly+                        [ s^.symbolPoint | s <- myPoints ats, isInsidePt  s ]+                        [ s^.symbolPoint | s <- myPoints ats, isBorderPt  s ]+                        [ s^.symbolPoint | s <- myPoints ats, isOutsidePt s ]+             | (poly :+ ats) <- polies+             ]+      where+        polies = page^..content.traverse._withAttrs _IpePath _asSimplePolygon+        syms   = page^..content.traverse._IpeUse+++        myPoints polyAts = [s | (s :+ ats) <- syms, belongsToPoly ats polyAts ]++        -- We test a point/polygon combination if they have the same color+        belongsToPoly symAts polyAts =+            lookupAttr colorP symAts == lookupAttr colorP polyAts++        -- A point i inside if it is a disk+        isInsidePt   :: IpeSymbol r -> Bool+        isInsidePt s = s^.symbolName == "mark/disk(sx)"++        -- Boxes are on the boundary+        isBorderPt s = s^.symbolName == "mark/box(sx)"++        -- crosses are outside the polygon+        isOutsidePt s = s^.symbolName == "mark/cross(sx)"++        colorP = Proxy :: Proxy Stroke+++++-- main = readInputFromFile "tests/Data/Geometry/pointInPolygon.ipe"
+ test/Data/Geometry/pointInPolygon.ipe view
@@ -0,0 +1,311 @@+<?xml version="1.0"?>+<!DOCTYPE ipe SYSTEM "ipe.dtd">+<ipe version="70107" creator="Ipe 7.1.7">+<info created="D:20150923215046" modified="D:20150924223742"/>+<ipestyle name="basic">+<symbol name="arrow/arc(spx)">+<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">+0 0 m+-1 0.333 l+-1 -0.333 l+h+</path>+</symbol>+<symbol name="arrow/farc(spx)">+<path stroke="sym-stroke" fill="white" pen="sym-pen">+0 0 m+-1 0.333 l+-1 -0.333 l+h+</path>+</symbol>+<symbol name="arrow/ptarc(spx)">+<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">+0 0 m+-1 0.333 l+-0.8 0 l+-1 -0.333 l+h+</path>+</symbol>+<symbol name="arrow/fptarc(spx)">+<path stroke="sym-stroke" fill="white" pen="sym-pen">+0 0 m+-1 0.333 l+-0.8 0 l+-1 -0.333 l+h+</path>+</symbol>+<symbol name="mark/circle(sx)" transformations="translations">+<path fill="sym-stroke">+0.6 0 0 0.6 0 0 e+0.4 0 0 0.4 0 0 e+</path>+</symbol>+<symbol name="mark/disk(sx)" transformations="translations">+<path fill="sym-stroke">+0.6 0 0 0.6 0 0 e+</path>+</symbol>+<symbol name="mark/fdisk(sfx)" transformations="translations">+<group>+<path fill="sym-fill">+0.5 0 0 0.5 0 0 e+</path>+<path fill="sym-stroke" fillrule="eofill">+0.6 0 0 0.6 0 0 e+0.4 0 0 0.4 0 0 e+</path>+</group>+</symbol>+<symbol name="mark/box(sx)" transformations="translations">+<path fill="sym-stroke" fillrule="eofill">+-0.6 -0.6 m+0.6 -0.6 l+0.6 0.6 l+-0.6 0.6 l+h+-0.4 -0.4 m+0.4 -0.4 l+0.4 0.4 l+-0.4 0.4 l+h+</path>+</symbol>+<symbol name="mark/square(sx)" transformations="translations">+<path fill="sym-stroke">+-0.6 -0.6 m+0.6 -0.6 l+0.6 0.6 l+-0.6 0.6 l+h+</path>+</symbol>+<symbol name="mark/fsquare(sfx)" transformations="translations">+<group>+<path fill="sym-fill">+-0.5 -0.5 m+0.5 -0.5 l+0.5 0.5 l+-0.5 0.5 l+h+</path>+<path fill="sym-stroke" fillrule="eofill">+-0.6 -0.6 m+0.6 -0.6 l+0.6 0.6 l+-0.6 0.6 l+h+-0.4 -0.4 m+0.4 -0.4 l+0.4 0.4 l+-0.4 0.4 l+h+</path>+</group>+</symbol>+<symbol name="mark/cross(sx)" transformations="translations">+<group>+<path fill="sym-stroke">+-0.43 -0.57 m+0.57 0.43 l+0.43 0.57 l+-0.57 -0.43 l+h+</path>+<path fill="sym-stroke">+-0.43 0.57 m+0.57 -0.43 l+0.43 -0.57 l+-0.57 0.43 l+h+</path>+</group>+</symbol>+<symbol name="arrow/fnormal(spx)">+<path stroke="sym-stroke" fill="white" pen="sym-pen">+0 0 m+-1 0.333 l+-1 -0.333 l+h+</path>+</symbol>+<symbol name="arrow/pointed(spx)">+<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">+0 0 m+-1 0.333 l+-0.8 0 l+-1 -0.333 l+h+</path>+</symbol>+<symbol name="arrow/fpointed(spx)">+<path stroke="sym-stroke" fill="white" pen="sym-pen">+0 0 m+-1 0.333 l+-0.8 0 l+-1 -0.333 l+h+</path>+</symbol>+<symbol name="arrow/linear(spx)">+<path stroke="sym-stroke" pen="sym-pen">+-1 0.333 m+0 0 l+-1 -0.333 l+</path>+</symbol>+<symbol name="arrow/fdouble(spx)">+<path stroke="sym-stroke" fill="white" pen="sym-pen">+0 0 m+-1 0.333 l+-1 -0.333 l+h+-1 0 m+-2 0.333 l+-2 -0.333 l+h+</path>+</symbol>+<symbol name="arrow/double(spx)">+<path stroke="sym-stroke" fill="sym-stroke" pen="sym-pen">+0 0 m+-1 0.333 l+-1 -0.333 l+h+-1 0 m+-2 0.333 l+-2 -0.333 l+h+</path>+</symbol>+<pen name="heavier" value="0.8"/>+<pen name="fat" value="1.2"/>+<pen name="ultrafat" value="2"/>+<symbolsize name="large" value="5"/>+<symbolsize name="small" value="2"/>+<symbolsize name="tiny" value="1.1"/>+<arrowsize name="large" value="10"/>+<arrowsize name="small" value="5"/>+<arrowsize name="tiny" value="3"/>+<color name="red" value="1 0 0"/>+<color name="green" value="0 1 0"/>+<color name="blue" value="0 0 1"/>+<color name="yellow" value="1 1 0"/>+<color name="orange" value="1 0.647 0"/>+<color name="gold" value="1 0.843 0"/>+<color name="purple" value="0.627 0.125 0.941"/>+<color name="gray" value="0.745"/>+<color name="brown" value="0.647 0.165 0.165"/>+<color name="navy" value="0 0 0.502"/>+<color name="pink" value="1 0.753 0.796"/>+<color name="seagreen" value="0.18 0.545 0.341"/>+<color name="turquoise" value="0.251 0.878 0.816"/>+<color name="violet" value="0.933 0.51 0.933"/>+<color name="darkblue" value="0 0 0.545"/>+<color name="darkcyan" value="0 0.545 0.545"/>+<color name="darkgray" value="0.663"/>+<color name="darkgreen" value="0 0.392 0"/>+<color name="darkmagenta" value="0.545 0 0.545"/>+<color name="darkorange" value="1 0.549 0"/>+<color name="darkred" value="0.545 0 0"/>+<color name="lightblue" value="0.678 0.847 0.902"/>+<color name="lightcyan" value="0.878 1 1"/>+<color name="lightgray" value="0.827"/>+<color name="lightgreen" value="0.565 0.933 0.565"/>+<color name="lightyellow" value="1 1 0.878"/>+<dashstyle name="dashed" value="[4] 0"/>+<dashstyle name="dotted" value="[1 3] 0"/>+<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>+<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>+<textsize name="large" value="\large"/>+<textsize name="Large" value="\Large"/>+<textsize name="LARGE" value="\LARGE"/>+<textsize name="huge" value="\huge"/>+<textsize name="Huge" value="\Huge"/>+<textsize name="small" value="\small"/>+<textsize name="footnote" value="\footnotesize"/>+<textsize name="tiny" value="\tiny"/>+<textstyle name="center" begin="\begin{center}" end="\end{center}"/>+<textstyle name="itemize" begin="\begin{itemize}" end="\end{itemize}"/>+<textstyle name="item" begin="\begin{itemize}\item{}" end="\end{itemize}"/>+<gridsize name="4 pts" value="4"/>+<gridsize name="8 pts (~3 mm)" value="8"/>+<gridsize name="16 pts (~6 mm)" value="16"/>+<gridsize name="32 pts (~12 mm)" value="32"/>+<gridsize name="10 pts (~3.5 mm)" value="10"/>+<gridsize name="20 pts (~7 mm)" value="20"/>+<gridsize name="14 pts (~5 mm)" value="14"/>+<gridsize name="28 pts (~10 mm)" value="28"/>+<gridsize name="56 pts (~20 mm)" value="56"/>+<anglesize name="90 deg" value="90"/>+<anglesize name="60 deg" value="60"/>+<anglesize name="45 deg" value="45"/>+<anglesize name="30 deg" value="30"/>+<anglesize name="22.5 deg" value="22.5"/>+<tiling name="falling" angle="-60" step="4" width="1"/>+<tiling name="rising" angle="30" step="4" width="1"/>+</ipestyle>+<ipestyle name="frank">+<arrowsize name="normal" value="5"/>+<arrowsize name="large" value="8"/>+<arrowsize name="huge" value="10"/>+<arrowsize name="small" value="3"/>+<arrowsize name="tiny" value="1"/>+<dashstyle name="dashed" value="[2 2] 0"/>+<dashstyle name="dotted" value="[0.5 1] 0"/>+<dashstyle name="dash dotted" value="[4 2 1 2] 0"/>+<dashstyle name="dash dot dotted" value="[4 2 1 2 1 2] 0"/>+<gridsize name="1 pts" value="1"/>+<gridsize name="2 pts" value="2"/>+<opacity name="10%" value="0.1"/>+<opacity name="20%" value="0.2"/>+<opacity name="30%" value="0.3"/>+<opacity name="40%" value="0.4"/>+<opacity name="50%" value="0.5"/>+<opacity name="60%" value="0.6"/>+<opacity name="70%" value="0.7"/>+<opacity name="80%" value="0.8"/>+<opacity name="90%" value="0.9"/>+</ipestyle>+<page>+<layer name="alpha"/>+<view layers="alpha" active="alpha"/>+<path layer="alpha" stroke="darkblue">+208 752 m+304 688 l+224 592 l+48 736 l+h+</path>+<use name="mark/disk(sx)" pos="128 704" size="normal" stroke="darkblue"/>+<use name="mark/disk(sx)" pos="192 672" size="normal" stroke="darkblue"/>+<use name="mark/disk(sx)" pos="192 720" size="normal" stroke="darkblue"/>+<use name="mark/box(sx)" pos="304 688" size="normal" stroke="black"/>+<use name="mark/cross(sx)" pos="352 736" size="normal" stroke="darkblue"/>+<use name="mark/cross(sx)" pos="352 704" size="normal" stroke="darkblue"/>+<path stroke="orange">+496 736 m+432 624 l+512 608 l+h+</path>+<use name="mark/box(sx)" pos="432 624" size="normal" stroke="orange"/>+<use name="mark/box(sx)" pos="512 608" size="normal" stroke="orange"/>+<use name="mark/box(sx)" pos="496 736" size="normal" stroke="orange"/>+<use name="mark/disk(sx)" pos="496 656" size="normal" stroke="orange"/>+<use name="mark/disk(sx)" pos="480 640" size="normal" stroke="orange"/>+<use name="mark/disk(sx)" pos="464 656" size="normal" stroke="orange"/>+<use name="mark/cross(sx)" pos="368 608" size="normal" stroke="orange"/>+<use name="mark/cross(sx)" pos="384 592" size="normal" stroke="orange"/>+<use name="mark/cross(sx)" pos="336 576" size="normal" stroke="orange"/>+<use name="mark/disk(sx)" pos="496 624" size="normal" stroke="orange"/>+<use name="mark/cross(sx)" pos="528 624" size="normal" stroke="orange"/>+<use name="mark/cross(sx)" pos="384 624" size="normal" stroke="orange"/>+<use name="mark/cross(sx)" pos="368 640" size="normal" stroke="orange"/>+<use name="mark/cross(sx)" pos="336 688" size="normal" stroke="darkblue"/>+<use name="mark/disk(sx)" pos="256 688" size="normal" stroke="darkblue"/>+<use name="mark/disk(sx)" pos="224 688" size="normal" stroke="darkblue"/>+</page>+</ipe>
+ test/Data/RangeSpec.hs view
@@ -0,0 +1,49 @@+module Data.RangeSpec where++import Data.Geometry.Properties+import Data.Range+import Frames.CoRec+import Test.Hspec+++spec :: Spec+spec = do+  describe "RangeRange Intersection" $ do+    -- it "openRange cap openrange" $ do+    --   ((OpenRange 1 (10 :: Int))  `intersect` (OpenRange 5 (10 :: Int)))+    --   `shouldBe` (coRec $ OpenRange 5 (10 :: Int))+    -- it "disjoint open ranges" $ do+    --   ((OpenRange 1 10) `intersect` (OpenRange 10 12))+    --   `shouldBe` (coRec NoIntersection)+    -- it "closed cap open, disjoint" $ do+    --   ((ClosedRange (1::Int) 10) `intersect` (OpenRange 50 (60 :: Int)))+    --   `shouldBe` (coRec NoIntersection)+    -- it "closed intersect open" $+    --   ((OpenRange 1 10) `intersect` (ClosedRange 10 12))+    --   `shouldBe` (coRec $ Range (Open 5) (Closed 10))++    -- it "open rage intersect closed " $+    -- (OpenRange 1 10) `intersect` (ClosedRange 10 12)+    -- `shouldBe` (coRec $ Range (Open 10) (Open 10))+  -- (Col Range {_lower = Closed 10, _upper = Open 10})+  -- >>> (OpenRange 1 10) `intersect` (ClosedRange 10 12)++    it "returns the first element of a list" $ do+      head [23 ..] `shouldBe` (23 :: Int)++    -- it "closed open " $ do+    --   ((ClosedRange 1 10) `intersect` (OpenRange 5 10))+    --   `shouldBe`+    --   (Col (Range (Open 5) (Closed 10)))+            -- encode "no-padding!!" `shouldBe` "bm8tcGFkZGluZyEh"++    -- |+  --+  -- >>>+  --+  -- >>>+  -- (Col NoIntersection)+  -- >>> (OpenRange 1 10) `intersect` (ClosedRange 10 12)+  -- (Col Range {_lower = Closed 10, _upper = Open 10})+  -- >>> (OpenRange 1 10) `intersect` (ClosedRange 10 12)+  -- FALSE
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ test/Util.hs view
@@ -0,0 +1,14 @@+module Util where++import Data.Vinyl+import Data.Geometry.Ipe+import Data.Proxy+import Data.Ext+import Data.Function(on)+import qualified Data.List as L++byStrokeColour :: (Stroke ∈ AttributesOf g) => [IpeObject' g r] -> [[IpeObject' g r]]+byStrokeColour = map (map fst) . L.groupBy ((==) `on` snd) . L.sortOn snd+               . map (\x -> (x,lookup' x))+  where+    lookup' (_ :+ ats) = lookupAttr (Proxy :: Proxy Stroke) ats