reanimate/videos/map-projection/gif.hs
David Himmelstrup fd2935c2ba Improve README examples (#68)
- Move examples higher, above any technical details.
- Use a consistent off-white background color.
- Use more interesting and less trivial examples.
- Play at 50fps rather than 25fps.
- Host offsite (imgur) as to not take up repo space.
- Link to source file for each example.

Former-commit-id: 7e62a3a1ca99e9abc5781568ff920066c94783ee
2020-05-06 19:16:25 +08:00

184 lines
5.1 KiB
Haskell

#!/usr/bin/env stack
-- stack runghc --package reanimate
{-# LANGUAGE OverloadedStrings, ApplicativeDo #-}
module Main(main) where
import qualified Data.Text as T
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.Map (Map)
import qualified Data.Map as Map
import Graphics.SvgTree (PathCommand (..), Tree (None))
import Data.Foldable
import Control.Lens ((^.))
main :: IO ()
main = reanimate $ sceneAnimation $ do
newSpriteSVG $ mkBackgroundPixel rtfdBackgroundColor
prevProj <- newVar equirectangularP
let push label proj = do
prev <- readVar prevProj
play $ pauseAtEnd waitT $ signalA (curveS 2) $
mkAnimation morphT $ \t ->
mkGroup $
[ grid $ mergeP prev proj t ]
writeVar prevProj proj
-- play $ staticFrame morphT $
-- mkGroup
-- [ grid equirectangularP ]
-- push "Lambert" lambertP
--push "Web Mercator" mercatorP
push "Mollweide" mollweideP
push "Bottomley 30\\degree" (bottomleyP (toRads 30))
-- 4
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
-- 10
push "Lagrange" lagrangeP
prev <- readVar prevProj
play $ signalA (curveS 2) $
mkAnimation morphT $ grid . mergeP prev equirectangularP
where
waitT = 0
morphT = 1
toRads :: Double -> Double
toRads dec = dec/180 * pi
grid :: Projection -> SVG
-- grid p = None
grid p =
withStrokeWidth strokeWidth $
lowerTransformations $
scaleXY
(screenWidth)
(screenHeight)
$
translate (-1/2) (-1/2) $
withFillOpacity 0 $
mkGroup
[ mkGroup []
, withStrokeColor "black" $
withFillOpacity 0 $ mkGroup
[ geometryToSVG p geo
| geo <- landBorders
]
, withStrokeColor "black" $
mkGroup $ map mkLinePath (latitudeLines p ++ longitudeLines p)
]
where
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 p =
[ latitudeLine (fromToS (-pi) pi (n/(latLines*2)))
| n <- [0 .. latLines*2]]
where
latLines = 2
segments = 100
maxLat = atan (sinh pi)
latitudeLine lam =
[ (x, y)
| n <- [0..segments]
, let phi = fromToS (-maxLat) maxLat (n/segments)
, let XYCoord x y = projectionForward p $ LonLat lam phi ]
longitudeLines :: Projection -> [[(Double, Double)]]
longitudeLines p =
longitudeLine maxLat :
longitudeLine (-maxLat) :
[ longitudeLine (fromToS (-halfPi) halfPi (n/(lonLines*2)))
| n <- [1 .. lonLines*2-1] ]
where
lonLines = 2
segments = 100
maxLat = atan (sinh pi)
longitudeLine phi =
[ (x, y)
| n <- [0..segments]
, let lam = fromToS (-pi) pi (n/segments)
, let XYCoord x y = projectionForward p $ LonLat lam phi ]
halfPi :: Double
halfPi = pi/2
landBorders :: [(GeospatialGeometry)]
landBorders = unsafePerformIO $ do
Just geo <- decodeFileStrict "countries.json"
return
[ (feature ^. geometry)
| feature <- toList $ geo ^. geofeatures
, let p = feature ^. properties :: Map String Value
]
geometryToSVG :: Projection -> GeospatialGeometry -> SVG
geometryToSVG p geometry =
case geometry of
MultiPolygon mpolygon ->
mkGroup $ map (geometryToSVG p . Polygon) $ toList (splitGeoMultiPolygon mpolygon)
Polygon poly ->
mkGroup
[ mkLinePath section
| section <- pure
[ (x', y')
| PointXY x y <- map retrieveXY (fromLinearRing (head (toList (poly^.unGeoPolygon))))
, let XYCoord x' y' = projectionForward p $ LonLat (x/180*pi) (y/180*pi)
]
]
Line line ->
mkLinePath
[ (x', y')
| PointXY x y <- map retrieveXY (Line.fromLineString (line ^. unGeoLine))
, let XYCoord x' y' = projectionForward p $ LonLat (x/180*pi) (y/180*pi)
]
MultiLine ml ->
mkGroup $ map (geometryToSVG p . Line) $ toList (splitGeoMultiLine ml)
_ -> None