packages feed

astro-0.4.1.0: app/Main.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where

import GHC.Generics
import Data.Aeson
import qualified Data.ByteString.Lazy.Char8 as B
import Data.Maybe (fromMaybe)
import Data.Time.LocalTime (ZonedTime, getZonedTime)
import Options.Applicative
import Data.Monoid((<>))

-- Astro Imports
import Data.Astro.Time.JulianDate
import Data.Astro.Time.Conv (zonedTimeToLCT, zonedTimeToLCD, lctToZonedTime)

import Data.Astro.Effects (refract)
import Data.Astro.CelestialObject.RiseSet(riseAndSetLCT, riseAndSet2, RiseSetMB(..), RiseSetLCT(..))

import Data.Astro.Sun

import Data.Astro.Star

import Data.Astro.Types
import Data.Astro.Coordinate

import Data.Astro.Moon (moonPosition1, moonDistance1, moonAngularSize)
import Data.Astro.Moon.MoonDetails (j2010MoonDetails, mduToKm)

import Data.Astro.Planet (Planet(..), planetPosition, planetTrueAnomaly1, planetDistance1, planetAngularDiameter)
import Data.Astro.Planet.PlanetDetails (j2010PlanetDetails)


main :: IO ()
main = execParser opts >>= run
  where opts = info (cmdOptions <**> helper)
          ( progDesc "Amateur astronomical computations"
            <> header "Astro" )


run :: CmdOptions -> IO ()
run cmdOptions = do
  defParams <- defaultParams
  let params = fromMaybe defParams $ fromMaybe defParams <$> decode <$> B.pack <$> cmdJson cmdOptions
      res = processQuery params
  B.putStrLn $ encode res


-- Calcs
calculateSunResult :: Params -> PlanetaiResult
calculateSunResult params = PR {
  riseSet = riseSet
  , distance = DR distance "km"
  , angularSize = angularSize
  , position = hcPosition
  }
  where coords = paramsCoordinates params
        date = paramsDate params
        lct = paramsDateTime params
        jd = lctUniversalTime lct
        rs = sunRiseAndSet coords 0.833333 date
        riseSet = toRiseSetResult rs
        distance = sunDistance jd
        DD angularSize = sunAngularSize jd
        ec1 = sunPosition2 jd
        hcPosition = toHorizonCoordinatesResult coords jd ec1


calculateMoonResult :: Params -> PlanetaiResult
calculateMoonResult params = PR {
  riseSet = riseSet
  , distance = DR distance "km"
  , angularSize = angularSize
  , position = hcPosition
  }
  where position = moonPosition1 j2010MoonDetails
        coords = paramsCoordinates params
        verticalShift = refract (DD 0) 12 1012
        date = paramsDate params
        lct = paramsDateTime params
        jd = lctUniversalTime lct
        rs = riseAndSet2 0.000001 position coords verticalShift date
        riseSet = toRiseSetResult rs
        mdu = moonDistance1 j2010MoonDetails jd
        distance = mduToKm mdu
        DD angularSize = moonAngularSize mdu
        ec1 = position jd
        hcPosition = toHorizonCoordinatesResult coords jd ec1


calculatePlanetResult :: Params -> Planet -> PlanetaiResult
calculatePlanetResult params planet = PR {
  riseSet = riseSet
  , distance = DR distance "AU"
  , angularSize = angularSize
  , position = hcPosition
  }
  where coords = paramsCoordinates params
        verticalShift = refract (DD 0) 12 1012
        date = paramsDate params
        lct = paramsDateTime params
        jd = lctUniversalTime lct
        planetDetails = j2010PlanetDetails planet
        earthDetails = j2010PlanetDetails Earth
        position = planetPosition planetTrueAnomaly1 planetDetails earthDetails
        rs = riseAndSet2 0.000001 position coords verticalShift date
        riseSet = toRiseSetResult rs
        au = planetDistance1 planetDetails earthDetails jd
        AU distance = au
        DD angularSize = planetAngularDiameter planetDetails au
        ec1 = position jd
        hcPosition = toHorizonCoordinatesResult coords jd ec1


calculateStarResult :: Params -> Star -> StarResult
calculateStarResult params star = SR {
  starRiseSet = riseSet
  , starPosition = hcPosition
  }
  where coords = paramsCoordinates params
        verticalShift = refract (DD 0) 12 1012
        date = paramsDate params
        lct = paramsDateTime params
        jd = lctUniversalTime lct
        ec1 = starCoordinates star
        rs = riseAndSetLCT coords date verticalShift ec1
        riseSet = fromRiseSetLCT rs
        hcPosition = toHorizonCoordinatesResult coords jd ec1


toRiseSetResult :: RiseSetMB -> RiseSetResult
toRiseSetResult rs = case rs of
  RiseSet rise set -> RSR { rise = lctToZonedTime <$> fst <$> rise
                          , riseAzimuth = ddValue <$> snd <$> rise
                          , set = lctToZonedTime <$> fst <$> set
                          , setAzimuth = ddValue <$> snd <$> set
                          , state = "Rise and/or set"
                          }
  Circumpolar -> RSR Nothing Nothing Nothing Nothing "Circumpolar"
  NeverRises -> RSR Nothing Nothing Nothing Nothing "NeverRises"


