Clean up gif example code.

This commit is contained in:
David Himmelstrup 2020-07-31 22:41:36 +08:00
commit a062eed0d6

View file

@ -1,122 +1,82 @@
#!/usr/bin/env stack #!/usr/bin/env stack
-- stack runghc --package reanimate -- stack runghc --package reanimate
{-# LANGUAGE OverloadedStrings, ApplicativeDo #-} {-# LANGUAGE ApplicativeDo #-}
module Main(main) where module Main(main) where
import qualified Data.Text as T import Control.Lens ((^.))
import Codec.Picture
import Codec.Picture.Jpg
import Codec.Picture.Types
import Control.Monad.ST
import Control.Monad
import qualified Data.ByteString as BS
import Reanimate
import Reanimate.Animation
import Reanimate.Scene
import Reanimate.GeoProjection
import Reanimate.Builtin.Documentation
import System.IO.Unsafe
import Data.Geospatial hiding (LonLat)
import Data.LinearRing
import qualified Data.LineString as Line
import Data.Aeson import Data.Aeson
import Data.Map (Map) import Data.Foldable (toList)
import qualified Data.Map as Map import Data.Geospatial (GeoFeature (..),
import Graphics.SvgTree (PathCommand (..), Tree (None)) GeospatialGeometry (..),
import Data.Foldable PointXY (..), geofeatures,
import Control.Lens ((^.)) geometry, retrieveXY,
splitGeoMultiLine,
splitGeoMultiPolygon,
unGeoLine, unGeoPolygon)
import Data.LinearRing (fromLinearRing)
import Data.LineString (fromLineString)
import Graphics.SvgTree (Tree (None))
import Reanimate
import Reanimate.Builtin.Documentation
import Reanimate.GeoProjection
import System.IO.Unsafe
main :: IO () main :: IO ()
main = reanimate $ sceneAnimation $ do main = reanimate $ sceneAnimation $ do
newSpriteSVG $ mkBackgroundPixel rtfdBackgroundColor -- Set the background to 'rtfdBackgroundColor'
newSpriteSVG_ $ mkBackgroundPixel rtfdBackgroundColor
-- We'll be cycling through projections so let's create a variable
-- containing the current projection.
prevProj <- newVar equirectangularP prevProj <- newVar equirectangularP
let push label proj = do -- Now we can define a function that animates smoothly from the
-- current projection to a new projection.
let push _label proj = do
prev <- readVar prevProj prev <- readVar prevProj
play $ pauseAtEnd waitT $ signalA (curveS 2) $ play $ animate (\t -> grid $ mergeP prev proj t)
mkAnimation morphT $ \t -> # setDuration morphT -- Set the length of the animation
mkGroup $ # signalA (curveS 2) -- Ease in and ease out.
[ grid $ mergeP prev proj t ] # pauseAtEnd waitT -- Then wait on the last frame.
-- The morph from one projection to another has finished so
-- update the variable with new projection.
writeVar prevProj proj writeVar prevProj proj
-- play $ staticFrame morphT $ -- Cycle from 'equirectangularP' through 5 projections and then
-- mkGroup -- back to 'equirectangularP'.
-- [ grid equirectangularP ]
-- push "Lambert" lambertP
--push "Web Mercator" mercatorP
push "Mollweide" mollweideP push "Mollweide" mollweideP
push "Bottomley 30\\degree" (bottomleyP (toRads 30)) push "Bottomley 30\\degree" (bottomleyP (toRads 30))
-- 4
push "Werner" wernerP push "Werner" wernerP
-- 5
-- push "Bonne 45\\degree" (bonneP (toRads 45))
-- pushT
-- (\t -> "Bonne " <> T.pack (show $ round $ fromToS 45 0 t) <> "\\degree")
-- (bonneP . toRads . fromToS 45 0)
-- 6
-- push "Eckert I" eckert1P
-- push "Eckert III" eckert3P
-- push "Eckert IV" eckert5P
-- 7
-- push "Fahey" faheyP
-- 8
-- push "August" augustP
-- 9
push "Foucaut" foucautP push "Foucaut" foucautP
-- 10
push "Lagrange" lagrangeP push "Lagrange" lagrangeP
prev <- readVar prevProj prev <- readVar prevProj
play $ signalA (curveS 2) $ play $ animate (\t -> grid $ mergeP prev equirectangularP t)
mkAnimation morphT $ grid . mergeP prev equirectangularP # setDuration morphT -- Set the length of the animation
# signalA (curveS 2) -- Ease in and ease out.
# pauseAtEnd waitT -- Then wait on the last frame.
where where
waitT = 0 waitT = 0 -- Seconds to wait between transformations
morphT = 1 morphT = 1 -- Duration (in seconds) of each transformation
toRads :: Double -> Double
toRads dec = dec/180 * pi
-- Draw grid lines and land borders.
grid :: Projection -> SVG grid :: Projection -> SVG
-- grid p = None
grid p = grid p =
withStrokeWidth strokeWidth $ withStrokeWidth strokeWidth $
lowerTransformations $ lowerTransformations $
scaleXY scaleXY screenWidth screenHeight $
(screenWidth)
(screenHeight)
$
translate (-1/2) (-1/2) $ translate (-1/2) (-1/2) $
withFillOpacity 0 $ withStrokeColor "black" $
withFillOpacity 0 $
mkGroup mkGroup
[ mkGroup [] [ mkGroup
, withStrokeColor "black" $
withFillOpacity 0 $ mkGroup
[ geometryToSVG p geo [ geometryToSVG p geo
| geo <- landBorders | geo <- landBorders
] ]
, withStrokeColor "black" $ , mkGroup $ map mkLinePath (latitudeLines p ++ longitudeLines p)
mkGroup $ map mkLinePath (latitudeLines p ++ longitudeLines p)
] ]
where where
strokeWidth = defaultStrokeWidth * 0.5 strokeWidth = defaultStrokeWidth * 0.5
worldLine :: Projection -> SVG
worldLine p =
mkLinePath $
map apply
[ (-pi, -halfPi)
, (-pi, halfPi)
, (pi, halfPi)
, (pi, -halfPi)
, (-pi, -halfPi) ]
where
apply (lam, phi) =
let XYCoord x y = projectionForward p $ LonLat lam phi
in (x, y)
latitudeLines :: Projection -> [[(Double, Double)]] latitudeLines :: Projection -> [[(Double, Double)]]
latitudeLines p = latitudeLines p =
[ latitudeLine (fromToS (-pi) pi (n/(latLines*2))) [ latitudeLine (fromToS (-pi) pi (n/(latLines*2)))
@ -147,21 +107,17 @@ longitudeLines p =
, let lam = fromToS (-pi) pi (n/segments) , let lam = fromToS (-pi) pi (n/segments)
, let XYCoord x y = projectionForward p $ LonLat lam phi ] , let XYCoord x y = projectionForward p $ LonLat lam phi ]
halfPi :: Double
halfPi = pi/2
landBorders :: [(GeospatialGeometry)] landBorders :: [(GeospatialGeometry)]
landBorders = unsafePerformIO $ do landBorders = unsafePerformIO $ do
Just geo <- decodeFileStrict "countries.json" Just geo <- decodeFileStrict "countries.json"
return return
[ (feature ^. geometry) [ (feature ^. geometry)
| feature <- toList $ geo ^. geofeatures | feature <- toList $ geo ^. geofeatures :: [GeoFeature Value]
, let p = feature ^. properties :: Map String Value
] ]
geometryToSVG :: Projection -> GeospatialGeometry -> SVG geometryToSVG :: Projection -> GeospatialGeometry -> SVG
geometryToSVG p geometry = geometryToSVG p geo =
case geometry of case geo of
MultiPolygon mpolygon -> MultiPolygon mpolygon ->
mkGroup $ map (geometryToSVG p . Polygon) $ toList (splitGeoMultiPolygon mpolygon) mkGroup $ map (geometryToSVG p . Polygon) $ toList (splitGeoMultiPolygon mpolygon)
Polygon poly -> Polygon poly ->
@ -176,9 +132,16 @@ geometryToSVG p geometry =
Line line -> Line line ->
mkLinePath mkLinePath
[ (x', y') [ (x', y')
| PointXY x y <- map retrieveXY (Line.fromLineString (line ^. unGeoLine)) | PointXY x y <- map retrieveXY (fromLineString (line ^. unGeoLine))
, let XYCoord x' y' = projectionForward p $ LonLat (x/180*pi) (y/180*pi) , let XYCoord x' y' = projectionForward p $ LonLat (x/180*pi) (y/180*pi)
] ]
MultiLine ml -> MultiLine ml ->
mkGroup $ map (geometryToSVG p . Line) $ toList (splitGeoMultiLine ml) mkGroup $ map (geometryToSVG p . Line) $ toList (splitGeoMultiLine ml)
_ -> None _ -> None
-- Convert degrees to radians
toRads :: Double -> Double
toRads dec = dec/180 * pi
halfPi :: Double
halfPi = pi/2