Skip to content

Commit

Permalink
More flexible splitDegrees, but convenient splitDegreesZodiac
Browse files Browse the repository at this point in the history
  • Loading branch information
lfborjas committed Sep 14, 2020
1 parent 20932b3 commit cfa0861
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 24 deletions.
37 changes: 25 additions & 12 deletions src/SwissEphemeris.hs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ module SwissEphemeris
julianDay,
deltaTime,
-- utilities for angles:
defaultSplitDegreesOptions,
splitDegrees,
splitDegreesZodiac,
)
Expand Down Expand Up @@ -315,27 +316,39 @@ deltaTime jt = do
return $ realToFrac deltaT

-- | Given a longitude, return the degrees it's from its nearest sign,
-- minutes, seconds and seconds fraction.
-- minutes, seconds, with seconds rounded. Convenience alias for `splitDegrees`,
-- when wanting to display e.g. a table in a horoscope.
splitDegreesZodiac :: Double -> LongitudeComponents
splitDegreesZodiac d =
LongitudeComponents (Just $ toEnum z) deg m s sf
where
(z, deg, m, s, sf) = splitDegrees' options d
options = mkSplitDegOptions $ defaultSplitDegOptions ++ [splitZodiacal]
splitDegreesZodiac = splitDegrees [SplitZodiacal, RoundSeconds]

-- | Given a longitude, return the degrees from zero, minutes, seconds and seconds fraction.
splitDegrees :: Double -> LongitudeComponents
splitDegrees d =
LongitudeComponents Nothing deg m s sf
-- | Given a `Double` representing an ecliptic longitude, split it according to any
-- options from `SplitDegreesOption`:
-- if `SplitZodiacal` or `SplitNakshatra` are specified, they're returned
-- in `longitudeZodiacSign` and `longitudeNakshatra`, respectively.
-- If neither of those is specified, the raw `signum` is then populated, in
-- `longitudeSignum` (-1 for negative, 1, for positive.)
-- /NOTE:/ this function can also be used for latitudes, speeds or quantities
-- from other positional systems (like declinations,) but the zodiacal or
-- nakshatra components would of course be nonsensical.
splitDegrees :: [SplitDegreesOption] -> Double -> LongitudeComponents
splitDegrees options d =
LongitudeComponents sign deg m s sf signum' nak
where
(_, deg, m, s, sf) = splitDegrees' options d
options = mkSplitDegOptions $ defaultSplitDegOptions
(z, deg, m, s, sf) = splitDegrees' flags d
flags = foldSplitDegOptions $ map splitOptionToFlag options
isZodiacSplit = SplitZodiacal `elem` options
isNakshatraSplit = SplitNakshatra `elem` options
sign = if isZodiacSplit then (Just . toEnum $ z) else Nothing
nak = if isNakshatraSplit then (Just . toEnum $ z) else Nothing
signum' = if (not isZodiacSplit && not isNakshatraSplit) then Just z else Nothing

-- | Internal implementation to split a given longitude into components.
splitDegrees' :: SplitDegFlag -> Double -> (Int, Integer, Integer, Integer, Double)
splitDegrees' options deg =
unsafePerformIO $ do
alloca $ \ideg -> alloca $ \imin -> alloca $ \isec -> alloca $ \dsecfr -> alloca $ \isign -> do
-- initialize with 0, since it may never be touched.
poke dsecfr 0
_ <-
c_swe_split_deg
(realToFrac deg)
Expand Down
48 changes: 40 additions & 8 deletions src/SwissEphemeris/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,30 @@ data ZodiacSignName
| Pisces
deriving (Eq, Show, Enum, Generic)

-- | Nakshatras, provided for thoroughness, please excuse the misspellings!
-- List from: https://en.wikipedia.org/wiki/List_of_Nakshatras
data NakshatraName
= Ashvini
deriving (Eq, Show, Enum, Generic)

-- | Options to split a `Double` representing degrees:
-- RoundSeconds -- round at the seconds granularity (omits seconds fraction.)
-- RoundMinutes -- round at the minutes granularity.
-- RoundDegrees -- round at the degrees granularity.
-- SplitZodiacal -- relative to zodiac signs.
-- SplitNakshatra -- relative to nakshatra.
-- KeepSign -- when rounding, don't round if it'll move it to the next zodiac sector.
-- KeepDegrees -- when rounding, don't round if it'll move it to the next degree.
data SplitDegreesOption
= RoundSeconds
| RoundMinutes
| RoundDegrees
| SplitZodiacal
| SplitNakshatra
| KeepSign
| KeepDegrees
deriving (Eq, Show, Enum, Generic)

