astro-0.4.3.0: src/Data/Astro/Coordinate.hs
{-|
Module: Data.Astro.Coordinate
Description: Celestial Coordinate Systems
Copyright: Alexander Ignatyev, 2016
See "Data.Astro.Types" module for Georgraphic Coordinates.
= Celestial Coordinate Systems
== /Horizon coordinates/
* __altitude, α__ - /'how far up'/ angle from the horizontal plane in degrees
* __azimuth, Α__ - /'how far round'/ agle from the north direction in degrees to the east
== /Equatorial coordinates/
Accoring to the equatorial coordinates system stars move westwards along the circles centered in the north selestial pole,
making the full cicrle in 24 hours of sidereal time (see "Data.Astro.Time.Sidereal").
* __declination, δ__ - /'how far up'/ angle from the quatorial plane;
* __right ascension, α__ - /'how far round'/ angle from the /vernal equinox/ to the east; __/or/__
* __hour angle__ - /'how far round'/ angle from the meridian to the west
== /Ecliptic Coordinate/
Accoring to the ecliptic coordinates system the Sun moves eastwards along the trace of th ecliptic. The Sun's ecplitic latitude is always 0.
* __ecliptic latitude, β__ - /'how far up'/ angle from the ecliptic
* __ecliptic longitude, λ__ - /'how far round'/ angle from the /vernal equinox/ to the east
== /Galactic Coordinates/
* __galactic latitute, b__ - /'how far up'/ angle from the plane of the Galaxy
* __galactiv longitude, l__ - - /'how far round'/ angle from the direction the Sun - the centre of the Galaxy
== /Terms/
* __ecliptic__ - the plane containing the Earth's orbit around the Sun
* __vernal equinox__, ♈ - fixed direction lies along the line of the intersection of the equatorial plane and the ecliptic
* __obliquity of the ecliptic, β__ - the angle between the plane of the Earth's equator and the ecliptic
* __north selestial pole, P__ - the point on the selestial sphere, right above the Earth's North Pole
= Examples
== /Horizontal Coordinate System/
@
import Data.Astro.Coordinate
import Data.Astro.Types
hc :: HorizonCoordinates
hc = HC (DD 30.5) (DD 180)
-- HC {hAltitude = DD 30.0, hAzimuth = DD 180.0}
@
== /Equatorial Coordinate System/
@
import Data.Astro.Coordinate
import Data.Astro.Types
ec1 :: EquatorialCoordinates1
ec1 = EC1 (DD 71.7) (DH 8)
-- EC1 {e1Declination = DD 71.7, e1RightAscension = DH 8.0}
ec2 :: EquatorialCoordinates2
ec2 = EC1 (DD 77.7) (DH 11)
-- EC2 {e2Declination = DD 77.7, e2HoursAngle = DH 11.0}
@
== /Transformations/
@
import Data.Astro.Time.JulianDate
import Data.Astro.Coordinate
import Data.Astro.Types
ro :: GeographicCoordinates
ro = GeoC (fromDMS 51 28 40) (-(fromDMS 0 0 5))
dt :: LocalCivilTime
dt = lctFromYMDHMS (DH 1) 2017 6 25 10 29 0
sunHC :: HorizonCoordinates
sunHC = HC (fromDMS 49 18 21.77) (fromDMS 118 55 19.53)
-- HC {hAltitude = DD 49.30604722222222, hAzimuth = DD 118.92209166666666}
sunEC2 :: EquatorialCoordinates2
sunEC2 = horizonToEquatorial (geoLatitude ro) sunHC
-- EC2 {e2Declination = DD 23.378295912623855, e2HoursAngle = DH 21.437117068873537}
sunEC1 :: EquatorialCoordinates1
sunEC1 = EC1 (e2Declination sunEC2) (haToRA (e2HoursAngle sunEC2) (geoLongitude ro) (lctUniversalTime dt))
-- EC1 {e1Declination = DD 23.378295912623855, e1RightAscension = DH 6.29383725890224}
sunEC2' :: EquatorialCoordinates2
sunEC2' = EC2 (e1Declination sunEC1) (raToHA (e1RightAscension sunEC1) (geoLongitude ro) (lctUniversalTime dt))
-- EC2 {e2Declination = DD 23.378295912623855, e2HoursAngle = DH 21.437117068873537}
sunHC' :: HorizonCoordinates
sunHC' = equatorialToHorizon (geoLatitude ro) sunEC2'
-- HC {hAltitude = DD 49.30604722222222, hAzimuth = DD 118.92209166666666}
@
=== /Function-shortcuts/
@
import Data.Astro.Time.JulianDate
import Data.Astro.Coordinate
import Data.Astro.Types
ro :: GeographicCoordinates
ro = GeoC (fromDMS 51 28 40) (-(fromDMS 0 0 5))
dt :: LocalCivilTime
dt = lctFromYMDHMS (DH 1) 2017 6 25 10 29 0
sunHC :: HorizonCoordinates
sunHC = HC (fromDMS 49 18 21.77) (fromDMS 118 55 19.53)
-- HC {hAltitude = DD 49.30604722222222, hAzimuth = DD 118.92209166666666}
sunEC1 :: EquatorialCoordinates1
sunEC1 = hcToEC1 ro (lctUniversalTime dt) sunHC
-- EC1 {e1Declination = DD 23.378295912623855, e1RightAscension = DH 6.29383725890224}
sunHC' :: HorizonCoordinates
sunHC' = ec1ToHC ro (lctUniversalTime dt) sunEC1
-- HC {hAltitude = DD 49.30604722222222, hAzimuth = DD 118.92209166666666}
@
-}
module Data.Astro.Coordinate
(
DecimalDegrees(..)
, DecimalHours(..)
, HorizonCoordinates(..)
, EquatorialCoordinates1(..)
, EquatorialCoordinates2(..)
, EclipticCoordinates(..)
, GalacticCoordinates(..)
, raToHA
, haToRA
, equatorialToHorizon
, horizonToEquatorial
, ec1ToHC
, hcToEC1
, ecHCConv
, obliquity
, eclipticToEquatorial
, equatorialToEcliptic
, galacticToEquatorial
, equatorialToGalactic
)
where
import Data.Astro.Time (utToLST)
import Data.Astro.Time.JulianDate (JulianDate(..), numberOfCenturies, splitToDayAndTime)
import Data.Astro.Time.Epoch (j2000)
import Data.Astro.Time.Sidereal (LocalSiderealTime(..), lstToDH)
import Data.Astro.Types (DecimalDegrees(..), DecimalHours(..)
, fromDecimalHours, toDecimalHours
, toRadians, fromRadians, fromDMS
, GeographicCoordinates(..))
import Data.Astro.Utils (fromFixed)
import Data.Astro.Effects.Nutation (nutationObliquity)
-- | Horizon Coordinates, for details see the module's description
data HorizonCoordinates = HC {
hAltitude :: DecimalDegrees -- ^ alpha
, hAzimuth :: DecimalDegrees -- ^ big alpha
} deriving (Show, Eq)
-- | Equatorial Coordinates, defines fixed position in the sky
data EquatorialCoordinates1 = EC1 {
e1Declination :: DecimalDegrees -- ^ delta
, e1RightAscension :: DecimalHours -- ^ alpha
} deriving (Show, Eq)
-- | Equatorial Coordinates
data EquatorialCoordinates2 = EC2 {
e2Declination :: DecimalDegrees -- ^ delta
, e2HoursAngle :: DecimalHours -- ^ H
} deriving (Show, Eq)
-- | Ecliptic Coordinates
data EclipticCoordinates = EcC {
ecLatitude :: DecimalDegrees -- ^ beta
, ecLongitude :: DecimalDegrees -- ^ lambda
} deriving (Show, Eq)
-- | Galactic Coordinates
data GalacticCoordinates = GC {
gLatitude :: DecimalDegrees -- ^ b
, gLongitude :: DecimalDegrees -- ^ l
} deriving (Show, Eq)
-- | Convert Right Ascension to Hour Angle for specified longitude and Universal Time
raToHA :: DecimalHours -> DecimalDegrees -> JulianDate -> DecimalHours
raToHA = haRAConv
-- | Convert Hour Angle to Right Ascension for specified longitude and Universal Time
haToRA :: DecimalHours -> DecimalDegrees -> JulianDate -> DecimalHours
haToRA = haRAConv
-- | HA <-> RA Conversions
haRAConv :: DecimalHours -> DecimalDegrees -> JulianDate -> DecimalHours
haRAConv dh longitude ut =
let lst = utToLST longitude ut -- Local Sidereal Time
DH hourAngle = (lstToDH lst) - dh
in if hourAngle < 0 then (DH $ hourAngle+24) else (DH hourAngle)
-- | Convert Equatorial Coordinates to Horizon Coordinates.
-- It takes a latitude of the observer and 'EquatorialCoordinates2'.
-- If you need to convert 'EquatorialCoordinates1'
-- you may use 'raToHa' function to obtain 'EquatorialCoordinates2'
-- or just use function-shortcut 'ec1ToHC' straightaway.
-- The functions returns 'HorizonCoordinates'.
equatorialToHorizon :: DecimalDegrees -> EquatorialCoordinates2 -> HorizonCoordinates
equatorialToHorizon latitude (EC2 dec hourAngle) =
let hourAngle' = fromDecimalHours hourAngle
(altitude, azimuth) = ecHCConv latitude (dec, hourAngle')
in HC altitude azimuth
-- | Convert Horizon Coordinates to Equatorial Coordinates.
-- It takes a latitude of the observer and 'HorizonCoordinates'.
-- The functions returns 'EquatorialCoordinates2'.
-- If you need to obtain 'EquatorialCoordinates1' you may use 'haToRa' function,
-- or function-shortcut `hcToEC1`.
horizonToEquatorial :: DecimalDegrees -> HorizonCoordinates -> EquatorialCoordinates2
horizonToEquatorial latitude (HC altitude azimuth) =
let (dec, hourAngle) = ecHCConv latitude (altitude, azimuth)
in EC2 dec $ toDecimalHours hourAngle
-- | Convert Equatorial Coordinates (Type 1) to Horizon Coordinates.
-- This is function shortcut - tt combines `equatorialToHorizon` and `raToHA`.
-- It takes geographic coordinates of the observer, universal time and equatorial coordinates.
ec1ToHC :: GeographicCoordinates -> JulianDate -> EquatorialCoordinates1 -> HorizonCoordinates
ec1ToHC (GeoC latitude longitude) jd (EC1 delta alpha) =
let ec2 = EC2 delta (raToHA alpha longitude jd)
in equatorialToHorizon latitude ec2
-- | Convert Horizon Coordinates to Equatorial Coordinates (Type 1).
-- This is function shortcut - tt combines `horizonToEquatorial` and `haToRA`.
-- It takes geographic coordinates of the observer, universal time and horizon coordinates.
hcToEC1 :: GeographicCoordinates -> JulianDate -> HorizonCoordinates -> EquatorialCoordinates1
hcToEC1 (GeoC latitude longitude) jd hc =
let (EC2 dec hourAngle) = horizonToEquatorial latitude hc
in EC1 dec (haToRA hourAngle longitude jd)
-- | Function converts Equatorial Coordinates To Horizon Coordinates and vice versa
-- It takes a latitide of the observer as a first parameter and a pair of 'how far up' and 'how far round' coordinates
-- as a second parameter. It returns a pair of 'how far up' and 'how far round' coordinates.
ecHCConv :: DecimalDegrees -> (DecimalDegrees, DecimalDegrees) -> (DecimalDegrees, DecimalDegrees)
ecHCConv latitude (up, round) =
let latitude' = toRadians latitude
up' = toRadians up
round' = toRadians round
sinUpResult = (sin up')*(sin latitude') + (cos up')*(cos latitude')*(cos round')
upResult = asin sinUpResult
roundResult = acos $ ((sin up') - (sin latitude')*sinUpResult) / ((cos latitude') * (cos upResult))
roundResult' = if (sin round') < 0 then roundResult else (2*pi - roundResult)
in ((fromRadians upResult), (fromRadians roundResult'))
-- | Calculate the obliquity of the ecpliptic on JulianDate
obliquity :: JulianDate -> DecimalDegrees
obliquity jd =
let DD baseObliquity = fromDMS 23 26 21.45
t = numberOfCenturies j2000 jd
de = (46.815*t + 0.0006*t*t - 0.00181*t*t*t) / 3600 -- 3600 number of seconds in 1 degree
in (DD $ baseObliquity - de) + (nutationObliquity jd)
-- | Converts Ecliptic Coordinates on specified Julian Date to Equatorial Coordinates
eclipticToEquatorial :: EclipticCoordinates -> JulianDate -> EquatorialCoordinates1
eclipticToEquatorial (EcC beta gamma) jd =
let epsilon' = toRadians $ obliquity jd
beta' = toRadians beta
gamma' = toRadians gamma
delta = asin $ (sin beta')*(cos epsilon') + (cos beta')*(sin epsilon')*(sin gamma')
y = (sin gamma')*(cos epsilon') - (tan beta')*(sin epsilon')
x = cos gamma'
alpha = reduceToZero2PI $ atan2 y x
in EC1 (fromRadians delta) (toDecimalHours $ fromRadians alpha)
-- | Converts Equatorial Coordinates to Ecliptic Coordinates on specified Julian Date
equatorialToEcliptic :: EquatorialCoordinates1 -> JulianDate -> EclipticCoordinates
equatorialToEcliptic (EC1 delta alpha) jd =
let epsilon' = toRadians $ obliquity jd
delta' = toRadians delta
alpha' = toRadians $ fromDecimalHours alpha
beta = asin $ (sin delta')*(cos epsilon') - (cos delta')*(sin epsilon')*(sin alpha')
y = (sin alpha')*(cos epsilon') + (tan delta')*(sin epsilon')
x = cos alpha'
gamma = reduceToZero2PI $ atan2 y x
in EcC (fromRadians beta) (fromRadians gamma)
-- | Galactic Pole Coordinates
galacticPole :: EquatorialCoordinates1
galacticPole = EC1 (DD 27.4) (toDecimalHours $ DD 192.25)
galacticPoleInRadians = (delta, alpha)
where delta = toRadians $ e1Declination galacticPole
alpha = toRadians $ fromDecimalHours $ e1RightAscension galacticPole
-- | Ascending node of the galactic place on equator
ascendingNode :: DecimalDegrees
ascendingNode = DD 33
-- | Convert Galactic Coordinates Equatorial Coordinates
galacticToEquatorial :: GalacticCoordinates -> EquatorialCoordinates1
galacticToEquatorial (GC b l) =
let b' = toRadians b
l' = toRadians l
(poleDelta, poleAlpha) = galacticPoleInRadians
an = toRadians ascendingNode
delta = asin $ (cos b')*(cos poleDelta)*(sin (l'-an)) + (sin b')*(sin poleDelta)
y = (cos b')*(cos (l'-an))
x = (sin b')*(cos poleDelta) - (cos b')*(sin poleDelta)*(sin (l'-an))
alpha = reduceToZero2PI $ (atan2 y x) + poleAlpha
in EC1 (fromRadians delta) (toDecimalHours $ fromRadians alpha)
-- | Convert Equatorial Coordinates to Galactic Coordinates
equatorialToGalactic :: EquatorialCoordinates1 -> GalacticCoordinates
equatorialToGalactic (EC1 delta alpha) =
let delta' = toRadians delta
alpha' = toRadians $ fromDecimalHours alpha
(poleDelta, poleAlpha) = galacticPoleInRadians
sinb = (cos delta')*(cos poleDelta)*(cos (alpha'-poleAlpha)) + (sin delta') * (sin poleDelta)
y = (sin delta') - sinb*(sin poleDelta)
x = (cos delta')*(sin (alpha'-poleAlpha))*(cos poleDelta)
b = asin sinb
l = reduceToZero2PI $ (atan2 y x) + (toRadians ascendingNode)
in GC (fromRadians b) (fromRadians l)
-- | Reduce angle from [-pi, pi] to [0, 2*pi]
-- Usefull to correct results of atan2 for 'how far round' coordinates
reduceToZero2PI :: (Floating a, Ord a) => a -> a
reduceToZero2PI rad = if rad < 0 then rad + 2*pi else rad