fromRiseSetLCT :: RiseSetLCT -> RiseSetResult
fromRiseSetLCT rs = case rs of
  RiseSet rise set -> RSR { rise = Just $ lctToZonedTime $ fst rise
                          , riseAzimuth = Just $ ddValue $ snd $ rise
                          , set = Just $ lctToZonedTime $ fst set
                          , setAzimuth = Just $ ddValue $ snd $ set
                          , state = "Rise and Set"
                          }
  Circumpolar -> RSR Nothing Nothing Nothing Nothing "Circumpolar"
  NeverRises -> RSR Nothing Nothing Nothing Nothing "NeverRises"


ddValue :: DecimalDegrees -> Double
ddValue (DD value) = value

toHorizonCoordinatesResult :: GeographicCoordinates
                           -> JulianDate
                           -> EquatorialCoordinates1
                           -> HorizonCoordinatesResult
toHorizonCoordinatesResult (GeoC lat long) jd (EC1 delta alpha) = HCR altitude azimuth
  where ec2 = EC2 delta (raToHA alpha long jd)
        hc = equatorialToHorizon lat ec2
        HC (DD altitude) (DD azimuth) = hc
        
        
        

processQuery :: Params -> AstroResult
processQuery params = AstroResult {
  request = params
  , sun = calculateSunResult params
  , moon = calculateMoonResult params
  , mercury = calculatePlanetResult params Mercury
  , venus = calculatePlanetResult params Venus
  , mars = calculatePlanetResult params Mars
  , jupiter = calculatePlanetResult params Jupiter
  , saturn = calculatePlanetResult params Saturn
  , uranus = calculatePlanetResult params Uranus
  , neptune = calculatePlanetResult params Neptune
  , polaris = calculateStarResult params Polaris
  , alphaCrucis = calculateStarResult params AlphaCrucis
  , sirius = calculateStarResult params Sirius
  , betelgeuse = calculateStarResult params Betelgeuse
  , rigel = calculateStarResult params Rigel
  , vega = calculateStarResult params Vega
  , antares = calculateStarResult params Antares
  , canopus = calculateStarResult params Canopus
  , pleiades = calculateStarResult params Pleiades
  }


-- Command Line Options
data CmdOptions = CmdOptions {
  cmdJson :: Maybe String
  }


cmdOptions :: Parser CmdOptions
cmdOptions = CmdOptions
  <$> (optional $ strOption ( long "json" <> short 'j' <> help "JSON-encoded params") )


-- Params
data CoordinatesParam = CoordinatesParam {
    latitude :: Double
  , longitude  :: Double
  } deriving (Generic, Show)

instance ToJSON CoordinatesParam
instance FromJSON CoordinatesParam


data Params = Params {
  coordinates :: CoordinatesParam
  , datetime :: ZonedTime
  } deriving (Generic, Show)

instance ToJSON Params
instance FromJSON Params


paramsCoordinates :: Params -> GeographicCoordinates
paramsCoordinates params = GeoC (DD $ latitude coords) (DD $ longitude coords)
  where coords = coordinates params


paramsDateTime :: Params -> LocalCivilTime
paramsDateTime = zonedTimeToLCT . datetime


paramsDate :: Params -> LocalCivilDate
paramsDate = zonedTimeToLCD . datetime


greenwichCoordinates :: CoordinatesParam
greenwichCoordinates = CoordinatesParam 51.4768 0


defaultParams :: IO (Params)
defaultParams = do
  time <- getZonedTime
  return Params {
    coordinates = greenwichCoordinates
    , datetime = time
    }


-- Result
data HorizonCoordinatesResult = HCR {
  altitude :: Double
  , azimuth :: Double
  } deriving (Generic, Show)

instance ToJSON HorizonCoordinatesResult

data RiseSetResult = RSR {
  rise :: Maybe ZonedTime
  , riseAzimuth :: Maybe Double
  , set :: Maybe ZonedTime
  , setAzimuth :: Maybe Double
  , state :: String
  } deriving (Generic, Show)

instance ToJSON RiseSetResult


data DistanceResult = DR {
  value :: Double
  , units :: String
  } deriving (Generic, Show)

instance ToJSON DistanceResult

data PlanetaiResult = PR {
  riseSet :: RiseSetResult
  , distance :: DistanceResult
  , angularSize:: Double
  , position :: HorizonCoordinatesResult             
  } deriving (Generic, Show)

instance ToJSON PlanetaiResult

data StarResult = SR {
  starRiseSet :: RiseSetResult
  , starPosition :: HorizonCoordinatesResult
  } deriving (Generic, Show)

instance ToJSON StarResult

data AstroResult = AstroResult {
  request :: Params
  , sun :: PlanetaiResult
  , moon :: PlanetaiResult
  , mercury :: PlanetaiResult
  , venus :: PlanetaiResult
  , mars :: PlanetaiResult
  , jupiter :: PlanetaiResult
  , saturn :: PlanetaiResult
  , uranus :: PlanetaiResult
  , neptune :: PlanetaiResult
  , polaris :: StarResult
  , alphaCrucis :: StarResult
  , sirius :: StarResult
  , betelgeuse :: StarResult
  , rigel :: StarResult
  , vega :: StarResult
  , antares :: StarResult
  , canopus :: StarResult
  , pleiades :: StarResult
  } deriving (Generic, Show)

instance ToJSON AstroResult