From cfa08610cefeda2b67aace8ff5629b2a06466a73 Mon Sep 17 00:00:00 2001 From: Luis Borjas Reyes Date: Mon, 14 Sep 2020 19:36:30 -0400 Subject: [PATCH] More flexible `splitDegrees`, but convenient `splitDegreesZodiac` --- src/SwissEphemeris.hs | 37 +++++++++++++++++--------- src/SwissEphemeris/Internal.hs | 48 ++++++++++++++++++++++++++++------ test/SwissEphemerisSpec.hs | 8 +++--- 3 files changed, 69 insertions(+), 24 deletions(-) diff --git a/src/SwissEphemeris.hs b/src/SwissEphemeris.hs index 0ea4e7b..fdcd5a6 100644 --- a/src/SwissEphemeris.hs +++ b/src/SwissEphemeris.hs @@ -60,6 +60,7 @@ module SwissEphemeris julianDay, deltaTime, -- utilities for angles: + defaultSplitDegreesOptions, splitDegrees, splitDegreesZodiac, ) @@ -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) diff --git a/src/SwissEphemeris/Internal.hs b/src/SwissEphemeris/Internal.hs index 85f27f6..3d864ad 100644 --- a/src/SwissEphemeris/Internal.hs +++ b/src/SwissEphemeris/Internal.hs @@ -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: -- @@ -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) @@ -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 diff --git a/test/SwissEphemerisSpec.hs b/test/SwissEphemerisSpec.hs index 7a78f60..cf2dec3 100644 --- a/test/SwissEphemerisSpec.hs +++ b/test/SwissEphemerisSpec.hs @@ -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