Geo projections (#52)

Former-commit-id: bc6475b83149db308bf541f1a49b6a8e67729a81
This commit is contained in:
David Himmelstrup 2020-02-11 19:10:45 +08:00 committed by GitHub
commit cbfb95f9be
31 changed files with 1160 additions and 21 deletions

View file

@ -20,7 +20,7 @@ jobs:
steps:
- task: Cache@2
inputs:
key: ${{ parameters.name }} | "${{ parameters.vmImage }}" | $(STACK_YAML) | stack-root | $(Agent.OS) | version4
key: ${{ parameters.name }} | "${{ parameters.vmImage }}" | $(STACK_YAML) | stack-root | $(Agent.OS) | version5
path: $(STACK_ROOT)
cacheHitVar: CACHE_RESTORED
displayName: Cache stack root

View file

@ -2,3 +2,4 @@
* https://www.solarsystemscope.com/textures/
* https://cc0textures.com/
* https://www.publicdomainpictures.net/en/index.php
* https://www.naturalearthdata.com/

BIN
data/small_earth.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

12
examples/doc_bonneP.hs Executable file
View file

@ -0,0 +1,12 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
module Main (main) where
import Reanimate
import Reanimate.GeoProjection
import Reanimate.Builtin.Images
main :: IO ()
main = reanimate $ animate $
const $ scaleToSize screenWidth screenHeight $
embedImage $ project smallEarth (bonneP (45/180*pi))

12
examples/doc_bottomleyP.hs Executable file
View file

@ -0,0 +1,12 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
module Main (main) where
import Reanimate
import Reanimate.GeoProjection
import Reanimate.Builtin.Images
main :: IO ()
main = reanimate $ animate $
const $ scaleToSize screenWidth screenHeight $
embedImage $ project smallEarth (bottomleyP (30/180*pi))

12
examples/doc_collignonP.hs Executable file
View file

@ -0,0 +1,12 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
module Main (main) where
import Reanimate
import Reanimate.GeoProjection
import Reanimate.Builtin.Images
main :: IO ()
main = reanimate $ animate $
const $ scaleToSize screenWidth screenHeight $
embedImage $ project smallEarth collignonP

12
examples/doc_eckert1P.hs Executable file
View file

@ -0,0 +1,12 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
module Main (main) where
import Reanimate
import Reanimate.GeoProjection
import Reanimate.Builtin.Images
main :: IO ()
main = reanimate $ animate $
const $ scaleToSize screenWidth screenHeight $
embedImage $ project smallEarth eckert1P

12
examples/doc_eckert3P.hs Executable file
View file

@ -0,0 +1,12 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
module Main (main) where
import Reanimate
import Reanimate.GeoProjection
import Reanimate.Builtin.Images
main :: IO ()
main = reanimate $ animate $
const $ scaleToSize screenWidth screenHeight $
embedImage $ project smallEarth eckert3P

12
examples/doc_eckert5P.hs Executable file
View file

@ -0,0 +1,12 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
module Main (main) where
import Reanimate
import Reanimate.GeoProjection
import Reanimate.Builtin.Images
main :: IO ()
main = reanimate $ animate $
const $ scaleToSize screenWidth screenHeight $
embedImage $ project smallEarth eckert5P

View file

@ -0,0 +1,12 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
module Main (main) where
import Reanimate
import Reanimate.GeoProjection
import Reanimate.Builtin.Images
main :: IO ()
main = reanimate $ animate $
const $ scaleToSize screenWidth screenHeight $
embedImage $ project smallEarth equirectangularP

12
examples/doc_faheyP.hs Executable file
View file

@ -0,0 +1,12 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
module Main (main) where
import Reanimate
import Reanimate.GeoProjection
import Reanimate.Builtin.Images
main :: IO ()
main = reanimate $ animate $
const $ scaleToSize screenWidth screenHeight $
embedImage $ project smallEarth faheyP

12
examples/doc_hammerP.hs Executable file
View file

@ -0,0 +1,12 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
module Main (main) where
import Reanimate
import Reanimate.GeoProjection
import Reanimate.Builtin.Images
main :: IO ()
main = reanimate $ animate $
const $ scaleToSize screenWidth screenHeight $
embedImage $ project smallEarth hammerP

12
examples/doc_lambertP.hs Executable file
View file

@ -0,0 +1,12 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
module Main (main) where
import Reanimate
import Reanimate.GeoProjection
import Reanimate.Builtin.Images
main :: IO ()
main = reanimate $ animate $
const $ scaleToSize screenWidth screenHeight $
embedImage $ project smallEarth lambertP

12
examples/doc_mercatorP.hs Executable file
View file

@ -0,0 +1,12 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
module Main (main) where
import Reanimate
import Reanimate.GeoProjection
import Reanimate.Builtin.Images
main :: IO ()
main = reanimate $ animate $
const $ scaleToSize screenWidth screenHeight $
embedImage $ project smallEarth mercatorP

12
examples/doc_mollweideP.hs Executable file
View file

@ -0,0 +1,12 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
module Main (main) where
import Reanimate
import Reanimate.GeoProjection
import Reanimate.Builtin.Images
main :: IO ()
main = reanimate $ animate $
const $ scaleToSize screenWidth screenHeight $
embedImage $ project smallEarth mollweideP

12
examples/doc_orthoP.hs Executable file
View file

@ -0,0 +1,12 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
module Main (main) where
import Reanimate
import Reanimate.GeoProjection
import Reanimate.Builtin.Images
main :: IO ()
main = reanimate $ animate $
const $ scaleToSize screenWidth screenHeight $
embedImage $ project smallEarth (orthoP 0 0)

12
examples/doc_sinusoidalP.hs Executable file
View file

@ -0,0 +1,12 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
module Main (main) where
import Reanimate
import Reanimate.GeoProjection
import Reanimate.Builtin.Images
main :: IO ()
main = reanimate $ animate $
const $ scaleToSize screenWidth screenHeight $
embedImage $ project smallEarth sinusoidalP

12
examples/doc_smallEarth.hs Executable file
View file

@ -0,0 +1,12 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
module Main (main) where
import Reanimate
import Reanimate.Builtin.Documentation
import Reanimate.Builtin.Images
main :: IO ()
main = reanimate $ docEnv $ animate $ const $
scaleToSize screenWidth screenHeight $
embedImage smallEarth

12
examples/doc_wernerP.hs Executable file
View file

@ -0,0 +1,12 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
module Main (main) where
import Reanimate
import Reanimate.GeoProjection
import Reanimate.Builtin.Images
main :: IO ()
main = reanimate $ animate $
const $ scaleToSize screenWidth screenHeight $
embedImage $ project smallEarth wernerP

View file

@ -30,6 +30,7 @@ data-files: viewer-elm/dist/index.html
data/CIE_XYZ.csv
data/cone_sensitivity_lms.csv
data/*.svg
data/*.jpg
Source-Repository head
Type: git
@ -69,6 +70,7 @@ library
Reanimate.Parameters
Reanimate.Chiphunk
Reanimate.PolyShape
Reanimate.GeoProjection
Reanimate.Builtin.Documentation
Reanimate.Builtin.Images
other-modules: Reanimate.Cache

View file

@ -7,9 +7,9 @@ import Reanimate.Svg
import Reanimate.Constants
circlePlot :: Int -- ^ Pixels in the X-axis.
-> (Double -- ^ Angle in radians
-> Double -- ^ Radius in percent
-> PixelRGBA8) -> Tree
-> (Double -> Double -> PixelRGBA8)
-- ^ Angle and radius in radians percent respectively.
-> Tree
circlePlot density fn =
scaleToHeight screenHeight $ flipYAxis $
embedImage $ generateImage gen density density

View file

@ -2,8 +2,10 @@ module Reanimate.Builtin.Images
( svgLogo
, haskellLogo
, githubIcon
, smallEarth
) where
import Codec.Picture
import qualified Data.ByteString as B
import Graphics.SvgTree (parseSvgFile)
import Paths_reanimate
@ -19,6 +21,14 @@ embedImage key = do
Nothing -> error "Malformed svg"
Just svg -> return $ embedDocument svg
loadJPG :: FilePath -> Image PixelRGB8
loadJPG key = unsafePerformIO $ do
jpg_file <- getDataFileName key
dat <- B.readFile jpg_file
case decodeJpeg dat of
Left err -> error err
Right img -> return $ convertRGB8 img
-- | <<docs/gifs/doc_svgLogo.gif>>
svgLogo :: SVG
svgLogo = unsafePerformIO $ embedImage "data/svg-logo.svg"
@ -30,3 +40,9 @@ haskellLogo = unsafePerformIO $ embedImage "data/haskell.svg"
-- | <<docs/gifs/doc_githubIcon.gif>>
githubIcon :: SVG
githubIcon = unsafePerformIO $ embedImage "data/github-icon.svg"
-- | 300x150 equirectangular earth
--
-- <<docs/gifs/doc_smallEarth.gif>>
smallEarth :: Image PixelRGB8
smallEarth = loadJPG "data/small_earth.jpg"

View file

@ -0,0 +1,671 @@
{-# LANGUAGE MultiWayIf #-}
module Reanimate.GeoProjection
( Projection(..)
, XYCoord(..)
, LonLat(..)
, project
, interpP
, mergeP
, isValidP
, scaleP
, flipYAxisP
, moveBottomP
, moveTopP
-- * Projections
, equirectangularP
, mercatorP
, mollweideP
, hammerP
, lambertP
, bottomleyP
, sinusoidalP
, wernerP
, bonneP
, orthoP
, cassiniP
, augustP
, collignonP
, eckert1P
, eckert3P
, eckert5P
, faheyP
, foucautP
, lagrangeP
) where
import Codec.Picture
import Codec.Picture.Types
import Control.Monad
import Control.Monad.ST
-- import Data.List
import Data.Maybe
import Debug.Trace
import Reanimate
-- Constants
halfPi, sqrtPi, epsilon, tau :: Double
halfPi = pi/2
sqrtPi = sqrt pi
epsilon = 1.0e-12
tau = pi*2
toRads, cot :: Double -> Double
toRads dec = dec/180 * pi
cot = recip . tan
srcPixel :: Image PixelRGB8 -> LonLat -> PixelRGB8
srcPixel src (LonLat lam phi) =
pixelAt src xPx yPx
where
xPx = round $ ((lam+pi)/tau) * fromIntegral (imageWidth src-1)
yPx = round $ (1-((phi+halfPi)/pi)) * fromIntegral (imageHeight src-1)
findValidCoord :: Image PixelRGB8 -> Projection -> XYCoord -> XYCoord
findValidCoord src p (XYCoord x y) = fromMaybe (XYCoord x y) $ listToMaybe
[ XYCoord x' y'
| let xi = round $ x * wMax
yi = round $ y * hMax
, ax <- [xi, xi-1, xi+1]
, ay <- [yi, yi-1, yi+1]
, (ax >= 0 && ax < w && ay >= 0 && ay < h)
, let x' = fromIntegral ax / wMax
y' = fromIntegral ay / hMax
, validLonLat $ projectionInverse p (XYCoord x' y')
]
where
w = imageWidth src
h = imageHeight src
wMax = fromIntegral (w-1)
hMax = fromIntegral (h-1)
isInWorld :: Projection -> XYCoord -> Bool
isInWorld p coord =
odd $ length $ isInWorld' p coord
isInWorld' :: Projection -> XYCoord -> [(Double, Double)]
isInWorld' p (XYCoord x y) =
[ (x1, y1)
| (XYCoord x1 y1, XYCoord x2 y2) <- world
, (y1 > y) /= (y2 > y) -- y is between y1 and y2
, x < (x2 - x1) * (y - y1) / (y2 - y1) + x1 -- x is to the left of the line
]
where
world = worldPolygon p
worldPolygon :: Projection -> [(XYCoord, XYCoord)]
worldPolygon p =
interp (-pi, -halfPi) (-pi, halfPi) ++
interp (-pi, halfPi) (pi, halfPi) ++
interp (pi, halfPi) (pi, -halfPi) ++
interp (pi, -halfPi) (-pi, -halfPi)
where
apply (lam, phi) = projectionForward p $ LonLat lam phi
steps = 100
interp (x1, y1) (x2,y2) =
[ ( apply (fromToS x1 x2 (n/steps), fromToS y1 y2 (n/steps))
, apply (fromToS x1 x2 ((n+1)/steps), fromToS y1 y2 ((n+1)/steps)))
| n <- [0..steps-1]]
findNearestPixel :: MutableImage s PixelRGBA8 -> Int -> Int -> Int -> Int -> ST s PixelRGBA8
findNearestPixel src w h srcX srcY = worker
[ (x, y)
| n <- [1..]
, x <- [srcX-n .. srcY+n]
, y <- if x == srcX-n || x == srcX+n then [srcY-n,srcY+n] else [srcY-n .. srcY+n]
, x >= 0
, y >= 0
, x < w
, y < h
]
where
worker [] = undefined
worker ((x,y):rest) = do
this <- readPixel src x y
if this == blank
then worker rest
else return this
blank = PixelRGBA8 0x00 0x00 0x00 0x00
interpP :: Image PixelRGB8 -> Projection -> Projection -> Double -> Image PixelRGBA8
interpP src p1 p2 t = runST $ do
img <- newMutableImage w h
let blank = PixelRGBA8 0x00 0x00 0x00 0x00
let isBlank pixel = pixel == blank
-- forM_ [0..w-1] $ \x ->
-- forM_ [0..h-1] $ \y -> do
-- let x1 = fromIntegral x / (wMax)
-- y1 = 1 - fromIntegral y / (hMax)
-- when (isInWorld (mergeP p1 p2 t) (XYCoord x1 y1)) $
-- writePixel img x y $ PixelRGBA8 0xFF 0x00 0x00 0xFF
let factor = 2
forM_ [0..(w*factor)-1] $ \x ->
forM_ [0..(h*factor)-1] $ \y -> do
let x1' = fromIntegral x / (wMax*fromIntegral factor)
y1' = fromIntegral y / (hMax*fromIntegral factor)
lonlat = projectionInverse p1 (XYCoord x1' y1')
XYCoord x1 y1 = projectionForward p1 lonlat
XYCoord x2 y2 = findValidCoord src p2 $ projectionForward p2 lonlat
x3 = round $ fromToS x1 x2 t * wMax
y3 = round $ (1 - fromToS y1 y2 t) * hMax
when (validLonLat lonlat && validXYCoord (XYCoord x2 y2)) $ do
when (x3 >= 0 && x3 < w && y3 >= 0 && y3 < h) $ do
writePixel img x3 y3 (promotePixel $ srcPixel src lonlat)
forM_ [0..(w*factor)-1] $ \x ->
forM_ [0..(h*factor)-1] $ \y -> do
let x2' = fromIntegral x / (wMax*fromIntegral factor)
y2' = fromIntegral y / (hMax*fromIntegral factor)
lonlat = projectionInverse p2 (XYCoord x2' y2')
XYCoord x2 y2 = projectionForward p2 lonlat
XYCoord x1 y1 = findValidCoord src p1 $ projectionForward p1 lonlat
-- (x2,y2) = p2 lam phi
x3 = round $ fromToS x1 x2 t * wMax
y3 = round $ (1 - fromToS y1 y2 t) * hMax
when (validLonLat lonlat && validXYCoord (XYCoord x1 y1)) $
when (validLonLat lonlat) $
when (x3 >= 0 && x3 < w && y3 >= 0 && y3 < h) $
writePixel img x3 y3 (promotePixel $ srcPixel src lonlat)
forM_ [1..w-1] $ \x ->
forM_ [0..h-1] $ \y -> do
let x1 = fromIntegral x / (wMax)
y1 = 1 - fromIntegral y / (hMax)
this <- readPixel img x y
when (isBlank this) $
when (isInWorld (mergeP p1 p2 t) (XYCoord x1 y1)) $
writePixel img x y =<< findNearestPixel img w h x y
unsafeFreezeImage img
where
w = imageWidth src
h = imageHeight src
wMax = fromIntegral (w-1)
hMax = fromIntegral (h-1)
eqLonLat :: LonLat -> LonLat -> Bool
eqLonLat (LonLat x1 y1) (LonLat x2 y2)
= eqDouble x1 x2 && eqDouble y1 y2
{-
eqCoords :: XYCoord -> XYCoord -> Bool
eqCoords (XYCoord x1 y1) (XYCoord x2 y2)
= eqDouble x1 x2 && eqDouble y1 y2
-}
eqDouble :: Double -> Double -> Bool
eqDouble a b = abs (a-b) < epsilon
data XYCoord = XYCoord Double Double -- 0 to 1
deriving (Read,Show,Eq,Ord)
data LonLat = LonLat Double Double -- -pi to +pi, -halfPi to +halfPi
deriving (Read,Show,Eq,Ord)
data Projection = Projection
{ projectionForward :: LonLat -> XYCoord
, projectionInverse :: XYCoord -> LonLat
}
-- FIXME: Verify that 'src' has an aspect ratio of 2:1.
project :: Image PixelRGB8 -> Projection -> Image PixelRGBA8
project src (Projection _ pInv) = generateImage fn w h
where
w = imageWidth src
h = imageHeight src
fn xPx yPx =
let x = (fromIntegral xPx / fromIntegral (w-1))
y = 1-(fromIntegral yPx / fromIntegral (h-1))
lonlat = pInv (XYCoord x y)
in
if validLonLat lonlat
then promotePixel (srcPixel src lonlat)
else PixelRGBA8 0 0 0 0
validLonLat :: LonLat -> Bool
validLonLat (LonLat lam phi) =
lam >= -pi && lam <= pi && phi >= -pi/2 && phi <= pi/2
validXYCoord :: XYCoord -> Bool
validXYCoord (XYCoord x y) = x >= 0 && x <= 1 && y >= 0 && y <= 1
isValidP :: Projection -> Bool
isValidP (Projection p pInv) = and
[ check x y
| x <- [0..w-1::Int]
, y <- [0..h-1::Int] ]
where
w = 100
h = 100
check xPx yPx =
let x = (fromIntegral xPx / fromIntegral (w-1))
y = (fromIntegral yPx / fromIntegral (h-1))
lonlat = pInv (XYCoord x y)
lonlat2 = pInv $ p lonlat
in if not (validLonLat lonlat) || eqLonLat lonlat lonlat2
then True
else trace (show (lonlat, lonlat2)) $ False
moveBottomP :: Double -> Projection -> Projection
moveBottomP offset (Projection p pInv) = Projection p' pInv'
where
p' (LonLat lon lat) =
case p (LonLat lon lat) of
XYCoord x y -> XYCoord x (fromToS offset 1 y)
pInv' (XYCoord x y) = pInv (XYCoord x ((y-offset)/(1-offset)))
moveTopP :: Double -> Projection -> Projection
moveTopP offset = flipYAxisP . moveBottomP offset . flipYAxisP
flipYAxisP :: Projection -> Projection
flipYAxisP (Projection p pInv) = Projection p' pInv'
where
p' (LonLat lam phi) =
let XYCoord x y = p (LonLat lam (negate phi))
in XYCoord x (1-y)
pInv' (XYCoord x y) =
let LonLat lam phi = pInv (XYCoord x (1-y))
in LonLat lam (negate phi)
scaleP :: Double -> Double -> Projection -> Projection
scaleP xScale yScale (Projection p pInv) = Projection forward inverse
where
forward lonlat =
case p lonlat of
XYCoord x y -> XYCoord ((x-0.5)*xScale+0.5) ((y-0.5)*yScale+0.5)
inverse (XYCoord x y) =
let new = XYCoord ((x-0.5)/xScale+0.5) ((y-0.5)/yScale+0.5)
in pInv new
mergeP :: Projection -> Projection -> Double -> Projection
mergeP p1 p2 t = Projection p pInv
where
p lonlat =
let XYCoord x1 y1 = projectionForward p1 lonlat
XYCoord x2 y2 = projectionForward p2 lonlat
in XYCoord (fromToS x1 x2 t) (fromToS y1 y2 t)
pInv coord =
let LonLat lon1 lat1 = projectionInverse p1 coord
LonLat lon2 lat2 = projectionInverse p2 coord
in
if | oob lon1 lat1 && oob lon2 lat2 -> LonLat (0/0) (0/0)
| otherwise -> LonLat (fromToS lon1 lon2 t) (fromToS lat1 lat2 t)
oob lon lat = lon < (-pi) || lon > pi || lat < (-pi/2) || lat > pi/2
-- | <<docs/gifs/doc_equirectangularP.gif>>
equirectangularP :: Projection
equirectangularP = Projection forward inverse
where
forward (LonLat lam phi) = XYCoord ((lam+pi)/tau) ((phi+pi/2)/pi)
inverse (XYCoord x y) = LonLat xPi yPi
where
xPi = fromToS (-pi) pi x
yPi = fromToS (-pi/2) (pi/2) y
-- | <<docs/gifs/doc_mercatorP.gif>>
mercatorP :: Projection
mercatorP = Projection forward inverse
where
forward (LonLat lam phi) =
XYCoord ((lam+pi)/tau)
(((log(tan(pi/4+phi/2))) + pi)/tau)
inverse (XYCoord x y) = LonLat xPi (atan (sinh yPi))
where
xPi = fromToS (-pi) pi x
yPi = fromToS (-pi) pi y
-- | <<docs/gifs/doc_mollweideP.gif>>
mollweideP :: Projection
mollweideP = Projection forward inverse
where
forward (LonLat lam phi) =
XYCoord ((x+sqrt2*2)/(4*sqrt2)) ((y+sqrt2)/(2*sqrt2))
where
sqrt2 = sqrt 2
x = (2*sqrt2)/pi * lam * cos theta
y = sqrt2*sin theta
theta = find_theta 100
find_theta :: Int -> Double
find_theta 0 = phi
find_theta _ | abs phi == pi/2 = signum phi * pi/2
find_theta n =
let sub = find_theta (n-1)
in sub - (2*sub+sin (2*sub)-pi*sin phi)/(2+2*cos(2*sub))
inverse (XYCoord x' y') = LonLat lam phi
where
x = fromToS (-2*sqrt(2)) (2*sqrt(2)) x'
y = fromToS (-sqrt 2) (sqrt 2) y'
theta = asin (y/sqrt(2))
lam = pi*x/(2*sqrt(2)*cos theta)
phi = asin ((2*theta+sin(2*theta))/pi)
-- | <<docs/gifs/doc_hammerP.gif>>
hammerP :: Projection
hammerP = Projection forward inverse
where
forward (LonLat lam phi) =
XYCoord ((x+sqrt2*2)/(4*sqrt2)) ((y+sqrt2)/(2*sqrt2))
where
sqrt2 = sqrt 2
x = (2*sqrt2*cos phi*sin (lam/2))/(sqrt (1+cos phi*cos (lam/2)))
y = (sqrt2*sin phi)/(sqrt (1+cos phi*cos (lam/2)))
inverse (XYCoord x' y') = LonLat lam phi
where
x = fromToS (-2*sqrt(2)) (2*sqrt(2)) x'
y = fromToS (-sqrt 2) (sqrt 2) y'
z = sqrt (1 - (x/4)**2 - (y/2)**2)
lam = 2 * atan2 (z*x) (2*(2*z**2-1))
phi = asin (z*y)
-- | <<docs/gifs/doc_lambertP.gif>>
lambertP :: Projection
lambertP = Projection forward inverse
where
forward (LonLat lam phi) =
XYCoord ((lam+pi)/tau) ((sin phi+1)/2)
inverse (XYCoord x' y') = LonLat x (asin y)
where
x = fromToS (-pi) (pi) x'
y = fromToS (-1) 1 y'
-- | <<docs/gifs/doc_bottomleyP.gif>>
bottomleyP :: Double -> Projection
bottomleyP phi_1 = Projection forward inverse
where
forward (LonLat lam phi) =
XYCoord ((x+pi)/tau) ((y+pi/2)/pi)
where
x = (rho * sin e) / sin phi_1
y = pi/2 - rho * cos e
rho = pi/2 - phi
e = lam * sin phi_1 * sin rho / rho
inverse (XYCoord x' y') = LonLat lam phi
where
x = fromToS (-pi) (pi) x'
y = fromToS (-pi/2) (pi/2) y'
x1 = x * sin phi_1
y1 = pi/2 - y
rho = sqrt (x1*x1 + y1*y1)
e = atan2 x1 y1
lam = (if rho == 0 then 1 else rho / sin rho) * e/sin phi_1
phi = pi/2 - rho
-- | <<docs/gifs/doc_sinusoidalP.gif>>
sinusoidalP :: Projection
sinusoidalP = Projection forward inverse
where
forward (LonLat lam phi) =
XYCoord ((x+pi)/tau) ((y+pi/2)/pi)
where
x = lam * cos phi
y = phi
inverse (XYCoord x' y') = LonLat (x/cos y) y
where
x = fromToS (-pi) (pi) x'
y = fromToS (-pi/2) (pi/2) y'
-- | <<docs/gifs/doc_wernerP.gif>>
wernerP :: Projection
wernerP = moveTopP 0.23 $ Projection forward inverse
where
forward (LonLat lam phi) =
XYCoord ((x+pi)/tau) ((y+pi/2)/pi)
where
rho = pi/2 - phi
e = lam * sin rho / rho
x = rho * sin e
y = pi/2 - rho * cos e
inverse (XYCoord x' y') = LonLat lam phi
where
x = fromToS (-pi) (pi) x'
y = fromToS (-pi/2) (pi/2) y'
rho = sqrt (x**2 + (pi/2 - y)**2)
e = atan2 x (pi/2 -y)
phi = pi/2 - rho
lam = e * rho / sin rho
-- FIXME: find right scale and position.
-- | <<docs/gifs/doc_bonneP.gif>>
bonneP :: Double -> Projection
bonneP 0 = sinusoidalP
bonneP phi_0 = moveTopP (-0.17*factor) $ scaleP 1 (fromToS 1 0.65 factor) $ Projection forward inverse
where
factor = sin phi_0 / sin (pi/4)
forward (LonLat lam phi ) = XYCoord ((x+pi)/tau) ((y+halfPi)/pi)
where
cotPhi0 = cot phi_0
rho = cotPhi0 + phi_0 - phi
e = (lam * cos phi) / rho
x = rho * sin e
y = cotPhi0 - rho * cos e
inverse (XYCoord x' y') = LonLat lam phi
where
x = fromToS (-pi) (pi) x'
y = fromToS (-pi/2) (pi/2) y'
cotPhi0 = cot phi_0
rho = sqrt (x*x + (cot phi_0 - y)**2)
-- e = atan2 x (cot phi_1 + phi_1 + y)
phi = cotPhi0 + phi_0 - rho
lam = rho / cos phi * atan2 x (cotPhi0-y)
-- | <<docs/gifs/doc_orthoP.gif>>
orthoP :: Double -> Double -> Projection
orthoP lam_0 phi_0 = Projection forward inverse
where
forward (LonLat lam phi)
| (lam+lam_0) < -halfPi || (lam+lam_0) > halfPi ||
(phi+phi_0) < -halfPi/2 || (phi+phi_0) > halfPi/2
= XYCoord (0/0) (0/0)
forward (LonLat lam phi) =
XYCoord ((x+(16/9))/(16/9*2)) ((y+1)/2)
where
x = cos phi * sin (lam - lam_0)
y = cos phi_0 * sin phi - sin phi_0 * cos phi * cos (lam - lam_0)
inverse (XYCoord x' y') = LonLat lam phi
where
x = fromToS (-16/9) (16/9) x'
y = fromToS (-1) (1) y'
lam = wrap (-pi) (pi) $
lam_0 + atan2 (x * sin c) (rho * cos c * cos phi_0 - y * sin c * sin phi_0)
phi = wrap (-pi/2) (pi/2) $
asin ((cos c * sin phi_0 + y * sin c * cos phi_0)/rho)
rho = sqrt (x**2 + y**2)
c = asin rho
wrap lower upper v
| v > upper = v-upper+lower
| v < lower = v+upper-lower
| otherwise = v
cassiniP :: Projection
cassiniP = Projection forward inverse
where
forward (LonLat lam phi) =
XYCoord ((asin (cos phi * sin lam)+halfPi)/pi) ((atan2 (tan phi) (cos lam)+pi)/tau)
inverse (XYCoord x' y') = LonLat lam phi
where
x = fromToS (-halfPi) halfPi x'
y = fromToS (-pi) (pi) y'
lam = atan2 (tan x) (cos y)
phi = asin (sin y * cos x)
augustP :: Projection
augustP = scaleP 0.70 0.70 $ Projection forward inverse
where
xHi = 16/3
xLo = -xHi
yHi = 8 / 3
yLo = -yHi
forward (LonLat lam phi) = XYCoord ((xPos-xLo)/(xHi-xLo)) ((yPos-yLo)/(yHi-yLo))
where
tanPhi = tan (phi/2)
k = sqrt (1 - tanPhi * tanPhi)
c = 1 + k * cos (lam / 2)
x = sin (lam/2) * k / c
y = tanPhi / c
x2 = x*x
y2 = y*y
xPos = (4 / 3 * x * (3+x2 - 3*y2))
yPos = (4 / 3 * y * (3 + 3*x2 - y2))
inverse (XYCoord x' y') = LonLat lam phi
where
x = fromToS xLo xHi x' * 3 / 8
y = fromToS yLo yHi y' * 3 / 8
x2 = x*x
y2 = y*y
s = 1 + x2 + y2
sin3Eta = sqrt ((s - sqrt (s*s - 4 * y * y)) / 2)
eta = asin (sin3Eta) / 3
xi = if sin3Eta /= 0 then acosh (abs (y / sin3Eta)) / 3 else asinh (abs x) / 3
cosEta = cos eta
coshXi = cosh xi
d = coshXi * coshXi - cosEta * cosEta
lam = signum x * 2 * atan2 (sinh xi * cosEta) (0.25 - d)
phi = signum y * 2 * atan2 (coshXi * sin eta) (0.25 + d)
-- | <<docs/gifs/doc_collignonP.gif>>
collignonP :: Projection
collignonP = Projection forward inverse
where
yHi = sqrtPi
yLo = sqrtPi * (1 - sqrt 2)
xLo = -pi*(2/sqrtPi)*(sqrt 2)
xHi = pi*(2/sqrtPi)*(sqrt 2)
forward (LonLat lam phi) = XYCoord ((x-xLo)/(xHi-xLo)) ((y-yLo)/(yHi-yLo))
where
alpha = sqrt (1 - sin phi)
x = (2 / sqrtPi) * lam * alpha
y = sqrtPi * (1 - alpha)
inverse (XYCoord x' y') = LonLat lam phi
where
x = fromToS xLo xHi x'
y = fromToS yLo yHi y'
l = (y / sqrtPi -1)**2
lam = if l > 0 then x * sqrt (pi / l) / 2 else 0
phi = asin (1-l)
-- | <<docs/gifs/doc_eckert1P.gif>>
eckert1P :: Projection
eckert1P = Projection forward inverse
where
alpha = sqrt (8 / (3*pi))
yLo = -alpha * halfPi
yHi = alpha * halfPi
xLo = -alpha * pi
xHi = alpha * pi
forward (LonLat lam phi) = XYCoord ((x-xLo)/(xHi-xLo)) ((y-yLo)/(yHi-yLo))
where
x = alpha * lam * (1 - abs phi / pi)
y = alpha * phi
inverse (XYCoord x' y') = LonLat lam phi
where
x = fromToS xLo xHi x'
y = fromToS yLo yHi y'
phi = y / alpha
lam = x / (alpha * (1 - abs phi / pi))
-- | <<docs/gifs/doc_eckert3P.gif>>
eckert3P :: Projection
eckert3P = Projection forward inverse
where
k = sqrt (pi * (4 + pi))
yLo = negate yHi
yHi = 4/k * halfPi
xLo = negate xHi
xHi = 4/k * pi
forward (LonLat lam phi) = XYCoord ((x-xLo)/(xHi-xLo)) ((y-yLo)/(yHi-yLo))
where
x = 2 / k * lam * (1 + sqrt (1 - 4*phi*phi/(pi*pi)))
y = 4 / k * phi
inverse (XYCoord x' y') = LonLat lam phi
where
x = fromToS xLo xHi x'
y = fromToS yLo yHi y'
lam = x * k/2 / (1 + sqrt (1 - y*y* (4+pi)/(4*pi)))
phi = y * k/4
-- | <<docs/gifs/doc_eckert5P.gif>>
eckert5P :: Projection
eckert5P = Projection forward inverse
where
k = sqrt (2 + pi)
yLo = negate yHi
yHi = pi/k
xLo = negate xHi
xHi = tau/k
forward (LonLat lam phi) = XYCoord ((x-xLo)/(xHi-xLo)) ((y-yLo)/(yHi-yLo))
where
x = lam * (1 + cos phi) / k
y = 2 * phi / k
inverse (XYCoord x' y') = LonLat lam phi
where
x = fromToS xLo xHi x'
y = fromToS yLo yHi y'
lam = k * x / (1 + cos phi)
phi = y * k / 2
-- | <<docs/gifs/doc_faheyP.gif>>
faheyP :: Projection
faheyP = Projection forward inverse
where
faheyK = cos (toRads 35)
yLo = negate yHi
yHi = 1 + faheyK
xLo = negate xHi
xHi = pi * faheyK * 16/9
forward (LonLat lam phi) = XYCoord ((x-xLo)/(xHi-xLo)) ((y-yLo)/(yHi-yLo))
where
t = tan (phi/2)
x = lam * faheyK * sqrt (1 - t*t)
y = (1 + faheyK) * t
inverse (XYCoord x' y') = LonLat lam phi
where
x = fromToS xLo xHi x'
y = fromToS yLo yHi y'
t = y / (1 + faheyK)
lam = x / (faheyK * sqrt (1 - t*t))
phi = 2 * atan2 y (1 + faheyK)
foucautP :: Projection
foucautP = Projection forward inverse
where
yLo = negate yHi
yHi = sqrtPi * tan (halfPi/2)
xLo = negate xHi
xHi = tau/sqrtPi
forward (LonLat lam phi) = XYCoord ((x-xLo)/(xHi-xLo)) ((y-yLo)/(yHi-yLo))
where
k = phi / 2
cosk = cos k
x = 2 * lam / sqrtPi * cos phi * cosk * cosk
y = sqrtPi * tan k
inverse (XYCoord x' y') = LonLat lam phi
where
x = fromToS xLo xHi x'
y = fromToS yLo yHi y'
k = atan (y / sqrtPi)
cosk = cos k
phi = 2 * k
lam = x * sqrtPi / 2 / (cos phi * cosk * cosk)
lagrangeP :: Projection
lagrangeP = Projection forward inverse
where
yLo = negate yHi
yHi = 2
xLo = negate xHi
xHi = 2
n = 0.5
forward (LonLat lam phi)
| abs (abs phi - halfPi) < epsilon = XYCoord 0.5 (if phi < 0 then 0 else 1)
| otherwise = XYCoord ((x-xLo)/(xHi-xLo)) ((y-yLo)/(yHi-yLo))
where
sinPhi = sin phi
v = ((1+sinPhi) / (1 - sinPhi))**(n/2)
c = 0.5 * (v + 1/v) + cos (lam*n)
x = 2 * sin (lam*n) / c
y = (v - 1/v) /c
inverse (XYCoord x' y')
| abs ((abs y')-1) < epsilon = LonLat 0 (signum y * halfPi)
| otherwise = LonLat lam phi
where
x = fromToS xLo xHi x' / 2
y = fromToS yLo yHi y' / 2
x2 = x * x
y2 = y * y
t = 2 * y / (1 + x2 + y2)
t' = ((1+t) / (1-t)) ** (1/n)
lam = atan2 (2*x) (1-x2-y2) / n
phi = asin ((t'-1)/(t'+1))

View file

@ -87,6 +87,7 @@ mkTexScript latexExec latexArgs texHeaders tex = T.unlines $
[ "% " <> T.pack (unwords (latexExec:latexArgs))
, "\\documentclass[preview]{standalone}"
, "\\usepackage{amsmath}"
, "\\usepackage{gensymb}"
] ++ texHeaders ++
[ "\\usepackage[english]{babel}"
, "\\linespread{1}"

View file

@ -6,6 +6,7 @@ module Reanimate.Raster
, vectorize
, vectorize_
, svgAsPngFile
, svgAsPngFile'
) where
import Codec.Picture.Types
@ -128,8 +129,14 @@ vectorize_ args path = unsafePerformIO $ do
-- imageAsFile img
svgAsPngFile :: Tree -> FilePath
svgAsPngFile _ | pNoExternals = "/svgAsPngFile/has/been/disabled"
svgAsPngFile svg = unsafePerformIO $ cacheFile template $ \pngPath -> do
svgAsPngFile = svgAsPngFile' width height
where
width = 2560
height = width * 9 `div` 16
svgAsPngFile' :: Int -> Int -> Tree -> FilePath
svgAsPngFile' _ _ _ | pNoExternals = "/svgAsPngFile/has/been/disabled"
svgAsPngFile' width height svg = unsafePerformIO $ cacheFile template $ \pngPath -> do
let svgPath = replaceExtension pngPath "svg"
-- ffmpeg <- requireExecutable "ffmpeg"
-- convert <- requireExecutable "convert"
@ -139,6 +146,4 @@ svgAsPngFile svg = unsafePerformIO $ cacheFile template $ \pngPath -> do
runCmd inkscape [ svgPath, "--export-png=" ++ pngPath, "--without-gui" ]
where
template = show (hash rendered) <.> "png"
rendered = renderSvg (Just $ Num width) (Just $ Num height) svg
width = 2560
height = width * 9 / 16
rendered = renderSvg (Just $ Px $ fromIntegral width) (Just $ Px $ fromIntegral height) svg

View file

@ -12,11 +12,11 @@ import Control.Monad (forM_)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Graphics.SvgTree (Number (..))
import Numeric
import Reanimate.Animation
import Reanimate.Misc
import System.FilePath ((</>))
-- import System.FilePath (replaceExtension)
import System.Exit
import System.FilePath ((</>))
import System.IO
import Text.Printf (printf)
@ -99,14 +99,14 @@ render ani target format width height fps = do
RenderGif -> withTempFile "png" $ \palette -> do
runCmd ffmpeg ["-i", template, "-y"
,"-vf", "fps="++show fps++",scale=320:-1:flags=lanczos,palettegen"
,"-t", show (duration ani)
,"-t", showFFloat Nothing (duration ani) ""
, palette ]
runCmd ffmpeg ["-i", template, "-y"
,"-i", palette
,"-progress", progress
,"-filter_complex"
,"fps="++show fps++",scale=320:-1:flags=lanczos[x];[x][1:v]paletteuse"
,"-t", show (duration ani)
,"-t", showFFloat Nothing (duration ani) ""
, target]
RenderWebm ->
runCmd ffmpeg ["-r", show fps, "-i", template, "-y"

View file

@ -29,12 +29,12 @@ boundingBox t =
svgHeight :: Tree -> Double
svgHeight t = h
where
(_w, h, _x, _y) = boundingBox t
(_x, _y, _w, h) = boundingBox t
svgWidth :: Tree -> Double
svgWidth t = w
where
(w, _h, _x, _y) = boundingBox t
(_x, _y, w, _h) = boundingBox t
linePoints :: [LineCommand] -> [RPoint]
linePoints = worker zero
@ -87,22 +87,22 @@ svgBoundingPoints t = map (Transform.transformPoint m) $
(Num x, Num y) -> V2 x y
_ -> error "Reanimate.Svg.svgBoundingPoints: Unrecognized number format."
circleBoundingPoints circ =
circleBoundingPoints circ =
let (xnum, ynum) = circ ^. circleCenter
rnum = circ ^. circleRadius
in case mapMaybe unpackNumber [xnum, ynum, rnum] of
in case mapMaybe unpackNumber [xnum, ynum, rnum] of
[x, y, r] -> [ V2 (x + r * cos angle) (y + r * sin angle) | angle <- [0, pi/10 .. 2 * pi]]
_ -> []
ellipseBoundingPoints e =
ellipseBoundingPoints e =
let (xnum,ynum) = e ^. ellipseCenter
xrnum = e ^. ellipseXRadius
yrnum = e ^. ellipseYRadius
in case mapMaybe unpackNumber [xnum, ynum, xrnum, yrnum] of
in case mapMaybe unpackNumber [xnum, ynum, xrnum, yrnum] of
[x,y,xr,yr] -> [V2 (x + xr * cos angle) (y + yr * sin angle) | angle <- [0, pi/10 .. 2 * pi]]
_ -> []
unpackNumber n =
case toUserUnit defaultDPI n of
Num d -> Just d
_ -> Nothing
_ -> Nothing

View file

@ -42,13 +42,15 @@ module Reanimate.Svg.Constructors
, flipXAxis
, flipYAxis
, aroundCenter
, aroundCenterX
, aroundCenterY
, withTransformations
-- * Other
, mkColor
, mkBackground
, mkBackgroundPixel
, gridLayout
) where
import Codec.Picture (PixelRGBA8 (..))
@ -98,6 +100,20 @@ aroundCenter fn t =
offsetY = -y-h/2
(x,y,w,h) = boundingBox t
aroundCenterY :: (Tree -> Tree) -> Tree -> Tree
aroundCenterY fn t =
translate 0 (-offsetY) $ fn $ translate 0 offsetY t
where
offsetY = -y-h/2
(_x,y,_w,h) = boundingBox t
aroundCenterX :: (Tree -> Tree) -> Tree -> Tree
aroundCenterX fn t =
translate (-offsetX) 0 $ fn $ translate offsetX 0 t
where
offsetX = -x-w/2
(x,_y,w,_h) = boundingBox t
-- | Scale the image uniformly by given factor along both X and Y axes.
-- For example @scale 2 image@ makes the image twice as large, while @scale 0.5 image@ makes it half the original size.
-- Negative values are also allowed, and lead to flipping the image along both X and Y axes.

View file

@ -0,0 +1,228 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE OverloadedStrings #-}
module Main(main) where
import Codec.Picture
import Codec.Picture.Jpg
import Codec.Picture.Types
import Control.Lens ((^.))
import Control.Monad
import Control.Monad.ST
import Data.Aeson
import qualified Data.ByteString as BS
import Data.Foldable
import Data.Geospatial hiding (LonLat)
import Data.LinearRing
import qualified Data.LineString as Line
import Data.Map (Map)
import qualified Data.Map as Map
import qualified Data.Text as T
import Graphics.SvgTree (PathCommand (..), Tree (None))
import Reanimate
import Reanimate.Animation
import Reanimate.GeoProjection
import Reanimate.Scene
import System.IO.Unsafe
main :: IO ()
main = seq equirectangular $ reanimate $ sceneAnimation $ do
prevProj <- newVar equirectangularP
txtVar <- newVar "Equirectangular"
txtS <- newSprite $ renderLabel <$> unVar txtVar
txtFade <- spriteVar txtS 1 withGroupOpacity
spriteZ txtS 2
let pushInterp = pushMerge' (\a b t -> interpP src a b t)
pushMerge' fn label proj = do
fork $ do
tweenVar txtFade 0.2 $ \v -> fromToS v 0 . curveS 2
writeVar txtVar label
tweenVar txtFade 0.2 $ \v -> fromToS v 1 . curveS 2
prev <- readVar prevProj
play $ pauseAtEnd waitT $ signalA (curveS 2) $
mkAnimation morphT $ \t ->
mkGroup $
[ scaleToSize screenWidth screenHeight $
embedImage $ fn prev proj t
, grid $ mergeP prev proj t ]
writeVar prevProj proj
pushT mkLabel mkProj = do
fork $ tweenVar txtVar morphT $ \v t -> if t > 0 then mkLabel t else v
play $ pauseAtEnd waitT $ signalA (curveS 2) $
mkAnimation morphT $ \t ->
mkGroup $
[ scaleToSize screenWidth screenHeight $
embedImage $ project src $ mkProj t
, grid $ mkProj t ]
writeVar prevProj (mkProj 1)
play $ staticFrame (waitT/2) $
mkGroup
[ scaleToSize screenWidth screenHeight $
embedImage $ project src equirectangularP
, grid equirectangularP ]
-- pushInterp "Lambert" lambertP
-- 1
pushInterp "Web Mercator" mercatorP
-- 2
pushInterp "Mollweide" mollweideP
-- 3
pushInterp "Bottomley 30\\degree" (bottomleyP (toRads 30))
-- 4
pushInterp "Werner" wernerP
-- 5
pushInterp "Bonne 45\\degree" (bonneP (toRads 45))
pushT
(\t -> "Bonne " <> T.pack (show $ round $ fromToS 45 0 t) <> "\\degree")
(bonneP . toRads . fromToS 45 0)
-- 6
pushInterp "Eckert I" eckert1P
eckert <- newSpriteSVG $ renderLabel "Eckert"
spriteZ eckert 2
pushInterp "Eckert III" eckert3P
pushInterp "Eckert V" eckert5P
destroySprite eckert
-- 7
pushInterp "Fahey" faheyP
-- 8
pushInterp "August" augustP
-- 9
pushInterp "Foucaut" foucautP
-- 10
pushInterp "Lagrange" lagrangeP
pushInterp "Equirectangular" equirectangularP
where
src = equirectangular
waitT = 2
morphT = 2
renderLabel label =
let ref = scale 1.5 $ latex "\\texttt{Tygv123}"
glyphs = scale 1.5 $ latex ("\\texttt{" <> label <> "}")
svgTxt = mkGroup
[ withStrokeColor "black" $ withFillColor "white" $
glyphs
, withFillColor "white" $
glyphs ]
in
translate (screenWidth*0.01) (screenHeight*0.02) $
translate (-screenWidth/2) (-screenHeight/2) $
translate 0 (svgHeight ref) svgTxt
equirectangular :: Image PixelRGB8
equirectangular = unsafePerformIO $ do
dat <- BS.readFile "earth.jpg"
case decodeJpeg dat of
Left err -> error err
Right img -> return $ convertRGB8 img
toRads :: Double -> Double
toRads dec = dec/180 * pi
grid :: Projection -> SVG
grid p =
scaleXY
(screenWidth)
(screenHeight)
$
translate (-1/2) (-1/2) $
withStrokeWidth strokeWidth $
withFillOpacity 0 $
mkGroup
[ mkGroup []
, withStrokeColorPixel (PixelRGBA8 0x90 0x90 0x90 0x0) $
withFillOpacity 0 $ mkGroup
[ geometryToSVG p geo
| geo <- landBorders
]
, withStrokeColorPixel (PixelRGBA8 0x30 0x30 0x30 0x0) $
mkGroup $ map mkLinePath (latitudeLines p ++ longitudeLines p)
]
where
strokeWidth = defaultStrokeWidth * 0.02
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 = 4
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 <- [0 .. lonLines*2]]
where
lonLines = 4
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 "land.geojson"
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

View file

@ -0,0 +1,6 @@
#!/usr/bin/env stack
-- stack runghc --package reanimate
module Main(main) where
main :: IO ()
main = return ()

View file

@ -0,0 +1 @@
088ba82a112f25607c756814500ad4246d7ced4c