hgeometry-0.7.0.0: src/Data/Geometry/Ipe/FromIpe.hs
{-# LANGUAGE OverloadedStrings #-}
module Data.Geometry.Ipe.FromIpe where
import Control.Lens hiding (Simple)
import Data.Ext
import Data.Geometry.Ipe.Reader
import Data.Geometry.Ipe.Types
import Data.Geometry.LineSegment
import qualified Data.Geometry.PolyLine as PolyLine
import Data.Geometry.Polygon
import Data.Geometry.Properties
import qualified Data.Seq2 as S2
import qualified Data.List.NonEmpty as NonEmpty
--------------------------------------------------------------------------------
-- $setup
-- >>> :{
-- import Data.Geometry.Ipe.Attributes
--
-- let testPath :: Path Int
-- testPath = Path . S2.l1Singleton . PolyLineSegment . PolyLine.fromPoints . map ext
-- $ [ origin, point2 10 10, point2 200 100 ]
--
-- testPathAttrs :: IpeAttributes Path Int
-- testPathAttrs = attr SStroke (IpeColor (Named "red"))
-- testObject :: IpeObject Int
-- testObject = IpePath (testPath :+ testPathAttrs)
-- :}
-- | 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
--
-- >>> testPath ^? _asPolyLine
-- Just (PolyLine {_points = Seq2 (Point2 [0,0] :+ ()) (fromList [Point2 [10,10] :+ ()]) (Point2 [200,100] :+ ())})
_asPolyLine :: Prism' (Path r) (PolyLine.PolyLine 2 () r)
_asPolyLine = prism' poly2path path2poly
where
poly2path = Path . S2.l1Singleton . PolyLineSegment
path2poly = preview (pathSegments.traverse._PolyLineSegment)
-- TODO: Check that the path actually is a polyline, rather
-- than ignoring everything that does not fit
-- | Convert to a simple polygon
_asSimplePolygon :: Prism' (Path r) (Polygon Simple () r)
_asSimplePolygon = prism' polygonToPath path2poly
where
path2poly p = pathToPolygon p >>= either pure (const Nothing)
-- | Convert to a multipolygon
_asMultiPolygon :: Prism' (Path r) (MultiPolygon () r)
_asMultiPolygon = prism' polygonToPath path2poly
where
path2poly p = pathToPolygon p >>= either (const Nothing) pure
polygonToPath :: Polygon t () r -> Path r
polygonToPath pg@(SimplePolygon _) = Path . S2.l1Singleton . PolygonPath $ pg
polygonToPath (MultiPolygon vs hs) = Path . S2.viewL1FromNonEmpty . fmap PolygonPath
$ SimplePolygon vs NonEmpty.:| hs
pathToPolygon :: Path r -> Maybe (Either (SimplePolygon () r) (MultiPolygon () r))
pathToPolygon p = case p^..pathSegments.traverse._PolygonPath of
[] -> Nothing
[pg] -> Just . Left $ pg
SimplePolygon vs: hs -> Just . Right $ MultiPolygon vs hs
-- | 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.
--
-- >>> testObject ^? _withAttrs _IpePath _asPolyLine
-- Just (PolyLine {_points = Seq2 (Point2 [0,0] :+ ()) (fromList [Point2 [10,10] :+ ()]) (Point2 [200,100] :+ ())} :+ Attrs {_unAttrs = {GAttr {_getAttr = Nothing}, GAttr {_getAttr = Nothing}, GAttr {_getAttr = Nothing}, GAttr {_getAttr = Nothing}, GAttr {_getAttr = Just (IpeColor (Named "red"))}, GAttr {_getAttr = Nothing}, GAttr {_getAttr = Nothing}, GAttr {_getAttr = Nothing}, GAttr {_getAttr = Nothing}, GAttr {_getAttr = Nothing}, GAttr {_getAttr = Nothing}, GAttr {_getAttr = Nothing}, GAttr {_getAttr = Nothing}, GAttr {_getAttr = Nothing}, GAttr {_getAttr = Nothing}, GAttr {_getAttr = Nothing}}})
_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
-- instance HasDefaultIpeObject Path where
-- defaultIpeObject' = _IpePath
-- class HasDefaultFromIpe g where
-- type DefaultFromIpe g :: * -> *
-- defaultIpeObject :: proxy g -> Prism' (IpeObject r) (DefaultFromIpe g r :+ IpeAttributes (DefaultFromIpe g) r)
-- defaultFromIpe :: proxy g -> Prism' (DefaultFromIpe g (NumType g)) g
class HasDefaultFromIpe g where
type DefaultFromIpe g :: * -> *
defaultFromIpe :: (r ~ NumType g)
=> Prism' (IpeObject r) (g :+ IpeAttributes (DefaultFromIpe g) r)
-- instance HasDefaultFromIpe (Point 2 r) where
-- type DefaultFromIpe (Point 2 r) = IpeSymbol
-- defaultFromIpe = _withAttrs _IpeUse symbolPoint
instance HasDefaultFromIpe (LineSegment 2 () r) where
type DefaultFromIpe (LineSegment 2 () r) = Path
defaultFromIpe = _withAttrs _IpePath _asLineSegment
instance HasDefaultFromIpe (PolyLine.PolyLine 2 () r) where
type DefaultFromIpe (PolyLine.PolyLine 2 () r) = Path
defaultFromIpe = _withAttrs _IpePath _asPolyLine
instance HasDefaultFromIpe (SimplePolygon () r) where
type DefaultFromIpe (SimplePolygon () r) = Path
defaultFromIpe = _withAttrs _IpePath _asSimplePolygon
instance HasDefaultFromIpe (MultiPolygon () r) where
type DefaultFromIpe (MultiPolygon () r) = Path
defaultFromIpe = _withAttrs _IpePath _asMultiPolygon
-- | Read all g's from some ipe page(s).
readAll :: (HasDefaultFromIpe g, r ~ NumType g, Foldable f)
=> f (IpePage r) -> [g :+ IpeAttributes (DefaultFromIpe g) r]
readAll = foldMap (^..content.traverse.defaultFromIpe)
-- | Convenience function from reading all g's from an ipe file. If there
-- is an error reading or parsing the file the error is "thrown away".
readAllFrom :: (HasDefaultFromIpe g, r ~ NumType g, Coordinate r, Eq r)
=> FilePath -> IO [g :+ IpeAttributes (DefaultFromIpe g) r]
readAllFrom fp = readAll <$> readSinglePageFile fp