-- | Represents an instant in Julian time.
-- see:
-- <https://www.astro.com/swisseph/swephprg.htm#_Toc49847871 8. Date and time conversion functions>
Expand Down Expand Up @@ -168,7 +192,9 @@ data LongitudeComponents = LongitudeComponents
longitudeDegrees :: Integer,
longitudeMinutes :: Integer,
longitudeSeconds :: Integer,
longitudeSecondsFraction :: Double
longitudeSecondsFraction :: Double,
longitudeSignum :: Maybe Int,
longitudeNakshatra :: Maybe NakshatraName
}
deriving (Show, Eq, Generic)

Expand All @@ -180,14 +206,20 @@ mkCalculationOptions = CalcFlag . foldr ((.|.) . unCalcFlag) 0
defaultCalculationOptions :: [CalcFlag]
defaultCalculationOptions = [speed, swissEph]

mkSplitDegOptions :: [SplitDegFlag] -> SplitDegFlag
mkSplitDegOptions = SplitDegFlag . foldr ((.|.) . unSplitDegFlag) 0
foldSplitDegOptions :: [SplitDegFlag] -> SplitDegFlag
foldSplitDegOptions = SplitDegFlag . foldr ((.|.) . unSplitDegFlag) 0

splitOptionToFlag :: SplitDegreesOption -> SplitDegFlag
splitOptionToFlag RoundSeconds = splitRoundSec
splitOptionToFlag RoundMinutes = splitRoundMin
splitOptionToFlag RoundDegrees = splitRoundDeg
splitOptionToFlag SplitZodiacal = splitZodiacal
splitOptionToFlag SplitNakshatra = splitNakshatra
splitOptionToFlag KeepSign = splitKeepSign
splitOptionToFlag KeepDegrees = splitKeepDeg

defaultSplitDegOptions :: [SplitDegFlag]
defaultSplitDegOptions =
[ splitKeepDeg, -- don't round up to the next degree
splitKeepSign -- don't round up to the next sign
]
defaultSplitDegreesOptions :: [SplitDegreesOption]
defaultSplitDegreesOptions = [KeepSign, KeepDegrees]

-- Helpers

Expand Down
8 changes: 4 additions & 4 deletions test/SwissEphemerisSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ spec = do
describe "splitDegrees" $ do
it "splits a given longitude into its components, not relative to the zodiac" $ do
let longitude = 285.64723120365153
split = LongitudeComponents {longitudeZodiacSign = Nothing, longitudeDegrees = 285, longitudeMinutes = 38, longitudeSeconds = 50, longitudeSecondsFraction = 3.233314550481481e-2}
(splitDegrees longitude) `shouldBe` split
split = LongitudeComponents {longitudeZodiacSign = Nothing, longitudeDegrees = 285, longitudeMinutes = 38, longitudeSeconds = 50, longitudeSecondsFraction = 3.233314550481481e-2, longitudeSignum = Just 1, longitudeNakshatra = Nothing}
(splitDegrees defaultSplitDegreesOptions longitude) `shouldBe` split

describe "splitDegreesZodiac" $ do
it "splits a given longitude into its components, relative to the nearest zodiac sign" $ do
it "splits a given longitude into its components, relative to the nearest zodiac sign; rounds seconds, keeps degrees and sign." $ do
let longitude = 285.64723120365153
split = LongitudeComponents {longitudeZodiacSign = Just Capricorn, longitudeDegrees = 15, longitudeMinutes = 38, longitudeSeconds = 50, longitudeSecondsFraction = 3.233314550481481e-2}
split = LongitudeComponents {longitudeZodiacSign = Just Capricorn, longitudeDegrees = 15, longitudeMinutes = 38, longitudeSeconds = 50, longitudeSecondsFraction = 0.0, longitudeSignum = Nothing, longitudeNakshatra = Nothing}
(splitDegreesZodiac longitude) `shouldBe` split

around_ (withoutEphemerides) $ do
Expand Down

0 comments on commit cfa0861

Please sign in to comment.