From 0a0d17aff07d2f7c878940eb323974f1b5861f98 Mon Sep 17 00:00:00 2001 From: Marek Hewelt <22530856+lightflicker@users.noreply.github.com> Date: Mon, 8 Jul 2019 23:02:39 +0100 Subject: [PATCH 01/14] Added new lists for modern astrology * Added LIST_OBJECTS as a common source for assignement of the * Added list with ten planest for modern astrology - LIST_TEN_PLANETS * Added list of aspecing planets - LIST_ASP_PLANTES * Added list with harmonious aspects - LIST_ASPECTS_POS * Added list with hard aspects - LIST_ASPECTS_NEG * Added list with tight orbs - LIST_ORBS_TIGHT * Added list with wide orbs - LIST_WIDE_ORBS * Defined a common orbs list LIST_IRBS for asignement * Added constant MINUTE for Julian calendar claculations * Added constant HOUR for Julian calendar calculations --- flatlib/CHANGELOG.md | 14 ++++++++ flatlib/const.py | 86 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 flatlib/CHANGELOG.md diff --git a/flatlib/CHANGELOG.md b/flatlib/CHANGELOG.md new file mode 100644 index 0000000..3dc0bd2 --- /dev/null +++ b/flatlib/CHANGELOG.md @@ -0,0 +1,14 @@ +#Changelog +--- +## const.py + * Added LIST_OBJECTS as a common source for assignement of the + default Object list + * Added list with ten planest for modern astrology - LIST_TEN_PLANETS + * Added list of aspecing planets - LIST_ASP_PLANTES + * Added list with harmonious aspects - LIST_ASPECTS_POS + * Added list with hard aspects - LIST_ASPECTS_NEG + * Added list with tight orbs - LIST_ORBS_TIGHT + * Added list with wide orbs - LIST_WIDE_ORBS + * Defined a common orbs list LIST_IRBS for asignement + * Added constant MINUTE for Julian calendar claculations + * Added constant HOUR for Julian calendar calculations diff --git a/flatlib/const.py b/flatlib/const.py index bd96a75..ddb72bf 100644 --- a/flatlib/const.py +++ b/flatlib/const.py @@ -163,7 +163,7 @@ HOUSES_POLICH_PAGE = 'Polich Page' HOUSES_ALCABITUS = 'Alcabitus' HOUSES_MORINUS = 'Morinus' -HOUSES_DEFAULT = HOUSES_ALCABITUS +HOUSES_DEFAULT = HOUSES_PLACIDUS # === Angles === */ @@ -275,6 +275,19 @@ LIST_SEVEN_PLANETS = [ SUN, MOON, MERCURY, VENUS, MARS, JUPITER, SATURN ] +"""MH on 2018/3/3 - List of 10 plantes for modern astrology""" +LIST_TEN_PLANETS = [ + SUN, MOON, MERCURY, VENUS, MARS, JUPITER, SATURN, NEPTUNE, URANUS, PLUTO +] + +"""MH on 2018/3/4 - List of aspecting planets""" + +#LIST_OBJECTS = LIST_TEN_PLANETS + +LIST_ASP_PLANETS = [ + SUN, MERCURY, VENUS, MARS, JUPITER, SATURN, NEPTUNE, URANUS, PLUTO, NORTH_NODE, SOUTH_NODE +] + LIST_HOUSES = [ HOUSE1, HOUSE2, HOUSE3, HOUSE4, HOUSE5, HOUSE6, @@ -297,3 +310,74 @@ STAR_LESATH, STAR_VEGA, STAR_ALTAIR, STAR_DENEB_ALGEDI, STAR_FOMALHAUT, STAR_DENEB_ADIGE, STAR_ACHERNAR, ] + +"""MH on 2018/3/6 - List of Positive Aspects""" +LIST_ASPECTS_POS = [ + SEXTILE, TRINE +] + +"""MH on 2018/3/6 - List of Negative Aspects""" +LIST_ASPECTS_NEG = [ + SQUARE, OPPOSITION +] + +"""MH on 2018/3/6 - List of tight orbs""" + +LIST_ORBS_TIGHT = { + NO_PLANET: 0, + SUN: 15, + MOON: 12, + MERCURY: 7, + VENUS: 7, + MARS: 8, + JUPITER: 9, + SATURN: 9, + URANUS: 5, + NEPTUNE: 5, + PLUTO: 5, + CHIRON: 5, + NORTH_NODE: 12, + SOUTH_NODE: 12, + SYZYGY: 0, + PARS_FORTUNA: 0 +} + +"""MH on 2018/3/6 - List of wide orbs""" +LIST_ORBS_WIDE = { + NO_PLANET: 0, + SUN: 5, + MOON: 4, + MERCURY: 2, + VENUS: 2, + MARS: 3, + JUPITER: 3, + SATURN: 3, + URANUS: 2, + NEPTUNE: 1, + PLUTO: 3, + CHIRON: 1, + NORTH_NODE: 2, + SOUTH_NODE: 2, + SYZYGY: 0, + PARS_FORTUNA: 0 +} + +LIST_ORBS = LIST_ORBS_TIGHT + +"""MH on 2018/3/18""" +LIST_RULERS = {ARIES:MARS, + TAURUS:VENUS, + GEMINI:MERCURY, + CANCER:MOON, + LEO:SUN, + VIRGO:MERCURY, + LIBRA:VENUS, + SCORPIO:PLUTO, + SAGITTARIUS:JUPITER, + CAPRICORN:SATURN, + AQUARIUS:URANUS, + PISCES:NEPTUNE} + +"""MH on 2018/3/9 - Time constants""" +MINUTE = 0.00069444440305233 +HOUR = 0.04166666651144624 From ee0f9a1594c1f6d6c82d1374f69eda721802f04c Mon Sep 17 00:00:00 2001 From: Marek Hewelt <22530856+lightflicker@users.noreply.github.com> Date: Mon, 8 Jul 2019 23:14:17 +0100 Subject: [PATCH 02/14] Change default house system to Placidus * Changed Traditional list of objects with the general list LIST_OBJECTS so the asignement can happen in the *const.py* * Removed assignement of a default house system @ 166 * Changed default house system to Placidus --- flatlib/CHANGELOG.md | 12 ++++++++++-- flatlib/chart.py | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/flatlib/CHANGELOG.md b/flatlib/CHANGELOG.md index 3dc0bd2..273dc60 100644 --- a/flatlib/CHANGELOG.md +++ b/flatlib/CHANGELOG.md @@ -1,5 +1,5 @@ -#Changelog ---- +# Changelog +-- ## const.py * Added LIST_OBJECTS as a common source for assignement of the default Object list @@ -12,3 +12,11 @@ * Defined a common orbs list LIST_IRBS for asignement * Added constant MINUTE for Julian calendar claculations * Added constant HOUR for Julian calendar calculations + +__ +## chart.py + * Changed Traditional list of objects with the general list LIST_OBJECTS + so the asignement can happen in the *const.py* + * Removed assignement of a default house system @ 166 + * Changed default house system to Placidus + diff --git a/flatlib/chart.py b/flatlib/chart.py index 098b874..4686ac5 100644 --- a/flatlib/chart.py +++ b/flatlib/chart.py @@ -45,7 +45,7 @@ def __init__(self, date, pos, **kwargs): """ # Handle optional arguments hsys = kwargs.get('hsys', const.HOUSES_DEFAULT) - IDs = kwargs.get('IDs', const.LIST_OBJECTS_TRADITIONAL) + IDs = kwargs.get('IDs', const.LIST_OBJECTS) self.date = date self.pos = pos @@ -166,4 +166,4 @@ def solarReturn(self, year): '00:00', self.date.utcoffset) srDate = ephem.nextSolarReturn(date, sun.lon) - return Chart(srDate, self.pos, hsys=self.hsys) + return Chart(srDate, self.pos) From 0890c141b11a4e70a4300ae78205cdcefa50ea89 Mon Sep 17 00:00:00 2001 From: Marek Hewelt <22530856+lightflicker@users.noreply.github.com> Date: Mon, 8 Jul 2019 23:27:38 +0100 Subject: [PATCH 03/14] Changed the traditional house offset from -5deg to 0deg --- flatlib/CHANGELOG.md | 7 +++++-- flatlib/object.py | 7 ++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/flatlib/CHANGELOG.md b/flatlib/CHANGELOG.md index 273dc60..f74e0f9 100644 --- a/flatlib/CHANGELOG.md +++ b/flatlib/CHANGELOG.md @@ -1,5 +1,5 @@ # Changelog --- +====== ## const.py * Added LIST_OBJECTS as a common source for assignement of the default Object list @@ -13,10 +13,13 @@ * Added constant MINUTE for Julian calendar claculations * Added constant HOUR for Julian calendar calculations -__ +------ ## chart.py * Changed Traditional list of objects with the general list LIST_OBJECTS so the asignement can happen in the *const.py* * Removed assignement of a default house system @ 166 * Changed default house system to Placidus +------ +## object.py + * Changed the traditional house offset from -5deg to 0deg diff --git a/flatlib/object.py b/flatlib/object.py index bd5fc70..d97adad 100644 --- a/flatlib/object.py +++ b/flatlib/object.py @@ -177,9 +177,10 @@ def isFast(self): class House(GenericObject): """ This class represents a generic house cusp. """ - + #MH on 2018/04/17 - Removes the tradditional offset of -5deg + _OFFSET = 0.0 # The traditional house offset - _OFFSET = -5.0 + #_OFFSET = -5.0 def __init__(self): super().__init__() @@ -265,4 +266,4 @@ def aspects(self, obj): """ dist = angle.closestdistance(self.lon, obj.lon) - return abs(dist) < self.orb() \ No newline at end of file + return abs(dist) < self.orb() From 270e460933b29525b2c6adb86a4b2460c638f79f Mon Sep 17 00:00:00 2001 From: Marek Hewelt <22530856+lightflicker@users.noreply.github.com> Date: Mon, 8 Jul 2019 23:35:22 +0100 Subject: [PATCH 04/14] Feeds orbs from the const.py rather than being hardcoded --- flatlib/CHANGELOG.md | 8 ++++++-- flatlib/props.py | 32 ++++++++++++++++---------------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/flatlib/CHANGELOG.md b/flatlib/CHANGELOG.md index f74e0f9..1a1def2 100644 --- a/flatlib/CHANGELOG.md +++ b/flatlib/CHANGELOG.md @@ -13,13 +13,17 @@ * Added constant MINUTE for Julian calendar claculations * Added constant HOUR for Julian calendar calculations ------- +====== ## chart.py * Changed Traditional list of objects with the general list LIST_OBJECTS so the asignement can happen in the *const.py* * Removed assignement of a default house system @ 166 * Changed default house system to Placidus ------- +====== ## object.py * Changed the traditional house offset from -5deg to 0deg + +====== +## props.py + * Feeds orbs from the const.py rather than being hardcoded diff --git a/flatlib/props.py b/flatlib/props.py index c9b247e..84b565e 100644 --- a/flatlib/props.py +++ b/flatlib/props.py @@ -201,22 +201,22 @@ class object: # Object orbs orb = { - const.NO_PLANET: 0, - const.SUN: 15, - const.MOON: 12, - const.MERCURY: 7, - const.VENUS: 7, - const.MARS: 8, - const.JUPITER: 9, - const.SATURN: 9, - const.URANUS: 5, - const.NEPTUNE: 5, - const.PLUTO: 5, - const.CHIRON: 5, - const.NORTH_NODE: 12, - const.SOUTH_NODE: 12, - const.SYZYGY: 0, - const.PARS_FORTUNA: 0 + const.NO_PLANET: const.LIST_ORBS[const.NO_PLANET], + const.SUN: const.LIST_ORBS[const.SUN], + const.MOON: const.LIST_ORBS[const.MOON], + const.MERCURY: const.LIST_ORBS[const.MERCURY], + const.VENUS: const.LIST_ORBS[const.VENUS], + const.MARS: const.LIST_ORBS[const.MARS], + const.JUPITER: const.LIST_ORBS[const.JUPITER], + const.SATURN: const.LIST_ORBS[const.SATURN], + const.URANUS: const.LIST_ORBS[const.URANUS], + const.NEPTUNE: const.LIST_ORBS[const.NEPTUNE], + const.PLUTO: const.LIST_ORBS[const.PLUTO], + const.CHIRON: const.LIST_ORBS[const.CHIRON], + const.NORTH_NODE: const.LIST_ORBS[const.NORTH_NODE], + const.SOUTH_NODE: const.LIST_ORBS[const.SOUTH_NODE], + const.SYZYGY: const.LIST_ORBS[const.SYZYGY], + const.PARS_FORTUNA: const.LIST_ORBS[const.PARS_FORTUNA] } # Planet elements From b2f54760c54d197d51031b25860a75591ce2a30a Mon Sep 17 00:00:00 2001 From: Marek Hewelt <22530856+lightflicker@users.noreply.github.com> Date: Mon, 8 Jul 2019 23:39:01 +0100 Subject: [PATCH 05/14] Moved CHANGELOG.md level up --- flatlib/CHANGELOG.md => CHANGELOG.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename flatlib/CHANGELOG.md => CHANGELOG.md (100%) diff --git a/flatlib/CHANGELOG.md b/CHANGELOG.md similarity index 100% rename from flatlib/CHANGELOG.md rename to CHANGELOG.md From e7b9ab36baeed926a084de6c1388421c4ff44555 Mon Sep 17 00:00:00 2001 From: lightflicker <22530856+lightflicker@users.noreply.github.com> Date: Mon, 8 Jul 2019 23:41:14 +0100 Subject: [PATCH 06/14] Modified markdown --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a1def2..9e26b03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # Changelog -====== +--- ## const.py * Added LIST_OBJECTS as a common source for assignement of the default Object list @@ -13,17 +13,17 @@ * Added constant MINUTE for Julian calendar claculations * Added constant HOUR for Julian calendar calculations -====== +--- ## chart.py * Changed Traditional list of objects with the general list LIST_OBJECTS so the asignement can happen in the *const.py* * Removed assignement of a default house system @ 166 * Changed default house system to Placidus -====== +--- ## object.py * Changed the traditional house offset from -5deg to 0deg -====== +--- ## props.py * Feeds orbs from the const.py rather than being hardcoded From f834827b18c6d0c9d5fcd2a4b77cc372c44e6d5d Mon Sep 17 00:00:00 2001 From: Marek Hewelt <22530856+lightflicker@users.noreply.github.com> Date: Tue, 9 Jul 2019 18:27:23 +0100 Subject: [PATCH 07/14] Added moonca.py with next ingress and voc calcs --- flatlib/moonca.py | 97 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 flatlib/moonca.py diff --git a/flatlib/moonca.py b/flatlib/moonca.py new file mode 100644 index 0000000..cb95326 --- /dev/null +++ b/flatlib/moonca.py @@ -0,0 +1,97 @@ +from . import const +from .chart import Chart +from .geopos import GeoPos +from .datetime import Datetime + +"""Calculate a minute in Julian Calendar""" +MINUTE = const.MINUTE +DIST = [0, 2, 3, 4, 6, 8, 9, 10] + +def NextMoonIngress(chart): + """Define the sign angles""" + angles = [30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330, 360] + + # Initate local variables + # The next sign angle from the current moon position + angle = 0.0 + # Distance between the current Moon position and the next sign + dist = 0.0 + # An error between the new Moon position and the sign angle + a_err = 10.0 + # Intiate iteration counter for statistical purposes + i = 0 + + # Load the current Moon position + moon = chart.get(const.MOON) + + # Find the next sign angle + for ang in angles: + if moon.lon < ang: + angle = ang + dist = ang - moon.lon + break + + # Evalute a time in day before the next Moon ingress + # Days to the next ingress + dti = (dist / const.MEAN_MOTION_MOON) + + # Add the DTI into the the current Moon position + tme = Datetime.fromJD(chart.date.jd + dti,0) + + # Evaluate progressed chart + p_chart = Chart(tme,chart.pos) + p_moon = p_chart.get(const.MOON) + a_err = angle - p_moon.lon + # Run the loop for calculating the ingress time + while (abs(a_err) > 0.0001): + p_chart = Chart(tme,chart.pos) + p_moon = p_chart.get(const.MOON) + a_err = angle - p_moon.lon + scale = 100.0 * abs(a_err) + + if a_err > 0.0: + tme = Datetime.fromJD(tme.jd + (MINUTE * scale),0) + if a_err == 0.0: + break + if a_err < 0.0: + tme = Datetime.fromJD(tme.jd - (MINUTE * scale),0) + i += 1 + return {'Date':tme,'Error':a_err,'Iter':i} + + +def isCA(chart): + """Is canceling aspect present?""" + IDate = NextMoonIngress(chart) + p_chart = Chart(IDate['Date'],chart.pos) + moon = chart.get(const.MOON) + obj = 0 + obj_lon = 0 + asp_type = 0 + + for ob in p_chart.objects: + if ob.id in const.LIST_ASP_PLANETS: + dist = abs(const.LIST_SIGNS.index(moon.sign) - const.LIST_SIGNS.index(ob.sign)) + if dist in DIST: + if ob.signlon > obj_lon: + obj_lon = ob.signlon + obj = ob + if dist == 0: + if obj.id in [const.SUN, + const.MERCURY, + const.VENUS, + const.JUPITER, + const.NEPTUNE]: #Conjunction + asp_type = 1 + else: + asp_type = 0 + if dist == 2 or dist == 10: #Sextile + asp_type = 1 + if dist == 3 or dist == 9: #Square + asp_type = 0 + if dist == 4 or dist == 8: #Trine + asp_type = 1 + if dist == 6: #Opposition + asp_type = 0 + + + return {'Last obj':obj.id,'Lon':obj.signlon,'POS':asp_type,'NI':IDate['Date']} From 868079becffdfb007d1a6b96d64a9b4570b2457e Mon Sep 17 00:00:00 2001 From: Marek Hewelt <22530856+lightflicker@users.noreply.github.com> Date: Tue, 9 Jul 2019 23:00:07 +0100 Subject: [PATCH 08/14] Implented NextMoonIngress function, howerver ingress to Aries still needs work (WIP) --- .gitignore | 3 +++ .main.py.swp | Bin 0 -> 12288 bytes flatlib/moonca.py | 67 +++++++++++++++++++++++++++++++--------------- 3 files changed, 48 insertions(+), 22 deletions(-) create mode 100644 .main.py.swp diff --git a/.gitignore b/.gitignore index 9f38193..1a2913a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ venv/ # Build build/ + +# Ignore vim swp files +*.swp diff --git a/.main.py.swp b/.main.py.swp new file mode 100644 index 0000000000000000000000000000000000000000..d088aa4cd352a29477981a4391ad752210fe97b4 GIT binary patch literal 12288 zcmeI2y>8S%6oqf76NrEcnt>>?1H1l9BC;zj5^NCKKp;{A4T_NCo!uC0kL8)wLIJcC z(DM{@@EE)RJs=(-71xfpFhx+xpfl3(jOU(vJ@ZK}M)raCbg(Tp9FMpy68)ZE-v55* zh(65|rH67V6H`V`Y%eC-?DS8xRKd4(L3gT(mpTt4>4w@A$}MEB+))ymBtCG{FwUI( z^}ox(0RkX!Dgq0%8u-<#zI6SXxODN!sj^`P0T2KI5C8!X009sH0T4Lb1WZ0h?|2L6 z>TO)E`? z=o{-Z>pg44dcfLZ-Da(_0@hX571mGA|H1mo`oj9i`oMa_Dr-v?8VG;@2!H?xfB*=9 z00@8p2!O!v})@Ep=wZB4*=@p{dAbKKIDvLtrk$h#gc{s?%9Mg7m z7#4<&RnNA5d2gFkwnVOUOAI!3FH)H{&cng(-t)(Ix9;`#b~%A9Y%iZ{;#AsJ;Q1Zb zyXE>Ft0k-;=&t!)&nw3_J+B;b4p-!y`%>*FZS&9REs>~<=lR;R{3)jr&-Sc!KkKYn ze9SFWD(b)3MV#iUF!eqfA!jUAS-N%{*Kaz8^*U#s*}9vqV+eJbibNwmORjj(l4##( Si%1RQY}^$SGiq;?GxP@!D6>HT literal 0 HcmV?d00001 diff --git a/flatlib/moonca.py b/flatlib/moonca.py index cb95326..99abad5 100644 --- a/flatlib/moonca.py +++ b/flatlib/moonca.py @@ -1,14 +1,26 @@ -from . import const -from .chart import Chart -from .geopos import GeoPos -from .datetime import Datetime +from flatlib import const +from flatlib.chart import Chart +from flatlib.datetime import Datetime -"""Calculate a minute in Julian Calendar""" +""" +Module to calcualate the moon ingress and +voc +""" + +# Assign a minute in Julian Calendar MINUTE = const.MINUTE +# List of house distances DIST = [0, 2, 3, 4, 6, 8, 9, 10] +# Mean Moon motion in deg/s +MEAN_MOTION_MOON_S = const.MEAN_MOTION_MOON / 86400 + def NextMoonIngress(chart): - """Define the sign angles""" + """ + Calculates datetime of the next Moon ingress + """ + #TODO Need solution for calculating ingress to Aries + # Define the sign angles angles = [30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330, 360] # Initate local variables @@ -16,13 +28,12 @@ def NextMoonIngress(chart): angle = 0.0 # Distance between the current Moon position and the next sign dist = 0.0 - # An error between the new Moon position and the sign angle - a_err = 10.0 # Intiate iteration counter for statistical purposes i = 0 # Load the current Moon position moon = chart.get(const.MOON) + print(moon.signlon) # Find the next sign angle for ang in angles: @@ -31,32 +42,44 @@ def NextMoonIngress(chart): dist = ang - moon.lon break - # Evalute a time in day before the next Moon ingress - # Days to the next ingress + # Evalute a mean time in days before the next Moon ingress dti = (dist / const.MEAN_MOTION_MOON) - # Add the DTI into the the current Moon position + # Add the 'dti' into the the current Moon position tme = Datetime.fromJD(chart.date.jd + dti,0) # Evaluate progressed chart - p_chart = Chart(tme,chart.pos) + p_chart = Chart(tme, chart.pos) + # Get Moon object p_moon = p_chart.get(const.MOON) - a_err = angle - p_moon.lon + + # Calculate differential between the progressed + # Moon position and the angle of the next sign + # plus 1 secound in mean angle for the ingress + # time to always occur in the new sign + a_diff = angle + MEAN_MOTION_MOON_S * 0.01 - p_moon.lon + # Run the loop for calculating the ingress time - while (abs(a_err) > 0.0001): + while True: + # Recalculate chart for the corrected time p_chart = Chart(tme,chart.pos) + # Get Moon object p_moon = p_chart.get(const.MOON) - a_err = angle - p_moon.lon - scale = 100.0 * abs(a_err) + # Calculate position differential + a_diff = angle + MEAN_MOTION_MOON_S * 0.01 - p_moon.lon + + # Calculate time increment factor based + # on the value of the differential + inc_factor = 100.0 * abs(a_diff) - if a_err > 0.0: - tme = Datetime.fromJD(tme.jd + (MINUTE * scale),0) - if a_err == 0.0: + if p_moon.lon > (angle + MEAN_MOTION_MOON_S * 0.1): + tme = Datetime.fromJD(tme.jd - (MINUTE * inc_factor),0) + if p_moon.lon < angle: + tme = Datetime.fromJD(tme.jd + (MINUTE * inc_factor),0) + if p_moon.lon > angle and p_moon.lon < (angle + MEAN_MOTION_MOON_S * 0.1): break - if a_err < 0.0: - tme = Datetime.fromJD(tme.jd - (MINUTE * scale),0) i += 1 - return {'Date':tme,'Error':a_err,'Iter':i} + return {'Date':tme, 'Error':a_diff, 'Iter':i, 'Lon':p_moon.signlon, 'Sign':p_moon.sign} def isCA(chart): From bfafd0ab115ac5c3e521b799c5a876fd39eb2c0b Mon Sep 17 00:00:00 2001 From: Marek Hewelt <22530856+lightflicker@users.noreply.github.com> Date: Wed, 10 Jul 2019 22:40:13 +0100 Subject: [PATCH 09/14] Next Moon Ingress implementaton completed --- flatlib/moonca.py | 46 +++++++++++++++++++++------------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/flatlib/moonca.py b/flatlib/moonca.py index 99abad5..d28b4ba 100644 --- a/flatlib/moonca.py +++ b/flatlib/moonca.py @@ -3,7 +3,7 @@ from flatlib.datetime import Datetime """ -Module to calcualate the moon ingress and +Module to calcualate the moon next ingress and voc """ @@ -15,7 +15,7 @@ # Mean Moon motion in deg/s MEAN_MOTION_MOON_S = const.MEAN_MOTION_MOON / 86400 -def NextMoonIngress(chart): +def next_moon_ingress(chart): """ Calculates datetime of the next Moon ingress """ @@ -32,14 +32,13 @@ def NextMoonIngress(chart): i = 0 # Load the current Moon position - moon = chart.get(const.MOON) - print(moon.signlon) + p_moon = chart.get(const.MOON) # Find the next sign angle for ang in angles: - if moon.lon < ang: + if p_moon.lon < ang: angle = ang - dist = ang - moon.lon + dist = ang - p_moon.lon break # Evalute a mean time in days before the next Moon ingress @@ -48,42 +47,39 @@ def NextMoonIngress(chart): # Add the 'dti' into the the current Moon position tme = Datetime.fromJD(chart.date.jd + dti,0) - # Evaluate progressed chart - p_chart = Chart(tme, chart.pos) - # Get Moon object - p_moon = p_chart.get(const.MOON) - - # Calculate differential between the progressed - # Moon position and the angle of the next sign - # plus 1 secound in mean angle for the ingress - # time to always occur in the new sign - a_diff = angle + MEAN_MOTION_MOON_S * 0.01 - p_moon.lon - # Run the loop for calculating the ingress time while True: # Recalculate chart for the corrected time p_chart = Chart(tme,chart.pos) # Get Moon object p_moon = p_chart.get(const.MOON) - # Calculate position differential - a_diff = angle + MEAN_MOTION_MOON_S * 0.01 - p_moon.lon + # Correct value of the moon's longtitude if ingress + # to Aries + if angle == 360 and p_moon.sign == const.ARIES: + p_moon_lon = p_moon.lon + 360.0 + else: + p_moon_lon = p_moon.lon + # Calculate position differential + a_diff = angle - p_moon_lon # Calculate time increment factor based # on the value of the differential inc_factor = 100.0 * abs(a_diff) - if p_moon.lon > (angle + MEAN_MOTION_MOON_S * 0.1): + if p_moon_lon > (angle + MEAN_MOTION_MOON_S * 0.01): tme = Datetime.fromJD(tme.jd - (MINUTE * inc_factor),0) - if p_moon.lon < angle: + if p_moon_lon < angle: tme = Datetime.fromJD(tme.jd + (MINUTE * inc_factor),0) - if p_moon.lon > angle and p_moon.lon < (angle + MEAN_MOTION_MOON_S * 0.1): + if p_moon_lon > angle and p_moon_lon < (angle + MEAN_MOTION_MOON_S * 0.01): break i += 1 - return {'Date':tme, 'Error':a_diff, 'Iter':i, 'Lon':p_moon.signlon, 'Sign':p_moon.sign} + return {'Date':tme, 'Sign':p_moon.sign} -def isCA(chart): - """Is canceling aspect present?""" +def next_ca(chart): + """ + Is canceling aspect present? + """ IDate = NextMoonIngress(chart) p_chart = Chart(IDate['Date'],chart.pos) moon = chart.get(const.MOON) From baaeaefe301da4d43f7d76538597312da722ee5e Mon Sep 17 00:00:00 2001 From: Marek Hewelt <22530856+lightflicker@users.noreply.github.com> Date: Thu, 11 Jul 2019 23:12:12 +0100 Subject: [PATCH 10/14] Integrated merge of moonca.py --- flatlib/__init__.py | 2 +- flatlib/angle.py | 2 +- flatlib/ephem/eph.py | 9 +--- flatlib/ephem/ephem.py | 50 +----------------- flatlib/ephem/swe.py | 31 ----------- flatlib/ephem/tools.py | 20 +------ flatlib/predictives/primarydirections.py | 29 +++++----- flatlib/tools/chartdynamics.py | 67 ++++++++++++++++++------ 8 files changed, 71 insertions(+), 139 deletions(-) diff --git a/flatlib/__init__.py b/flatlib/__init__.py index 80784ad..7334d1a 100644 --- a/flatlib/__init__.py +++ b/flatlib/__init__.py @@ -7,7 +7,7 @@ import os -__version__ = '0.2.2-dev' +__version__ = '0.2.1' # Library and resource paths PATH_LIB = os.path.dirname(__file__) + os.sep diff --git a/flatlib/angle.py b/flatlib/angle.py index a3f7847..c732f33 100644 --- a/flatlib/angle.py +++ b/flatlib/angle.py @@ -58,7 +58,7 @@ def _fixSlist(slist): def _roundSlist(slist): """ Rounds a signed list over the last element and removes it. """ slist[-1] = 60 if slist[-1] >= 30 else 0 - for i in range(len(slist)-1, 1, -1): + for i in range(len(slist)-1, 0, -1): if slist[i] == 60: slist[i] = 0 slist[i-1] += 1 diff --git a/flatlib/ephem/eph.py b/flatlib/ephem/eph.py index 4a607b7..39899fe 100644 --- a/flatlib/ephem/eph.py +++ b/flatlib/ephem/eph.py @@ -102,13 +102,6 @@ def lastSunset(jd, lat, lon): return nextSunset(jd - 1.0, lat, lon) -# === Stations === # - -def nextStation(ID, jd): - """ Returns the aproximate jd of the next station. """ - return tools.nextStationJD(ID, jd) - - # === Other functions === # def _signInfo(obj): @@ -117,4 +110,4 @@ def _signInfo(obj): obj.update({ 'sign': const.LIST_SIGNS[int(lon / 30)], 'signlon': lon % 30 - }) + }) \ No newline at end of file diff --git a/flatlib/ephem/ephem.py b/flatlib/ephem/ephem.py index e54645a..2f8c293 100644 --- a/flatlib/ephem/ephem.py +++ b/flatlib/ephem/ephem.py @@ -14,7 +14,6 @@ """ from . import eph -from . import swe from flatlib.datetime import Datetime from flatlib.object import (GenericObject, Object, @@ -105,51 +104,4 @@ def lastSunrise(date, pos): def lastSunset(date, pos): """ Returns the date of the last sunset. """ jd = eph.lastSunset(date.jd, pos.lat, pos.lon) - return Datetime.fromJD(jd, date.utcoffset) - - -# === Station === # - -def nextStation(ID, date): - """ Returns the aproximate date of the next station. """ - jd = eph.nextStation(ID, date.jd) - return Datetime.fromJD(jd, date.utcoffset) - - -# === Eclipses === # - -def prevSolarEclipse(date): - """ Returns the Datetime of the maximum phase of the - previous global solar eclipse. - - """ - - eclipse = swe.solarEclipseGlobal(date.jd, backward=True) - return Datetime.fromJD(eclipse['maximum'], date.utcoffset) - -def nextSolarEclipse(date): - """ Returns the Datetime of the maximum phase of the - next global solar eclipse. - - """ - - eclipse = swe.solarEclipseGlobal(date.jd, backward=False) - return Datetime.fromJD(eclipse['maximum'], date.utcoffset) - -def prevLunarEclipse(date): - """ Returns the Datetime of the maximum phase of the - previous global lunar eclipse. - - """ - - eclipse = swe.lunarEclipseGlobal(date.jd, backward=True) - return Datetime.fromJD(eclipse['maximum'], date.utcoffset) - -def nextLunarEclipse(date): - """ Returns the Datetime of the maximum phase of the - next global lunar eclipse. - - """ - - eclipse = swe.lunarEclipseGlobal(date.jd, backward=False) - return Datetime.fromJD(eclipse['maximum'], date.utcoffset) + return Datetime.fromJD(jd, date.utcoffset) \ No newline at end of file diff --git a/flatlib/ephem/swe.py b/flatlib/ephem/swe.py index 5e8b354..ea84920 100644 --- a/flatlib/ephem/swe.py +++ b/flatlib/ephem/swe.py @@ -142,34 +142,3 @@ def sweFixedStar(star, jd): 'lon': sweList[0], 'lat': sweList[1] } - - -# === Eclipses === # - -def solarEclipseGlobal(jd, backward): - """ Returns the jd details of previous or next global solar eclipse. """ - - sweList = swisseph.sol_eclipse_when_glob(jd, backward=backward) - return { - 'maximum': sweList[1][0], - 'begin': sweList[1][2], - 'end': sweList[1][3], - 'totality_begin': sweList[1][4], - 'totality_end': sweList[1][5], - 'center_line_begin': sweList[1][6], - 'center_line_end': sweList[1][7], - } - -def lunarEclipseGlobal(jd, backward): - """ Returns the jd details of previous or next global lunar eclipse. """ - - sweList = swisseph.lun_eclipse_when(jd, backward=backward) - return { - 'maximum': sweList[1][0], - 'partial_begin': sweList[1][2], - 'partial_end': sweList[1][3], - 'totality_begin': sweList[1][4], - 'totality_end': sweList[1][5], - 'penumbral_begin': sweList[1][6], - 'penumbral_end': sweList[1][7], - } diff --git a/flatlib/ephem/tools.py b/flatlib/ephem/tools.py index 1c8c59c..a207326 100644 --- a/flatlib/ephem/tools.py +++ b/flatlib/ephem/tools.py @@ -82,26 +82,10 @@ def solarReturnJD(jd, lon, forward=True): if forward: dist = angle.distance(sun, lon) else: - dist = -angle.distance(lon, sun) + dist = angle.distance(lon, sun) while abs(dist) > MAX_ERROR: jd = jd + dist / 0.9833 # Sun mean motion sun = swe.sweObjectLon(const.SUN, jd) dist = angle.closestdistance(sun, lon) - return jd - - -# === Other algorithms === # - -def nextStationJD(ID, jd): - """ Finds the aproximate julian date of the - next station of a planet. - - """ - speed = swe.sweObject(ID, jd)['lonspeed'] - for i in range(2000): - nextjd = jd + i / 2 - nextspeed = swe.sweObject(ID, nextjd)['lonspeed'] - if speed * nextspeed <= 0: - return nextjd - return None + return jd \ No newline at end of file diff --git a/flatlib/predictives/primarydirections.py b/flatlib/predictives/primarydirections.py index 9946212..6bccbd1 100644 --- a/flatlib/predictives/primarydirections.py +++ b/flatlib/predictives/primarydirections.py @@ -5,12 +5,6 @@ This module implements the Primary Directions method. - - Default assumptions: - - only directions with the primary motion (direct) - - only semi-arc method - - in-zodiaco aspects of promissors to significators - - in-mundo directions uses latitude of both promissors and significators """ @@ -269,10 +263,13 @@ def getList(self, aspList): # Promissors objects = self._elements(self.SIG_OBJECTS, self.N, aspList) + houses = self._elements(self.SIG_HOUSES, self.N, [0]) + angles = self._elements(self.SIG_ANGLES, self.N, [0]) terms = self._terms() antiscias = self._elements(self.SIG_OBJECTS, self.A, [0]) cantiscias = self._elements(self.SIG_OBJECTS, self.C, [0]) - promissors = objects + terms + antiscias + cantiscias + promissors = objects + houses + angles + terms + \ + antiscias + cantiscias # Compute all res = [] @@ -292,8 +289,8 @@ def getList(self, aspList): ]) return sorted(res) - - + + # ------------------ # # PD Table Class # # ------------------ # @@ -301,24 +298,24 @@ def getList(self, aspList): class PDTable: """ Represents the Primary Directions table for a chart. - + """ - + def __init__(self, chart, aspList=const.MAJOR_ASPECTS): pd = PrimaryDirections(chart) self.table = pd.getList(aspList) - + def view(self, arcmin, arcmax): """ Returns the directions within the min and max arcs. - + """ res = [] for direction in self.table: if arcmin < direction[0] < arcmax: res.append(direction) return res - + def bySignificator(self, ID): """ Returns all directions to a significator. """ res = [] @@ -326,11 +323,11 @@ def bySignificator(self, ID): if ID in direction[2]: res.append(direction) return res - + def byPromissor(self, ID): """ Returns all directions to a promissor. """ res = [] for direction in self.table: if ID in direction[1]: res.append(direction) - return res + return res \ No newline at end of file diff --git a/flatlib/tools/chartdynamics.py b/flatlib/tools/chartdynamics.py index f39611e..dedf0f4 100644 --- a/flatlib/tools/chartdynamics.py +++ b/flatlib/tools/chartdynamics.py @@ -74,7 +74,9 @@ def validAspects(self, ID, aspList): obj = self.chart.getObject(ID) res = [] - for otherID in const.LIST_SEVEN_PLANETS: + #for otherID in const.LIST_SEVEN_PLANETS: + for otherID in const.LIST_TEN_PLANETS: + if ID == otherID: continue @@ -116,25 +118,20 @@ def aspectsByCat(self, ID, aspList): }) return res - + def immediateAspects(self, ID, aspList): """ Returns the last separation and next application considering a list of possible aspects. - + """ asps = self.aspectsByCat(ID, aspList) - - applications = asps[const.APPLICATIVE] - separations = asps[const.SEPARATIVE] - exact = asps[const.EXACT] - - # Get applications and separations sorted by orb - - applications = applications + [val for val in exact if val['orb'] >= 0] - - applications = sorted(applications, key=lambda var: var['orb']) - separations = sorted(separations, key=lambda var: var['orb']) - + + # Get application and separations sorted by orb + applications = sorted(asps[const.APPLICATIVE], + key=lambda var: var['orb']) + separations = sorted(asps[const.SEPARATIVE], + key=lambda var: var['orb']) + return ( separations[0] if separations else None, applications[0] if applications else None @@ -150,3 +147,43 @@ def isVOC(self, ID): applications = asps[const.APPLICATIVE] exacts = asps[const.EXACT] return len(applications) == 0 and len(exacts) == 0 + + def isMVOC(self): + """MH on 2018/3/4 - Returns if the Moon is void of course + taking into consideration sign status""" + + """Loading all objects except the Moon""" + obj = [] + for ob in const.LIST_ASP_PLANETS: + obj.append(self.chart.get(ob)) + """Loading the Moon""" + moon = self.chart.get(const.MOON) + + """Check if any other object has a greater lon in the sign + If that is the case, check if any major aspect may exist before + th Moon leaves the sign""" + asp_type = const.NO_ASPECT + for ob in obj: + if ob.signlon >= moon.signlon: #Check if any other object has a greater lon in sign than the Moon + + """Calculate a distance between the signs""" + dist = abs(const.LIST_SIGNS.index(moon.sign) - const.LIST_SIGNS.index(ob.sign)) + #asp_type = const.NO_ASPECT + if dist == 0: + if ob.id in [const.SUN, + const.MERCURY, + const.VENUS, + const.JUPITER, + const.NEPTUNE]: #Conjunction + asp_type = 1 + else: + asp_type = 0 + if dist == 2 or dist == 10: #Sextile + asp_type = 1 + if dist == 3 or dist == 9: #Square + asp_type = 0 + if dist == 4 or dist == 8: #Trine + asp_type = 1 + if dist == 6: #Opposition + asp_type = 0 + return {"isVOC" : asp_type == const.NO_ASPECT,"asp" : asp_type} From 5943b0347f5f501722626844ad5fbd80ce3ca740 Mon Sep 17 00:00:00 2001 From: Marek Hewelt <22530856+lightflicker@users.noreply.github.com> Date: Thu, 11 Jul 2019 23:50:15 +0100 Subject: [PATCH 11/14] Implemented next_moon_ingress with a method of finding value from flatlib --- flatlib/moonca.py | 87 +++++++++++------------------------------------ 1 file changed, 20 insertions(+), 67 deletions(-) diff --git a/flatlib/moonca.py b/flatlib/moonca.py index d28b4ba..7848984 100644 --- a/flatlib/moonca.py +++ b/flatlib/moonca.py @@ -1,6 +1,7 @@ from flatlib import const from flatlib.chart import Chart from flatlib.datetime import Datetime +from flatlib.ephem import swe """ Module to calcualate the moon next ingress and @@ -15,11 +16,13 @@ # Mean Moon motion in deg/s MEAN_MOTION_MOON_S = const.MEAN_MOTION_MOON / 86400 +MAX_ERROR = 0.0003 + def next_moon_ingress(chart): """ Calculates datetime of the next Moon ingress """ - #TODO Need solution for calculating ingress to Aries + # Define the sign angles angles = [30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330, 360] @@ -32,85 +35,35 @@ def next_moon_ingress(chart): i = 0 # Load the current Moon position - p_moon = chart.get(const.MOON) + moon_lon = swe.sweObjectLon(const.MOON, chart.date.jd) # Find the next sign angle for ang in angles: - if p_moon.lon < ang: + if moon_lon < ang: angle = ang - dist = ang - p_moon.lon + dist = ang - moon_lon break # Evalute a mean time in days before the next Moon ingress - dti = (dist / const.MEAN_MOTION_MOON) + jd = dist / const.MEAN_MOTION_MOON - # Add the 'dti' into the the current Moon position - tme = Datetime.fromJD(chart.date.jd + dti,0) + # Add the 'jd' into the the current Moon position + jd = jd + chart.date.jd # Run the loop for calculating the ingress time - while True: - # Recalculate chart for the corrected time - p_chart = Chart(tme,chart.pos) - # Get Moon object - p_moon = p_chart.get(const.MOON) + while abs(dist) > MAX_ERROR: + # Get Moon longtitude + moon_lon = swe.sweObjectLon(const.MOON, jd) # Correct value of the moon's longtitude if ingress # to Aries - if angle == 360 and p_moon.sign == const.ARIES: - p_moon_lon = p_moon.lon + 360.0 - else: - p_moon_lon = p_moon.lon - + if angle == 360 and moon_lon >= 0.0 and moon_lon < 30.0: + moon_lon = moon_lon + 360.0 + + # Calcualte distance + dist = angle - moon_lon # Calculate position differential - a_diff = angle - p_moon_lon - # Calculate time increment factor based - # on the value of the differential - inc_factor = 100.0 * abs(a_diff) + jd = jd + dist / const.MEAN_MOTION_MOON - if p_moon_lon > (angle + MEAN_MOTION_MOON_S * 0.01): - tme = Datetime.fromJD(tme.jd - (MINUTE * inc_factor),0) - if p_moon_lon < angle: - tme = Datetime.fromJD(tme.jd + (MINUTE * inc_factor),0) - if p_moon_lon > angle and p_moon_lon < (angle + MEAN_MOTION_MOON_S * 0.01): - break i += 1 - return {'Date':tme, 'Sign':p_moon.sign} - - -def next_ca(chart): - """ - Is canceling aspect present? - """ - IDate = NextMoonIngress(chart) - p_chart = Chart(IDate['Date'],chart.pos) - moon = chart.get(const.MOON) - obj = 0 - obj_lon = 0 - asp_type = 0 - - for ob in p_chart.objects: - if ob.id in const.LIST_ASP_PLANETS: - dist = abs(const.LIST_SIGNS.index(moon.sign) - const.LIST_SIGNS.index(ob.sign)) - if dist in DIST: - if ob.signlon > obj_lon: - obj_lon = ob.signlon - obj = ob - if dist == 0: - if obj.id in [const.SUN, - const.MERCURY, - const.VENUS, - const.JUPITER, - const.NEPTUNE]: #Conjunction - asp_type = 1 - else: - asp_type = 0 - if dist == 2 or dist == 10: #Sextile - asp_type = 1 - if dist == 3 or dist == 9: #Square - asp_type = 0 - if dist == 4 or dist == 8: #Trine - asp_type = 1 - if dist == 6: #Opposition - asp_type = 0 + return {'Date':Datetime.fromJD(jd, '+00:00'), 'Iter':i} - - return {'Last obj':obj.id,'Lon':obj.signlon,'POS':asp_type,'NI':IDate['Date']} From 673af3e6581b613fa543319f633c725634269cae Mon Sep 17 00:00:00 2001 From: Marek Hewelt <22530856+lightflicker@users.noreply.github.com> Date: Sun, 1 Mar 2026 09:14:54 +0000 Subject: [PATCH 12/14] Converting to JS part 1 --- js/package.json | 21 ++++ js/src/flatlib/angle.js | 96 ++++++++++++++++ js/src/flatlib/const.js | 170 +++++++++++++++++++++++++++ js/src/flatlib/datetime.js | 177 ++++++++++++++++++++++++++++ js/src/flatlib/ephem/eph.js | 83 ++++++++++++++ js/src/flatlib/ephem/ephem.js | 79 +++++++++++++ js/src/flatlib/ephem/index.js | 23 ++++ js/src/flatlib/ephem/swe.js | 149 ++++++++++++++++++++++++ js/src/flatlib/ephem/tools.js | 61 ++++++++++ js/src/flatlib/geopos.js | 58 ++++++++++ js/src/flatlib/lists.js | 15 +++ js/src/flatlib/object.js | 209 ++++++++++++++++++++++++++++++++++ js/src/flatlib/props.js | 16 +++ js/src/flatlib/utils.js | 58 ++++++++++ js/tests/chart.test.js | 22 ++++ js/tests/ephem.test.js | 17 +++ 16 files changed, 1254 insertions(+) create mode 100644 js/package.json create mode 100644 js/src/flatlib/angle.js create mode 100644 js/src/flatlib/const.js create mode 100644 js/src/flatlib/datetime.js create mode 100644 js/src/flatlib/ephem/eph.js create mode 100644 js/src/flatlib/ephem/ephem.js create mode 100644 js/src/flatlib/ephem/index.js create mode 100644 js/src/flatlib/ephem/swe.js create mode 100644 js/src/flatlib/ephem/tools.js create mode 100644 js/src/flatlib/geopos.js create mode 100644 js/src/flatlib/lists.js create mode 100644 js/src/flatlib/object.js create mode 100644 js/src/flatlib/props.js create mode 100644 js/src/flatlib/utils.js create mode 100644 js/tests/chart.test.js create mode 100644 js/tests/ephem.test.js diff --git a/js/package.json b/js/package.json new file mode 100644 index 0000000..aa684b0 --- /dev/null +++ b/js/package.json @@ -0,0 +1,21 @@ +{ + "name": "flatlib-js", + "version": "0.1.0", + "description": "JavaScript port of the Python flatlib astrology library", + "main": "src/index.js", + "type": "module", + "scripts": { + "test": "jest", + "build": "echo \"No build step yet\"" + }, + "keywords": [ + "astrology", + "flatlib", + "python-port" + ], + "author": "Ported by user", + "license": "MIT", + "devDependencies": { + "jest": "^29.0.0" + } +} \ No newline at end of file diff --git a/js/src/flatlib/angle.js b/js/src/flatlib/angle.js new file mode 100644 index 0000000..4f6b91e --- /dev/null +++ b/js/src/flatlib/angle.js @@ -0,0 +1,96 @@ +// Port of flatlib/angle.py to JavaScript +// Contains utilities for angle manipulation and conversions. + +// === Angular utilities === +export function norm(angle) { + return ((angle % 360) + 360) % 360; +} + +export function znorm(angle) { + let a = ((angle % 360) + 360) % 360; + return a <= 180 ? a : a - 360; +} + +export function distance(angle1, angle2) { + return norm(angle2 - angle1); +} + +export function closestdistance(angle1, angle2) { + return znorm(angle2 - angle1); +} + +// === Signed list utilities === +function _fixSlist(slist) { + while (slist.length < 4) slist.push(0); + return slist.slice(0, 4); +} + +function _roundSlist(slist) { + slist[slist.length - 1] = slist[slist.length - 1] >= 30 ? 60 : 0; + for (let i = slist.length - 1; i > 0; i--) { + if (slist[i] === 60) { + slist[i] = 0; + slist[i - 1] += 1; + } + } + return slist.slice(0, slist.length - 1); +} + +// === Base conversions === +export function strSlist(string) { + const sign = string.startsWith('-') ? '-' : '+'; + const values = string + .replace(/^[-+]/, '') + .split(':') + .map((x) => Math.abs(parseInt(x, 10))); + return _fixSlist([sign, ...values]); +} + +export function slistStr(slist) { + slist = _fixSlist(slist); + const string = slist.slice(1).map((x) => String(x).padStart(2, '0')).join(':'); + return slist[0] + string; +} + +export function slistFloat(slist) { + slist = _fixSlist(slist); + const values = slist.slice(1).map((v, i) => v / 60 ** i); + const value = values.reduce((a, b) => a + b, 0); + return slist[0] === '-' ? -value : value; +} + +export function floatSlist(value) { + const slist = ['+', 0, 0, 0, 0]; + if (value < 0) { + slist[0] = '-'; + } + let v = Math.abs(value); + for (let i = 1; i < 5; i++) { + slist[i] = Math.floor(v); + v = (v - slist[i]) * 60; + } + return _roundSlist(slist); +} + +export function strFloat(string) { + return slistFloat(strSlist(string)); +} + +export function floatStr(value) { + return slistStr(floatSlist(value)); +} + +// === Direct conversions === +export function toFloat(value) { + if (typeof value === 'string') return strFloat(value); + if (Array.isArray(value)) return slistFloat(value); + return value; +} + +export function toList(value) { + return floatSlist(value); +} + +export function toString(value) { + return floatStr(value); +} diff --git a/js/src/flatlib/const.js b/js/src/flatlib/const.js new file mode 100644 index 0000000..d38851f --- /dev/null +++ b/js/src/flatlib/const.js @@ -0,0 +1,170 @@ +// flatlib constants ported from Python +// This module exports named constants for use throughout the library. + +// Four primitive qualities +export const HOT = 'Hot'; +export const COLD = 'Cold'; +export const DRY = 'Dry'; +export const HUMID = 'Humid'; + +// Four Elements +export const FIRE = 'Fire'; +export const EARTH = 'Earth'; +export const AIR = 'Air'; +export const WATER = 'Water'; + +// Four Temperaments +export const CHOLERIC = 'Choleric'; +export const MELANCHOLIC = 'Melancholic'; +export const SANGUINE = 'Sanguine'; +export const PHLEGMATIC = 'Phlegmatic'; + +// Genders +export const MASCULINE = 'Masculine'; +export const FEMININE = 'Feminine'; +export const NEUTRAL = 'Neutral'; + +// Factions +export const DIURNAL = 'Diurnal'; +export const NOCTURNAL = 'Nocturnal'; + +// Sun seasons +export const SPRING = 'Spring'; +export const SUMMER = 'Summer'; +export const AUTUMN = 'Autumn'; +export const WINTER = 'Winter'; + +// Moon Quarters +export const MOON_FIRST_QUARTER = 'First Quarter'; +export const MOON_SECOND_QUARTER = 'Second Quarter'; +export const MOON_THIRD_QUARTER = 'Third Quarter'; +export const MOON_LAST_QUARTER = 'Last Quarter'; + +// === Signs === +export const ARIES = 'Aries'; +export const TAURUS = 'Taurus'; +export const GEMINI = 'Gemini'; +export const CANCER = 'Cancer'; +export const LEO = 'Leo'; +export const VIRGO = 'Virgo'; +export const LIBRA = 'Libra'; +export const SCORPIO = 'Scorpio'; +export const SAGITTARIUS = 'Sagittarius'; +export const CAPRICORN = 'Capricorn'; +export const AQUARIUS = 'Aquarius'; +export const PISCES = 'Pisces'; + +export const CARDINAL = 'Cardinal'; +export const FIXED = 'Fixed'; +export const MUTABLE = 'Mutable'; + +export const SIGN_FIGURE_NONE = 'None'; +export const SIGN_FIGURE_BEAST = 'Beast'; +export const SIGN_FIGURE_HUMAN = 'Human'; +export const SIGN_FIGURE_WILD = 'Wild'; + +export const SIGN_FERTILE = 'Fertile'; +export const SIGN_STERILE = 'Sterile'; +export const SIGN_MODERATELY_FERTILE = 'Moderately Fertile'; +export const SIGN_MODERATELY_STERILE = 'Moderately Sterile'; + +// === Objects === +export const SUN = 'Sun'; +export const MOON = 'Moon'; +export const MERCURY = 'Mercury'; +export const VENUS = 'Venus'; +export const MARS = 'Mars'; +export const JUPITER = 'Jupiter'; +export const SATURN = 'Saturn'; +export const URANUS = 'Uranus'; +export const NEPTUNE = 'Neptune'; +export const PLUTO = 'Pluto'; +export const CHIRON = 'Chiron'; +export const NORTH_NODE = 'North Node'; +export const SOUTH_NODE = 'South Node'; +export const SYZYGY = 'Syzygy'; +export const PARS_FORTUNA = 'Pars Fortuna'; +export const NO_PLANET = 'None'; + +export const DIRECT = 'Direct'; +export const RETROGRADE = 'Retrogade'; +export const STATIONARY = 'Stationary'; + +export const MEAN_MOTION_SUN = 0.9833; +export const MEAN_MOTION_MOON = 13.1833; + +export const OBJ_PLANET = 'Planet'; +export const OBJ_HOUSE = 'House'; +export const OBJ_MOON_NODE = 'Moon Node'; +export const OBJ_ARABIC_PART = 'Arabic Part'; +export const OBJ_FIXED_STAR = 'Fixed Star'; +export const OBJ_ASTEROID = 'Asteroid'; +export const OBJ_LUNATION = 'Lunation'; +export const OBJ_GENERIC = 'Generic'; + +// === Houses === +export const HOUSE1 = 'House1'; +export const HOUSE2 = 'House2'; +export const HOUSE3 = 'House3'; +export const HOUSE4 = 'House4'; +export const HOUSE5 = 'House5'; +export const HOUSE6 = 'House6'; +export const HOUSE7 = 'House7'; +export const HOUSE8 = 'House8'; +export const HOUSE9 = 'House9'; +export const HOUSE10 = 'House10'; +export const HOUSE11 = 'House11'; +export const HOUSE12 = 'House12'; + +export const ANGULAR = 'Angular'; +export const SUCCEDENT = 'Succedent'; +export const CADENT = 'Cadent'; + +export const HOUSES_BENEFIC = [HOUSE1, HOUSE5, HOUSE11]; +export const HOUSES_MALEFIC = [HOUSE6, HOUSE12]; + +export const HOUSES_PLACIDUS = 'Placidus'; +export const HOUSES_KOCH = 'Koch'; +export const HOUSES_PORPHYRIUS = 'Porphyrius'; +export const HOUSES_REGIOMONTANUS = 'Regiomontanus'; +export const HOUSES_CAMPANUS = 'Campanus'; +export const HOUSES_EQUAL = 'Equal'; +export const HOUSES_EQUAL_2 = 'Equal 2'; +export const HOUSES_VEHLOW_EQUAL = 'Vehlow Equal'; +export const HOUSES_WHOLE_SIGN = 'Whole Sign'; +export const HOUSES_MERIDIAN = 'Meridian'; +export const HOUSES_AZIMUTHAL = 'Azimuthal'; +export const HOUSES_POLICH_PAGE = 'Polich Page'; +export const HOUSES_ALCABITUS = 'Alcabitus'; +export const HOUSES_MORINUS = 'Morinus'; +export const HOUSES_DEFAULT = HOUSES_PLACIDUS; + +// === Angles === +export const ASC = 'Asc'; +export const DESC = 'Desc'; +export const MC = 'MC'; +export const IC = 'IC'; + +// === Fixed Stars === +export const STAR_ALGENIB = 'Algenib'; +export const STAR_ALPHERATZ = 'Alpheratz'; +export const STAR_ALGOL = 'Algol'; +export const STAR_ALCYONE = 'Alcyone'; +export const STAR_PLEIADES = STAR_ALCYONE; +export const STAR_ALDEBARAN = 'Aldebaran'; +export const STAR_RIGEL = 'Rigel'; +export const STAR_CAPELLA = 'Capella'; +export const STAR_BETELGEUSE = 'Betelgeuse'; +export const STAR_SIRIUS = 'Sirius'; +export const STAR_CANOPUS = 'Canopus'; +export const STAR_CASTOR = 'Castor'; +export const STAR_POLLUX = 'Pollux'; +export const STAR_PROCYON = 'Procyon'; +export const STAR_ASELLUS_BOREALIS = 'Asellus Borealis'; +export const STAR_ASELLUS_AUSTRALIS = 'Asellus Australis'; +export const STAR_ALPHARD = 'Alphard'; +export const STAR_REGULUS = 'Regulus'; +export const STAR_DENEBOLA = 'Denebola'; +export const STAR_ALGORAB = 'Algorab'; +export const STAR_SPICA = 'Spica'; +export const STAR_ARCTURUS = 'Arcturus'; \ No newline at end of file diff --git a/js/src/flatlib/datetime.js b/js/src/flatlib/datetime.js new file mode 100644 index 0000000..34076b2 --- /dev/null +++ b/js/src/flatlib/datetime.js @@ -0,0 +1,177 @@ +// Port of flatlib/datetime.py to JavaScript + +import * as angle from './angle.js'; + +// Calendar types +export const GREGORIAN = 0; +export const JULIAN = 1; + +// === Julian Day Number conversions === +export function dateJDN(year, month, day, calendar) { + const a = Math.floor((14 - month) / 12); + const y = year + 4800 - a; + const m = month + 12 * a - 3; + if (calendar === GREGORIAN) { + return ( + day + + Math.floor((153 * m + 2) / 5) + + 365 * y + + Math.floor(y / 4) - + Math.floor(y / 100) + + Math.floor(y / 400) - + 32045 + ); + } else { + return ( + day + + Math.floor((153 * m + 2) / 5) + + 365 * y + + Math.floor(y / 4) - + 32083 + ); + } +} + +export function jdnDate(jdn) { + let a = jdn + 32044; + let b = Math.floor((4 * a + 3) / 146097); + let c = a - Math.floor((146097 * b) / 4); + let d = Math.floor((4 * c + 3) / 1461); + let e = c - Math.floor((1461 * d) / 4); + let m = Math.floor((5 * e + 2) / 153); + const day = e + 1 - Math.floor((153 * m + 2) / 5); + const month = m + 3 - 12 * Math.floor(m / 10); + const year = 100 * b + d - 4800 + Math.floor(m / 10); + return [year, month, day]; +} + +// ------------------ // +// Date Class // +// ------------------ // +export class DateObj { + constructor(value, calendar = GREGORIAN) { + if (typeof value === 'string') { + const parts = value.split('/').map((v) => parseInt(v, 10)); + value = dateJDN(parts[0], parts[1], parts[2], calendar); + } else if (Array.isArray(value)) { + value = dateJDN(value[0], value[1], value[2], calendar); + } + this.jdn = parseInt(value, 10); + } + + dayofweek() { + return (this.jdn + 1) % 7; + } + + date() { + return jdnDate(this.jdn); + } + + toList() { + const date = this.date(); + const sign = date[0] >= 0 ? '+' : '-'; + date[0] = Math.abs(date[0]); + return [sign, ...date]; + } + + toString() { + const slist = this.toList(); + const sign = slist[0] === '+' ? '' : '-'; + const string = slist + .slice(1) + .map((v) => String(v).padStart(2, '0')) + .join('/'); + return sign + string; + } + + toStringVerbose() { + return `<${this.toString()}>`; + } +} + +// ------------------ // +// Time Class // +// ------------------ // +export class Time { + constructor(value) { + this.value = angle.toFloat(value); + } + + getUTC(utcoffset) { + const newTime = (this.value - utcoffset.value) % 24; + return new Time(newTime); + } + + time() { + const slist = this.toList(); + if (slist[0] === '-') { + slist[1] *= -1; + if (slist[1] === -0) slist[1] = -0.0; + } + return slist.slice(1); + } + + toList() { + const slist = angle.toList(this.value); + slist[1] = slist[1] % 24; + return slist; + } + + toString() { + const slist = this.toList(); + const string = angle.slistStr(slist); + return slist[0] === '-' ? string : string.slice(1); + } + + toStringVerbose() { + return `<${this.toString()}>`; + } +} + +// ------------------ // +// Datetime Class // +// ------------------ // +export class Datetime { + constructor(date, time = 0, utcoffset = 0, calendar = GREGORIAN) { + if (date instanceof DateObj) { + this.date = date; + } else { + this.date = new DateObj(date, calendar); + } + if (time instanceof Time) { + this.time = time; + } else { + this.time = new Time(time); + } + if (utcoffset instanceof Time) { + this.utcoffset = utcoffset; + } else { + this.utcoffset = new Time(utcoffset); + } + this.jd = + this.date.jdn + + this.time.value / 24.0 - + this.utcoffset.value / 24.0 - + 0.5; + } + + static fromJD(jd, utcoffset) { + if (!(utcoffset instanceof Time)) { + utcoffset = new Time(utcoffset); + } + const localJD = jd + utcoffset.value / 24.0; + const date = new DateObj(Math.round(localJD)); + const time = new Time((localJD + 0.5 - date.jdn) * 24); + return new Datetime(date, time, utcoffset); + } + + getUTC() { + const timeUTC = this.time.getUTC(this.utcoffset); + const dateUTC = new DateObj(Math.round(this.jd)); + return new Datetime(dateUTC, timeUTC); + } + + toString() { + return `<${this.date.toString()} ${this.time.toString()} ${this.utcoffset.toString()}>`; + } +} diff --git a/js/src/flatlib/ephem/eph.js b/js/src/flatlib/ephem/eph.js new file mode 100644 index 0000000..d6cd773 --- /dev/null +++ b/js/src/flatlib/ephem/eph.js @@ -0,0 +1,83 @@ +// Port of flatlib/ephem/eph.py to JavaScript + +import * as swe from './swe.js'; +import * as angle from '../angle.js'; +import * as consts from '../const.js'; + +// Helper for sign information +function _signInfo(obj) { + const lon = obj.lon; + obj.sign = consts.LIST_SIGNS[Math.floor(lon / 30)]; + obj.signlon = lon % 30; +} + +// === Objects === +export function getObject(ID, jd, lat, lon) { + let obj; + if (ID === consts.SOUTH_NODE) { + obj = swe.sweObject(consts.NORTH_NODE, jd); + obj.id = consts.SOUTH_NODE; + obj.lon = angle.norm(obj.lon + 180); + } else if (ID === consts.PARS_FORTUNA) { + const pflon = tools.pfLon(jd, lat, lon); + obj = { + id: ID, + lon: pflon, + lat: 0, + lonspeed: 0, + latspeed: 0, + }; + } else if (ID === consts.SYZYGY) { + const szjd = tools.syzygyJD(jd); + obj = swe.sweObject(consts.MOON, szjd); + obj.id = consts.SYZYGY; + } else { + obj = swe.sweObject(ID, jd); + } + _signInfo(obj); + return obj; +} + +// === Houses === +export function getHouses(jd, lat, lon, hsys) { + const [houses, angles] = swe.sweHouses(jd, lat, lon, hsys); + houses.forEach(_signInfo); + angles.forEach(_signInfo); + return [houses, angles]; +} + +// === Fixed stars === +export function getFixedStar(ID, jd) { + const star = swe.sweFixedStar(ID, jd); + _signInfo(star); + return star; +} + +// === Solar returns === +export function nextSolarReturn(jd, lon) { + return tools.solarReturnJD(jd, lon, true); +} + +export function prevSolarReturn(jd, lon) { + return tools.solarReturnJD(jd, lon, false); +} + +// === Sunrises/sunsets === +export function nextSunrise(jd, lat, lon) { + return swe.sweNextTransit(consts.SUN, jd, lat, lon, 'RISE'); +} + +export function nextSunset(jd, lat, lon) { + return swe.sweNextTransit(consts.SUN, jd, lat, lon, 'SET'); +} + +export function lastSunrise(jd, lat, lon) { + return nextSunrise(jd - 1.0, lat, lon); +} + +export function lastSunset(jd, lat, lon) { + return nextSunset(jd - 1.0, lat, lon); +} + +// import tools at bottom to avoid circular dependency +import * as tools from './tools.js'; diff --git a/js/src/flatlib/ephem/ephem.js b/js/src/flatlib/ephem/ephem.js new file mode 100644 index 0000000..5fe4245 --- /dev/null +++ b/js/src/flatlib/ephem/ephem.js @@ -0,0 +1,79 @@ +// Port of flatlib/ephem/ephem.py to JavaScript + +import * as eph from './eph.js'; + +// Classes imported from other modules. For now they are +// simple wrappers that effectively return their input. +import { Datetime } from '../datetime.js'; +import { Object, House, FixedStar, GenericObject } from '../object.js'; +import { ObjectList, HouseList, FixedStarList, GenericList } from '../lists.js'; + +// === Objects === +export function getObject(ID, date, pos) { + const obj = eph.getObject(ID, date.jd, pos.lat, pos.lon); + return Object.fromDict(obj); +} + +export function getObjectList(IDs, date, pos) { + const objList = IDs.map((ID) => getObject(ID, date, pos)); + return new ObjectList(objList); +} + +// === Houses and angles === +export function getHouses(date, pos, hsys) { + const [houses, angles] = eph.getHouses(date.jd, pos.lat, pos.lon, hsys); + const hList = houses.map((h) => House.fromDict(h)); + const aList = angles.map((a) => GenericObject.fromDict(a)); + return [new HouseList(hList), new GenericList(aList)]; +} + +export function getHouseList(date, pos, hsys) { + return getHouses(date, pos, hsys)[0]; +} + +export function getAngleList(date, pos, hsys) { + return getHouses(date, pos, hsys)[1]; +} + +// === Fixed stars === +export function getFixedStar(ID, date) { + const star = eph.getFixedStar(ID, date.jd); + return FixedStar.fromDict(star); +} + +export function getFixedStarList(IDs, date) { + const starList = IDs.map((ID) => getFixedStar(ID, date)); + return new FixedStarList(starList); +} + +// === Solar returns === +export function nextSolarReturn(date, lon) { + const jd = eph.nextSolarReturn(date.jd, lon); + return Datetime.fromJD(jd, date.utcoffset); +} + +export function prevSolarReturn(date, lon) { + const jd = eph.prevSolarReturn(date.jd, lon); + return Datetime.fromJD(jd, date.utcoffset); +} + +// === Sunrise and sunsets === +export function nextSunrise(date, pos) { + const jd = eph.nextSunrise(date.jd, pos.lat, pos.lon); + return Datetime.fromJD(jd, date.utcoffset); +} + +export function nextSunset(date, pos) { + const jd = eph.nextSunset(date.jd, pos.lat, pos.lon); + return Datetime.fromJD(jd, date.utcoffset); +} + +export function lastSunrise(date, pos) { + const jd = eph.lastSunrise(date.jd, pos.lat, pos.lon); + return Datetime.fromJD(jd, date.utcoffset); +} + +export function lastSunset(date, pos) { + const jd = eph.lastSunset(date.jd, pos.lat, pos.lon); + return Datetime.fromJD(jd, date.utcoffset); +} diff --git a/js/src/flatlib/ephem/index.js b/js/src/flatlib/ephem/index.js new file mode 100644 index 0000000..881052c --- /dev/null +++ b/js/src/flatlib/ephem/index.js @@ -0,0 +1,23 @@ +// Entry point for ephem subpackage + +import * as swe from './swe.js'; +import * as eph from './eph.js'; +import * as tools from './tools.js'; +import * as ephem from './ephem.js'; + +export { swe, eph, tools, ephem }; + +// Set default path; user may override using setPath +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// default sweepath, mimicking Python's flatlib.PATH_RES + 'swefiles' +export function setPath(p) { + swe.setPath(p); +} + +// immediately set path to a reasonable default (needs adjustment) +// For now leave empty or require user to call explicitly. diff --git a/js/src/flatlib/ephem/swe.js b/js/src/flatlib/ephem/swe.js new file mode 100644 index 0000000..fba40f1 --- /dev/null +++ b/js/src/flatlib/ephem/swe.js @@ -0,0 +1,149 @@ +// Port of flatlib/ephem/swe.py to JavaScript +// This module provides a thin wrapper around the Swiss +// Ephemeris. Node users should install the `swisseph` npm +// package (https://www.npmjs.com/package/swisseph). + +// Attempt to load the native Swiss Ephemeris wrapper. If the +// package is not installed (e.g. during early development) we fall +// back to a stubbed object so that the module can still be imported. +import * as angle from '../angle.js'; +import * as consts from '../const.js'; + +import { createRequire } from 'module'; +const require = createRequire(import.meta.url); +let swisseph; +try { + swisseph = require('swisseph'); +} catch (err) { + // stub functions with basic signatures + swisseph = { + swe_set_ephe_path: () => {}, + swe_calc_ut: () => [0, 0, 0, 0, 0], + swe_rise_trans: () => [0, [0, 0]], + swe_houses: () => [[0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0]], + swe_fixstar_ut: () => [0, 0], + swe_fixstar_mag: () => 0, + CALC_RISE: 0, + CALC_SET: 1, + }; +} + +// Map objects +export const SWE_OBJECTS = { + [consts.SUN]: 0, + [consts.MOON]: 1, + [consts.MERCURY]: 2, + [consts.VENUS]: 3, + [consts.MARS]: 4, + [consts.JUPITER]: 5, + [consts.SATURN]: 6, + [consts.URANUS]: 7, + [consts.NEPTUNE]: 8, + [consts.PLUTO]: 9, + [consts.CHIRON]: 15, + [consts.NORTH_NODE]: 10, +}; + +// Map house systems +export const SWE_HOUSESYS = { + [consts.HOUSES_PLACIDUS]: 'P', + [consts.HOUSES_KOCH]: 'K', + [consts.HOUSES_PORPHYRIUS]: 'O', + [consts.HOUSES_REGIOMONTANUS]: 'R', + [consts.HOUSES_CAMPANUS]: 'C', + [consts.HOUSES_EQUAL]: 'A', + [consts.HOUSES_EQUAL_2]: 'E', + [consts.HOUSES_VEHLOW_EQUAL]: 'V', + [consts.HOUSES_WHOLE_SIGN]: 'W', + [consts.HOUSES_MERIDIAN]: 'X', + [consts.HOUSES_AZIMUTHAL]: 'H', + [consts.HOUSES_POLICH_PAGE]: 'T', + [consts.HOUSES_ALCABITUS]: 'B', + [consts.HOUSES_MORINUS]: 'M', +}; + +// ==== Internal functions ==== +export function setPath(path) { + swisseph.swe_set_ephe_path(path); +} + +// === Object functions === +export function sweObject(obj, jd) { + const sweObj = SWE_OBJECTS[obj]; + // `swe_calc_ut` returns an array [lon, lat, distance, speedlon,...] + const res = swisseph.swe_calc_ut(jd, sweObj); + // some wrappers return object with error, handle both + const sweList = Array.isArray(res) ? res : res[0]; + return { + id: obj, + lon: sweList[0], + lat: sweList[1], + lonspeed: sweList[3], + latspeed: sweList[4], + }; +} + +export function sweObjectLon(obj, jd) { + const sweObj = SWE_OBJECTS[obj]; + const res = swisseph.swe_calc_ut(jd, sweObj); + const sweList = Array.isArray(res) ? res : res[0]; + return sweList[0]; +} + +export function sweNextTransit(obj, jd, lat, lon, flag) { + const sweObj = SWE_OBJECTS[obj]; + const swFlag = flag === 'RISE' ? swisseph.CALC_RISE : swisseph.CALC_SET; + const trans = swisseph.swe_rise_trans(jd, sweObj, lon, lat, 0, 0, 0, swFlag); + // returns [status, [jdRise, jdSet]] + return trans[1][0]; +} + +// === Houses and angles === +export function sweHouses(jd, lat, lon, hsys) { + const code = SWE_HOUSESYS[hsys]; + const res = swisseph.swe_houses(jd, lat, lon, code); + // result is [hlist, ascmc] + const hlist = res[0].slice(); + const ascmc = res[1].slice(); + hlist.push(hlist[0]); + const houses = []; + for (let i = 0; i < 12; i++) { + houses.push({ + id: consts.LIST_HOUSES[i], + lon: hlist[i], + size: angle.distance(hlist[i], hlist[i + 1]), + }); + } + const angles = [ + { id: consts.ASC, lon: ascmc[0] }, + { id: consts.MC, lon: ascmc[1] }, + { id: consts.DESC, lon: angle.norm(ascmc[0] + 180) }, + { id: consts.IC, lon: angle.norm(ascmc[1] + 180) }, + ]; + return [houses, angles]; +} + +export function sweHousesLon(jd, lat, lon, hsys) { + const code = SWE_HOUSESYS[hsys]; + const res = swisseph.swe_houses(jd, lat, lon, code); + const ascmc = res[1]; + const angles = [ + ascmc[0], + ascmc[1], + angle.norm(ascmc[0] + 180), + angle.norm(ascmc[1] + 180), + ]; + return [res[0], angles]; +} + +// === Fixed stars === +export function sweFixedStar(star, jd) { + const sweList = swisseph.swe_fixstar_ut(star, jd); + const mag = swisseph.swe_fixstar_mag(star); + return { + id: star, + mag, + lon: sweList[0], + lat: sweList[1], + }; +} diff --git a/js/src/flatlib/ephem/tools.js b/js/src/flatlib/ephem/tools.js new file mode 100644 index 0000000..08bdd75 --- /dev/null +++ b/js/src/flatlib/ephem/tools.js @@ -0,0 +1,61 @@ +// Port of flatlib/ephem/tools.py to JavaScript + +import * as swe from './swe.js'; +import * as angle from '../angle.js'; +import * as consts from '../const.js'; +import * as utils from '../utils.js'; + +export const MAX_ERROR = 0.0003; + +// === Object positions === +export function pfLon(jd, lat, lon) { + const sun = swe.sweObjectLon(consts.SUN, jd); + const moon = swe.sweObjectLon(consts.MOON, jd); + const asc = swe.sweHousesLon(jd, lat, lon, consts.HOUSES_DEFAULT)[1][0]; + + if (isDiurnal(jd, lat, lon)) { + return angle.norm(asc + moon - sun); + } else { + return angle.norm(asc + sun - moon); + } +} + +// === Diurnal === +export function isDiurnal(jd, lat, lon) { + const sun = swe.sweObject(consts.SUN, jd); + const mc = swe.sweHousesLon(jd, lat, lon, consts.HOUSES_DEFAULT)[1][1]; + const [ra, decl] = utils.eqCoords(sun.lon, sun.lat); + const [mcRA] = utils.eqCoords(mc, 0.0); + return utils.isAboveHorizon(ra, decl, mcRA, lat); +} + +// === Iterative algorithms === +export function syzygyJD(jd) { + let sun = swe.sweObjectLon(consts.SUN, jd); + let moon = swe.sweObjectLon(consts.MOON, jd); + let dist = angle.distance(sun, moon); + const offset = dist >= 180 ? 180 : 0; + while (Math.abs(dist) > MAX_ERROR) { + jd = jd - dist / 13.1833; + sun = swe.sweObjectLon(consts.SUN, jd); + moon = swe.sweObjectLon(consts.MOON, jd); + dist = angle.closestdistance(sun - offset, moon); + } + return jd; +} + +export function solarReturnJD(jd, lon, forward = true) { + let sun = swe.sweObjectLon(consts.SUN, jd); + let dist; + if (forward) { + dist = angle.distance(sun, lon); + } else { + dist = angle.distance(lon, sun); + } + while (Math.abs(dist) > MAX_ERROR) { + jd = jd + dist / 0.9833; + sun = swe.sweObjectLon(consts.SUN, jd); + dist = angle.closestdistance(sun, lon); + } + return jd; +} diff --git a/js/src/flatlib/geopos.js b/js/src/flatlib/geopos.js new file mode 100644 index 0000000..5f0ad03 --- /dev/null +++ b/js/src/flatlib/geopos.js @@ -0,0 +1,58 @@ +// Port of flatlib/geopos.py to JavaScript + +import * as angle from './angle.js'; + +export const LAT = 0; +export const LON = 1; + +export const SIGN = { N: '+', S: '-', E: '+', W: '-' }; +export const CHAR = { + [LAT]: { '+': 'N', '-': 'S' }, + [LON]: { '+': 'E', '-': 'W' }, +}; + +export function toFloat(value) { + if (typeof value === 'string') { + let v = value.toUpperCase(); + for (const char of ['N', 'S', 'E', 'W']) { + if (v.includes(char)) { + v = SIGN[char] + v.replace(char, ':'); + break; + } + } + value = v; + } + return angle.toFloat(value); +} + +export function toList(value) { + return angle.toList(value); +} + +export function toString(value, mode) { + let string = angle.toString(value); + const sign = string[0]; + const separator = CHAR[mode][sign]; + string = string.replace(':', separator); + return string.slice(1); +} + +export class GeoPos { + constructor(lat, lon) { + this.lat = toFloat(lat); + this.lon = toFloat(lon); + } + + slists() { + return [toList(this.lat), toList(this.lon)]; + } + + strings() { + return [toString(this.lat, LAT), toString(this.lon, LON)]; + } + + toString() { + const [la, lo] = this.strings(); + return `<${la} ${lo}>`; + } +} diff --git a/js/src/flatlib/lists.js b/js/src/flatlib/lists.js new file mode 100644 index 0000000..9517b36 --- /dev/null +++ b/js/src/flatlib/lists.js @@ -0,0 +1,15 @@ +// Port of flatlib/lists.py to JavaScript + +// The Python version defines specialized list classes; here we +// provide simple wrappers that behave like arrays but also +// carry a type identifier if needed. + +export class GenericList extends Array { + constructor(items = []) { + super(...items); + } +} + +export class ObjectList extends GenericList {} +export class HouseList extends GenericList {} +export class FixedStarList extends GenericList {} diff --git a/js/src/flatlib/object.js b/js/src/flatlib/object.js new file mode 100644 index 0000000..27da0a1 --- /dev/null +++ b/js/src/flatlib/object.js @@ -0,0 +1,209 @@ +// Port of flatlib/object.py to JavaScript + +import * as consts from './const.js'; +import * as angle from './angle.js'; +import * as utils from './utils.js'; + +// For now props is stubbed; real implementation should mirror Python's props +import { objectProps, houseProps } from './props.js'; + +// ------------------ // +// Generic Object // +// ------------------ // + +export class GenericObject { + constructor() { + this.id = consts.NO_PLANET; + this.type = consts.OBJ_GENERIC; + this.lon = 0.0; + this.lat = 0.0; + this.sign = consts.ARIES; + this.signlon = 0.0; + } + + static fromDict(dict) { + const obj = new this(); + Object.assign(obj, dict); + return obj; + } + + copy() { + return this.constructor.fromDict({ ...this }); + } + + toString() { + return `<${this.id} ${this.sign} ${angle.toString(this.signlon)}>`; + } + + orb() { + return -1.0; + } + + isPlanet() { + return this.type === consts.OBJ_PLANET; + } + + eqCoords(zerolat = false) { + const lat = zerolat ? 0.0 : this.lat; + return utils.eqCoords(this.lon, lat); + } + + relocate(lon) { + this.lon = angle.norm(lon); + this.signlon = this.lon % 30; + this.sign = consts.LIST_SIGNS[Math.floor(this.lon / 30.0)]; + } + + antiscia() { + const obj = this.copy(); + obj.type = consts.OBJ_GENERIC; + obj.relocate(360 - obj.lon + 180); + return obj; + } + + cantiscia() { + const obj = this.copy(); + obj.type = consts.OBJ_GENERIC; + obj.relocate(360 - obj.lon); + return obj; + } +} + +// -------------------- // +// Astrology Object // +// -------------------- // + +export class Object extends GenericObject { + constructor() { + super(); + this.type = consts.OBJ_PLANET; + this.lonspeed = 0.0; + this.latspeed = 0.0; + } + + toString() { + const base = super.toString().slice(0, -1); + return `${base} ${angle.toString(this.lonspeed)}>`; + } + + orb() { + return objectProps.orb[this.id]; + } + + meanMotion() { + return objectProps.meanMotion[this.id]; + } + + movement() { + if (Math.abs(this.lonspeed) < 0.0003) { + return consts.STATIONARY; + } else if (this.lonspeed > 0) { + return consts.DIRECT; + } + return consts.RETROGRADE; + } + + gender() { + return objectProps.gender[this.id]; + } + + faction() { + return objectProps.faction[this.id]; + } + + element() { + return objectProps.element[this.id]; + } + + isDirect() { + return this.movement() === consts.DIRECT; + } + + isRetrograde() { + return this.movement() === consts.RETROGRADE; + } + + isStationary() { + return this.movement() === consts.STATIONARY; + } + + isFast() { + return Math.abs(this.lonspeed) >= this.meanMotion(); + } +} + +// ------------------ // +// House Cusp // +// ------------------ // + +export class House extends GenericObject { + static _OFFSET = 0.0; + + constructor() { + super(); + this.type = consts.OBJ_HOUSE; + this.size = 30.0; + } + + toString() { + const base = super.toString().slice(0, -1); + return `${base} ${this.size}>`; + } + + num() { + return parseInt(this.id.slice(5), 10); + } + + condition() { + return houseProps.condition[this.id]; + } + + gender() { + return houseProps.gender[this.id]; + } + + isAboveHorizon() { + return houseProps.aboveHorizon.includes(this.id); + } + + inHouse(lon) { + const dist = angle.distance(this.lon + House._OFFSET, lon); + return dist < this.size; + } + + hasObject(obj) { + return this.inHouse(obj.lon); + } +} + +// ------------------ // +// Fixed Star // +// ------------------ // + +export class FixedStar extends GenericObject { + constructor() { + super(); + this.type = consts.OBJ_FIXED_STAR; + this.mag = 0.0; + } + + toString() { + const base = super.toString().slice(0, -1); + return `${base} ${this.mag}>`; + } + + orb() { + const ORBS = [[2, 7.5], [3, 5.5], [4, 3.5], [5, 1.5]]; + for (const [mag, orb] of ORBS) { + if (this.mag < mag) { + return orb; + } + } + return 0.5; + } + + aspects(obj) { + const dist = angle.closestdistance(this.lon, obj.lon); + return Math.abs(dist) < this.orb(); + } +} diff --git a/js/src/flatlib/props.js b/js/src/flatlib/props.js new file mode 100644 index 0000000..b48777a --- /dev/null +++ b/js/src/flatlib/props.js @@ -0,0 +1,16 @@ +// Stub for flatlib/props.py +// Real data should be ported from Python props module. + +export const objectProps = { + orb: {}, + meanMotion: {}, + gender: {}, + faction: {}, + element: {}, +}; + +export const houseProps = { + condition: {}, + gender: {}, + aboveHorizon: [], +}; diff --git a/js/src/flatlib/utils.js b/js/src/flatlib/utils.js new file mode 100644 index 0000000..098098a --- /dev/null +++ b/js/src/flatlib/utils.js @@ -0,0 +1,58 @@ +// Port of flatlib/utils.py to JavaScript + +import * as angle from './angle.js'; + +// One arc-second error for iterative algorithms +export const MAX_ERROR = 0.0003; + +// === Diurnal and nocturnal arcs === +export function ascdiff(decl, lat) { + const delta = (decl * Math.PI) / 180; + const phi = (lat * Math.PI) / 180; + const ad = Math.asin(Math.tan(delta) * Math.tan(phi)); + return (ad * 180) / Math.PI; +} + +export function dnarcs(decl, lat) { + const dArc = 180 + 2 * ascdiff(decl, lat); + const nArc = 360 - dArc; + return [dArc, nArc]; +} + +// === Above horizon === +export function isAboveHorizon(ra, decl, mcRA, lat) { + const [dArc] = dnarcs(decl, lat); + const dist = Math.abs(angle.closestdistance(mcRA, ra)); + return dist <= dArc / 2.0 + 0.0003; +} + +// === Coordinate systems === +export function eqCoords(lon, lat) { + const _lambda = (lon * Math.PI) / 180; + const _beta = (lat * Math.PI) / 180; + const _epson = (23.44 * Math.PI) / 180; + + const decl = Math.asin( + Math.sin(_epson) * Math.sin(_lambda) * Math.cos(_beta) + + Math.cos(_epson) * Math.sin(_beta) + ); + const ED = Math.acos( + (Math.cos(_lambda) * Math.cos(_beta)) / Math.cos(decl) + ); + let ra = (lon < 180 ? ED : 2 * Math.PI - ED); + + if ( + Math.abs(angle.closestdistance(lon, 0)) < 5 || + Math.abs(angle.closestdistance(lon, 180)) < 5 + ) { + const a = Math.sin(ra) * Math.cos(decl); + const b = + Math.cos(_epson) * Math.sin(_lambda) * Math.cos(_beta) - + Math.sin(_epson) * Math.sin(_beta); + if (Math.abs(a - b) > 0.0003) { + ra = 2 * Math.PI - ra; + } + } + + return [(ra * 180) / Math.PI, (decl * 180) / Math.PI]; +} diff --git a/js/tests/chart.test.js b/js/tests/chart.test.js new file mode 100644 index 0000000..ef5483a --- /dev/null +++ b/js/tests/chart.test.js @@ -0,0 +1,22 @@ +import { HOUSES_MORINUS } from '../src/flatlib/const.js'; + +// Placeholder imports; real implementations will be added once modules are ported +import { Chart } from '../src/flatlib/chart.js'; +import { Datetime } from '../src/flatlib/datetime.js'; +import { GeoPos } from '../src/flatlib/geopos.js'; + +describe('Chart', () => { + let date; + let pos; + + beforeEach(() => { + date = new Datetime('2015/03/13', '17:00', '+00:00'); + pos = new GeoPos('38n32', '8w54'); + }); + + test('solar return hsys', () => { + const chart = new Chart(date, pos, { hsys: HOUSES_MORINUS }); + const sr = chart.solarReturn(2018); + expect(chart.hsys).toBe(sr.hsys); + }); +}); \ No newline at end of file diff --git a/js/tests/ephem.test.js b/js/tests/ephem.test.js new file mode 100644 index 0000000..075ebc7 --- /dev/null +++ b/js/tests/ephem.test.js @@ -0,0 +1,17 @@ +import { ephem, swe, eph, tools } from '../src/flatlib/ephem/index.js'; +import * as consts from '../src/flatlib/const.js'; + +describe('ephem package', () => { + test('basic exports are present', () => { + expect(typeof ephem.getObject).toBe('function'); + expect(typeof ephem.getHouses).toBe('function'); + expect(typeof swe.sweObject).toBe('function'); + expect(typeof eph.getObject).toBe('function'); + expect(typeof tools.pfLon).toBe('function'); + }); + + test('object map includes Sun and Moon', () => { + expect(swe.SWE_OBJECTS[consts.SUN]).toBeDefined(); + expect(swe.SWE_OBJECTS[consts.MOON]).toBeDefined(); + }); +}); From ed2cf9f43c2f1dc2a1b58d114a78308ee580a97a Mon Sep 17 00:00:00 2001 From: Marek Hewelt <22530856+lightflicker@users.noreply.github.com> Date: Sun, 1 Mar 2026 09:17:51 +0000 Subject: [PATCH 13/14] removed js folder --- js/package.json | 21 ---- js/src/flatlib/angle.js | 96 ---------------- js/src/flatlib/const.js | 170 --------------------------- js/src/flatlib/datetime.js | 177 ---------------------------- js/src/flatlib/ephem/eph.js | 83 -------------- js/src/flatlib/ephem/ephem.js | 79 ------------- js/src/flatlib/ephem/index.js | 23 ---- js/src/flatlib/ephem/swe.js | 149 ------------------------ js/src/flatlib/ephem/tools.js | 61 ---------- js/src/flatlib/geopos.js | 58 ---------- js/src/flatlib/lists.js | 15 --- js/src/flatlib/object.js | 209 ---------------------------------- js/src/flatlib/props.js | 16 --- js/src/flatlib/utils.js | 58 ---------- js/tests/chart.test.js | 22 ---- js/tests/ephem.test.js | 17 --- 16 files changed, 1254 deletions(-) delete mode 100644 js/package.json delete mode 100644 js/src/flatlib/angle.js delete mode 100644 js/src/flatlib/const.js delete mode 100644 js/src/flatlib/datetime.js delete mode 100644 js/src/flatlib/ephem/eph.js delete mode 100644 js/src/flatlib/ephem/ephem.js delete mode 100644 js/src/flatlib/ephem/index.js delete mode 100644 js/src/flatlib/ephem/swe.js delete mode 100644 js/src/flatlib/ephem/tools.js delete mode 100644 js/src/flatlib/geopos.js delete mode 100644 js/src/flatlib/lists.js delete mode 100644 js/src/flatlib/object.js delete mode 100644 js/src/flatlib/props.js delete mode 100644 js/src/flatlib/utils.js delete mode 100644 js/tests/chart.test.js delete mode 100644 js/tests/ephem.test.js diff --git a/js/package.json b/js/package.json deleted file mode 100644 index aa684b0..0000000 --- a/js/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "flatlib-js", - "version": "0.1.0", - "description": "JavaScript port of the Python flatlib astrology library", - "main": "src/index.js", - "type": "module", - "scripts": { - "test": "jest", - "build": "echo \"No build step yet\"" - }, - "keywords": [ - "astrology", - "flatlib", - "python-port" - ], - "author": "Ported by user", - "license": "MIT", - "devDependencies": { - "jest": "^29.0.0" - } -} \ No newline at end of file diff --git a/js/src/flatlib/angle.js b/js/src/flatlib/angle.js deleted file mode 100644 index 4f6b91e..0000000 --- a/js/src/flatlib/angle.js +++ /dev/null @@ -1,96 +0,0 @@ -// Port of flatlib/angle.py to JavaScript -// Contains utilities for angle manipulation and conversions. - -// === Angular utilities === -export function norm(angle) { - return ((angle % 360) + 360) % 360; -} - -export function znorm(angle) { - let a = ((angle % 360) + 360) % 360; - return a <= 180 ? a : a - 360; -} - -export function distance(angle1, angle2) { - return norm(angle2 - angle1); -} - -export function closestdistance(angle1, angle2) { - return znorm(angle2 - angle1); -} - -// === Signed list utilities === -function _fixSlist(slist) { - while (slist.length < 4) slist.push(0); - return slist.slice(0, 4); -} - -function _roundSlist(slist) { - slist[slist.length - 1] = slist[slist.length - 1] >= 30 ? 60 : 0; - for (let i = slist.length - 1; i > 0; i--) { - if (slist[i] === 60) { - slist[i] = 0; - slist[i - 1] += 1; - } - } - return slist.slice(0, slist.length - 1); -} - -// === Base conversions === -export function strSlist(string) { - const sign = string.startsWith('-') ? '-' : '+'; - const values = string - .replace(/^[-+]/, '') - .split(':') - .map((x) => Math.abs(parseInt(x, 10))); - return _fixSlist([sign, ...values]); -} - -export function slistStr(slist) { - slist = _fixSlist(slist); - const string = slist.slice(1).map((x) => String(x).padStart(2, '0')).join(':'); - return slist[0] + string; -} - -export function slistFloat(slist) { - slist = _fixSlist(slist); - const values = slist.slice(1).map((v, i) => v / 60 ** i); - const value = values.reduce((a, b) => a + b, 0); - return slist[0] === '-' ? -value : value; -} - -export function floatSlist(value) { - const slist = ['+', 0, 0, 0, 0]; - if (value < 0) { - slist[0] = '-'; - } - let v = Math.abs(value); - for (let i = 1; i < 5; i++) { - slist[i] = Math.floor(v); - v = (v - slist[i]) * 60; - } - return _roundSlist(slist); -} - -export function strFloat(string) { - return slistFloat(strSlist(string)); -} - -export function floatStr(value) { - return slistStr(floatSlist(value)); -} - -// === Direct conversions === -export function toFloat(value) { - if (typeof value === 'string') return strFloat(value); - if (Array.isArray(value)) return slistFloat(value); - return value; -} - -export function toList(value) { - return floatSlist(value); -} - -export function toString(value) { - return floatStr(value); -} diff --git a/js/src/flatlib/const.js b/js/src/flatlib/const.js deleted file mode 100644 index d38851f..0000000 --- a/js/src/flatlib/const.js +++ /dev/null @@ -1,170 +0,0 @@ -// flatlib constants ported from Python -// This module exports named constants for use throughout the library. - -// Four primitive qualities -export const HOT = 'Hot'; -export const COLD = 'Cold'; -export const DRY = 'Dry'; -export const HUMID = 'Humid'; - -// Four Elements -export const FIRE = 'Fire'; -export const EARTH = 'Earth'; -export const AIR = 'Air'; -export const WATER = 'Water'; - -// Four Temperaments -export const CHOLERIC = 'Choleric'; -export const MELANCHOLIC = 'Melancholic'; -export const SANGUINE = 'Sanguine'; -export const PHLEGMATIC = 'Phlegmatic'; - -// Genders -export const MASCULINE = 'Masculine'; -export const FEMININE = 'Feminine'; -export const NEUTRAL = 'Neutral'; - -// Factions -export const DIURNAL = 'Diurnal'; -export const NOCTURNAL = 'Nocturnal'; - -// Sun seasons -export const SPRING = 'Spring'; -export const SUMMER = 'Summer'; -export const AUTUMN = 'Autumn'; -export const WINTER = 'Winter'; - -// Moon Quarters -export const MOON_FIRST_QUARTER = 'First Quarter'; -export const MOON_SECOND_QUARTER = 'Second Quarter'; -export const MOON_THIRD_QUARTER = 'Third Quarter'; -export const MOON_LAST_QUARTER = 'Last Quarter'; - -// === Signs === -export const ARIES = 'Aries'; -export const TAURUS = 'Taurus'; -export const GEMINI = 'Gemini'; -export const CANCER = 'Cancer'; -export const LEO = 'Leo'; -export const VIRGO = 'Virgo'; -export const LIBRA = 'Libra'; -export const SCORPIO = 'Scorpio'; -export const SAGITTARIUS = 'Sagittarius'; -export const CAPRICORN = 'Capricorn'; -export const AQUARIUS = 'Aquarius'; -export const PISCES = 'Pisces'; - -export const CARDINAL = 'Cardinal'; -export const FIXED = 'Fixed'; -export const MUTABLE = 'Mutable'; - -export const SIGN_FIGURE_NONE = 'None'; -export const SIGN_FIGURE_BEAST = 'Beast'; -export const SIGN_FIGURE_HUMAN = 'Human'; -export const SIGN_FIGURE_WILD = 'Wild'; - -export const SIGN_FERTILE = 'Fertile'; -export const SIGN_STERILE = 'Sterile'; -export const SIGN_MODERATELY_FERTILE = 'Moderately Fertile'; -export const SIGN_MODERATELY_STERILE = 'Moderately Sterile'; - -// === Objects === -export const SUN = 'Sun'; -export const MOON = 'Moon'; -export const MERCURY = 'Mercury'; -export const VENUS = 'Venus'; -export const MARS = 'Mars'; -export const JUPITER = 'Jupiter'; -export const SATURN = 'Saturn'; -export const URANUS = 'Uranus'; -export const NEPTUNE = 'Neptune'; -export const PLUTO = 'Pluto'; -export const CHIRON = 'Chiron'; -export const NORTH_NODE = 'North Node'; -export const SOUTH_NODE = 'South Node'; -export const SYZYGY = 'Syzygy'; -export const PARS_FORTUNA = 'Pars Fortuna'; -export const NO_PLANET = 'None'; - -export const DIRECT = 'Direct'; -export const RETROGRADE = 'Retrogade'; -export const STATIONARY = 'Stationary'; - -export const MEAN_MOTION_SUN = 0.9833; -export const MEAN_MOTION_MOON = 13.1833; - -export const OBJ_PLANET = 'Planet'; -export const OBJ_HOUSE = 'House'; -export const OBJ_MOON_NODE = 'Moon Node'; -export const OBJ_ARABIC_PART = 'Arabic Part'; -export const OBJ_FIXED_STAR = 'Fixed Star'; -export const OBJ_ASTEROID = 'Asteroid'; -export const OBJ_LUNATION = 'Lunation'; -export const OBJ_GENERIC = 'Generic'; - -// === Houses === -export const HOUSE1 = 'House1'; -export const HOUSE2 = 'House2'; -export const HOUSE3 = 'House3'; -export const HOUSE4 = 'House4'; -export const HOUSE5 = 'House5'; -export const HOUSE6 = 'House6'; -export const HOUSE7 = 'House7'; -export const HOUSE8 = 'House8'; -export const HOUSE9 = 'House9'; -export const HOUSE10 = 'House10'; -export const HOUSE11 = 'House11'; -export const HOUSE12 = 'House12'; - -export const ANGULAR = 'Angular'; -export const SUCCEDENT = 'Succedent'; -export const CADENT = 'Cadent'; - -export const HOUSES_BENEFIC = [HOUSE1, HOUSE5, HOUSE11]; -export const HOUSES_MALEFIC = [HOUSE6, HOUSE12]; - -export const HOUSES_PLACIDUS = 'Placidus'; -export const HOUSES_KOCH = 'Koch'; -export const HOUSES_PORPHYRIUS = 'Porphyrius'; -export const HOUSES_REGIOMONTANUS = 'Regiomontanus'; -export const HOUSES_CAMPANUS = 'Campanus'; -export const HOUSES_EQUAL = 'Equal'; -export const HOUSES_EQUAL_2 = 'Equal 2'; -export const HOUSES_VEHLOW_EQUAL = 'Vehlow Equal'; -export const HOUSES_WHOLE_SIGN = 'Whole Sign'; -export const HOUSES_MERIDIAN = 'Meridian'; -export const HOUSES_AZIMUTHAL = 'Azimuthal'; -export const HOUSES_POLICH_PAGE = 'Polich Page'; -export const HOUSES_ALCABITUS = 'Alcabitus'; -export const HOUSES_MORINUS = 'Morinus'; -export const HOUSES_DEFAULT = HOUSES_PLACIDUS; - -// === Angles === -export const ASC = 'Asc'; -export const DESC = 'Desc'; -export const MC = 'MC'; -export const IC = 'IC'; - -// === Fixed Stars === -export const STAR_ALGENIB = 'Algenib'; -export const STAR_ALPHERATZ = 'Alpheratz'; -export const STAR_ALGOL = 'Algol'; -export const STAR_ALCYONE = 'Alcyone'; -export const STAR_PLEIADES = STAR_ALCYONE; -export const STAR_ALDEBARAN = 'Aldebaran'; -export const STAR_RIGEL = 'Rigel'; -export const STAR_CAPELLA = 'Capella'; -export const STAR_BETELGEUSE = 'Betelgeuse'; -export const STAR_SIRIUS = 'Sirius'; -export const STAR_CANOPUS = 'Canopus'; -export const STAR_CASTOR = 'Castor'; -export const STAR_POLLUX = 'Pollux'; -export const STAR_PROCYON = 'Procyon'; -export const STAR_ASELLUS_BOREALIS = 'Asellus Borealis'; -export const STAR_ASELLUS_AUSTRALIS = 'Asellus Australis'; -export const STAR_ALPHARD = 'Alphard'; -export const STAR_REGULUS = 'Regulus'; -export const STAR_DENEBOLA = 'Denebola'; -export const STAR_ALGORAB = 'Algorab'; -export const STAR_SPICA = 'Spica'; -export const STAR_ARCTURUS = 'Arcturus'; \ No newline at end of file diff --git a/js/src/flatlib/datetime.js b/js/src/flatlib/datetime.js deleted file mode 100644 index 34076b2..0000000 --- a/js/src/flatlib/datetime.js +++ /dev/null @@ -1,177 +0,0 @@ -// Port of flatlib/datetime.py to JavaScript - -import * as angle from './angle.js'; - -// Calendar types -export const GREGORIAN = 0; -export const JULIAN = 1; - -// === Julian Day Number conversions === -export function dateJDN(year, month, day, calendar) { - const a = Math.floor((14 - month) / 12); - const y = year + 4800 - a; - const m = month + 12 * a - 3; - if (calendar === GREGORIAN) { - return ( - day + - Math.floor((153 * m + 2) / 5) + - 365 * y + - Math.floor(y / 4) - - Math.floor(y / 100) + - Math.floor(y / 400) - - 32045 - ); - } else { - return ( - day + - Math.floor((153 * m + 2) / 5) + - 365 * y + - Math.floor(y / 4) - - 32083 - ); - } -} - -export function jdnDate(jdn) { - let a = jdn + 32044; - let b = Math.floor((4 * a + 3) / 146097); - let c = a - Math.floor((146097 * b) / 4); - let d = Math.floor((4 * c + 3) / 1461); - let e = c - Math.floor((1461 * d) / 4); - let m = Math.floor((5 * e + 2) / 153); - const day = e + 1 - Math.floor((153 * m + 2) / 5); - const month = m + 3 - 12 * Math.floor(m / 10); - const year = 100 * b + d - 4800 + Math.floor(m / 10); - return [year, month, day]; -} - -// ------------------ // -// Date Class // -// ------------------ // -export class DateObj { - constructor(value, calendar = GREGORIAN) { - if (typeof value === 'string') { - const parts = value.split('/').map((v) => parseInt(v, 10)); - value = dateJDN(parts[0], parts[1], parts[2], calendar); - } else if (Array.isArray(value)) { - value = dateJDN(value[0], value[1], value[2], calendar); - } - this.jdn = parseInt(value, 10); - } - - dayofweek() { - return (this.jdn + 1) % 7; - } - - date() { - return jdnDate(this.jdn); - } - - toList() { - const date = this.date(); - const sign = date[0] >= 0 ? '+' : '-'; - date[0] = Math.abs(date[0]); - return [sign, ...date]; - } - - toString() { - const slist = this.toList(); - const sign = slist[0] === '+' ? '' : '-'; - const string = slist - .slice(1) - .map((v) => String(v).padStart(2, '0')) - .join('/'); - return sign + string; - } - - toStringVerbose() { - return `<${this.toString()}>`; - } -} - -// ------------------ // -// Time Class // -// ------------------ // -export class Time { - constructor(value) { - this.value = angle.toFloat(value); - } - - getUTC(utcoffset) { - const newTime = (this.value - utcoffset.value) % 24; - return new Time(newTime); - } - - time() { - const slist = this.toList(); - if (slist[0] === '-') { - slist[1] *= -1; - if (slist[1] === -0) slist[1] = -0.0; - } - return slist.slice(1); - } - - toList() { - const slist = angle.toList(this.value); - slist[1] = slist[1] % 24; - return slist; - } - - toString() { - const slist = this.toList(); - const string = angle.slistStr(slist); - return slist[0] === '-' ? string : string.slice(1); - } - - toStringVerbose() { - return `<${this.toString()}>`; - } -} - -// ------------------ // -// Datetime Class // -// ------------------ // -export class Datetime { - constructor(date, time = 0, utcoffset = 0, calendar = GREGORIAN) { - if (date instanceof DateObj) { - this.date = date; - } else { - this.date = new DateObj(date, calendar); - } - if (time instanceof Time) { - this.time = time; - } else { - this.time = new Time(time); - } - if (utcoffset instanceof Time) { - this.utcoffset = utcoffset; - } else { - this.utcoffset = new Time(utcoffset); - } - this.jd = - this.date.jdn + - this.time.value / 24.0 - - this.utcoffset.value / 24.0 - - 0.5; - } - - static fromJD(jd, utcoffset) { - if (!(utcoffset instanceof Time)) { - utcoffset = new Time(utcoffset); - } - const localJD = jd + utcoffset.value / 24.0; - const date = new DateObj(Math.round(localJD)); - const time = new Time((localJD + 0.5 - date.jdn) * 24); - return new Datetime(date, time, utcoffset); - } - - getUTC() { - const timeUTC = this.time.getUTC(this.utcoffset); - const dateUTC = new DateObj(Math.round(this.jd)); - return new Datetime(dateUTC, timeUTC); - } - - toString() { - return `<${this.date.toString()} ${this.time.toString()} ${this.utcoffset.toString()}>`; - } -} diff --git a/js/src/flatlib/ephem/eph.js b/js/src/flatlib/ephem/eph.js deleted file mode 100644 index d6cd773..0000000 --- a/js/src/flatlib/ephem/eph.js +++ /dev/null @@ -1,83 +0,0 @@ -// Port of flatlib/ephem/eph.py to JavaScript - -import * as swe from './swe.js'; -import * as angle from '../angle.js'; -import * as consts from '../const.js'; - -// Helper for sign information -function _signInfo(obj) { - const lon = obj.lon; - obj.sign = consts.LIST_SIGNS[Math.floor(lon / 30)]; - obj.signlon = lon % 30; -} - -// === Objects === -export function getObject(ID, jd, lat, lon) { - let obj; - if (ID === consts.SOUTH_NODE) { - obj = swe.sweObject(consts.NORTH_NODE, jd); - obj.id = consts.SOUTH_NODE; - obj.lon = angle.norm(obj.lon + 180); - } else if (ID === consts.PARS_FORTUNA) { - const pflon = tools.pfLon(jd, lat, lon); - obj = { - id: ID, - lon: pflon, - lat: 0, - lonspeed: 0, - latspeed: 0, - }; - } else if (ID === consts.SYZYGY) { - const szjd = tools.syzygyJD(jd); - obj = swe.sweObject(consts.MOON, szjd); - obj.id = consts.SYZYGY; - } else { - obj = swe.sweObject(ID, jd); - } - _signInfo(obj); - return obj; -} - -// === Houses === -export function getHouses(jd, lat, lon, hsys) { - const [houses, angles] = swe.sweHouses(jd, lat, lon, hsys); - houses.forEach(_signInfo); - angles.forEach(_signInfo); - return [houses, angles]; -} - -// === Fixed stars === -export function getFixedStar(ID, jd) { - const star = swe.sweFixedStar(ID, jd); - _signInfo(star); - return star; -} - -// === Solar returns === -export function nextSolarReturn(jd, lon) { - return tools.solarReturnJD(jd, lon, true); -} - -export function prevSolarReturn(jd, lon) { - return tools.solarReturnJD(jd, lon, false); -} - -// === Sunrises/sunsets === -export function nextSunrise(jd, lat, lon) { - return swe.sweNextTransit(consts.SUN, jd, lat, lon, 'RISE'); -} - -export function nextSunset(jd, lat, lon) { - return swe.sweNextTransit(consts.SUN, jd, lat, lon, 'SET'); -} - -export function lastSunrise(jd, lat, lon) { - return nextSunrise(jd - 1.0, lat, lon); -} - -export function lastSunset(jd, lat, lon) { - return nextSunset(jd - 1.0, lat, lon); -} - -// import tools at bottom to avoid circular dependency -import * as tools from './tools.js'; diff --git a/js/src/flatlib/ephem/ephem.js b/js/src/flatlib/ephem/ephem.js deleted file mode 100644 index 5fe4245..0000000 --- a/js/src/flatlib/ephem/ephem.js +++ /dev/null @@ -1,79 +0,0 @@ -// Port of flatlib/ephem/ephem.py to JavaScript - -import * as eph from './eph.js'; - -// Classes imported from other modules. For now they are -// simple wrappers that effectively return their input. -import { Datetime } from '../datetime.js'; -import { Object, House, FixedStar, GenericObject } from '../object.js'; -import { ObjectList, HouseList, FixedStarList, GenericList } from '../lists.js'; - -// === Objects === -export function getObject(ID, date, pos) { - const obj = eph.getObject(ID, date.jd, pos.lat, pos.lon); - return Object.fromDict(obj); -} - -export function getObjectList(IDs, date, pos) { - const objList = IDs.map((ID) => getObject(ID, date, pos)); - return new ObjectList(objList); -} - -// === Houses and angles === -export function getHouses(date, pos, hsys) { - const [houses, angles] = eph.getHouses(date.jd, pos.lat, pos.lon, hsys); - const hList = houses.map((h) => House.fromDict(h)); - const aList = angles.map((a) => GenericObject.fromDict(a)); - return [new HouseList(hList), new GenericList(aList)]; -} - -export function getHouseList(date, pos, hsys) { - return getHouses(date, pos, hsys)[0]; -} - -export function getAngleList(date, pos, hsys) { - return getHouses(date, pos, hsys)[1]; -} - -// === Fixed stars === -export function getFixedStar(ID, date) { - const star = eph.getFixedStar(ID, date.jd); - return FixedStar.fromDict(star); -} - -export function getFixedStarList(IDs, date) { - const starList = IDs.map((ID) => getFixedStar(ID, date)); - return new FixedStarList(starList); -} - -// === Solar returns === -export function nextSolarReturn(date, lon) { - const jd = eph.nextSolarReturn(date.jd, lon); - return Datetime.fromJD(jd, date.utcoffset); -} - -export function prevSolarReturn(date, lon) { - const jd = eph.prevSolarReturn(date.jd, lon); - return Datetime.fromJD(jd, date.utcoffset); -} - -// === Sunrise and sunsets === -export function nextSunrise(date, pos) { - const jd = eph.nextSunrise(date.jd, pos.lat, pos.lon); - return Datetime.fromJD(jd, date.utcoffset); -} - -export function nextSunset(date, pos) { - const jd = eph.nextSunset(date.jd, pos.lat, pos.lon); - return Datetime.fromJD(jd, date.utcoffset); -} - -export function lastSunrise(date, pos) { - const jd = eph.lastSunrise(date.jd, pos.lat, pos.lon); - return Datetime.fromJD(jd, date.utcoffset); -} - -export function lastSunset(date, pos) { - const jd = eph.lastSunset(date.jd, pos.lat, pos.lon); - return Datetime.fromJD(jd, date.utcoffset); -} diff --git a/js/src/flatlib/ephem/index.js b/js/src/flatlib/ephem/index.js deleted file mode 100644 index 881052c..0000000 --- a/js/src/flatlib/ephem/index.js +++ /dev/null @@ -1,23 +0,0 @@ -// Entry point for ephem subpackage - -import * as swe from './swe.js'; -import * as eph from './eph.js'; -import * as tools from './tools.js'; -import * as ephem from './ephem.js'; - -export { swe, eph, tools, ephem }; - -// Set default path; user may override using setPath -import path from 'path'; -import { fileURLToPath } from 'url'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -// default sweepath, mimicking Python's flatlib.PATH_RES + 'swefiles' -export function setPath(p) { - swe.setPath(p); -} - -// immediately set path to a reasonable default (needs adjustment) -// For now leave empty or require user to call explicitly. diff --git a/js/src/flatlib/ephem/swe.js b/js/src/flatlib/ephem/swe.js deleted file mode 100644 index fba40f1..0000000 --- a/js/src/flatlib/ephem/swe.js +++ /dev/null @@ -1,149 +0,0 @@ -// Port of flatlib/ephem/swe.py to JavaScript -// This module provides a thin wrapper around the Swiss -// Ephemeris. Node users should install the `swisseph` npm -// package (https://www.npmjs.com/package/swisseph). - -// Attempt to load the native Swiss Ephemeris wrapper. If the -// package is not installed (e.g. during early development) we fall -// back to a stubbed object so that the module can still be imported. -import * as angle from '../angle.js'; -import * as consts from '../const.js'; - -import { createRequire } from 'module'; -const require = createRequire(import.meta.url); -let swisseph; -try { - swisseph = require('swisseph'); -} catch (err) { - // stub functions with basic signatures - swisseph = { - swe_set_ephe_path: () => {}, - swe_calc_ut: () => [0, 0, 0, 0, 0], - swe_rise_trans: () => [0, [0, 0]], - swe_houses: () => [[0,0,0,0,0,0,0,0,0,0,0,0], [0,0,0,0]], - swe_fixstar_ut: () => [0, 0], - swe_fixstar_mag: () => 0, - CALC_RISE: 0, - CALC_SET: 1, - }; -} - -// Map objects -export const SWE_OBJECTS = { - [consts.SUN]: 0, - [consts.MOON]: 1, - [consts.MERCURY]: 2, - [consts.VENUS]: 3, - [consts.MARS]: 4, - [consts.JUPITER]: 5, - [consts.SATURN]: 6, - [consts.URANUS]: 7, - [consts.NEPTUNE]: 8, - [consts.PLUTO]: 9, - [consts.CHIRON]: 15, - [consts.NORTH_NODE]: 10, -}; - -// Map house systems -export const SWE_HOUSESYS = { - [consts.HOUSES_PLACIDUS]: 'P', - [consts.HOUSES_KOCH]: 'K', - [consts.HOUSES_PORPHYRIUS]: 'O', - [consts.HOUSES_REGIOMONTANUS]: 'R', - [consts.HOUSES_CAMPANUS]: 'C', - [consts.HOUSES_EQUAL]: 'A', - [consts.HOUSES_EQUAL_2]: 'E', - [consts.HOUSES_VEHLOW_EQUAL]: 'V', - [consts.HOUSES_WHOLE_SIGN]: 'W', - [consts.HOUSES_MERIDIAN]: 'X', - [consts.HOUSES_AZIMUTHAL]: 'H', - [consts.HOUSES_POLICH_PAGE]: 'T', - [consts.HOUSES_ALCABITUS]: 'B', - [consts.HOUSES_MORINUS]: 'M', -}; - -// ==== Internal functions ==== -export function setPath(path) { - swisseph.swe_set_ephe_path(path); -} - -// === Object functions === -export function sweObject(obj, jd) { - const sweObj = SWE_OBJECTS[obj]; - // `swe_calc_ut` returns an array [lon, lat, distance, speedlon,...] - const res = swisseph.swe_calc_ut(jd, sweObj); - // some wrappers return object with error, handle both - const sweList = Array.isArray(res) ? res : res[0]; - return { - id: obj, - lon: sweList[0], - lat: sweList[1], - lonspeed: sweList[3], - latspeed: sweList[4], - }; -} - -export function sweObjectLon(obj, jd) { - const sweObj = SWE_OBJECTS[obj]; - const res = swisseph.swe_calc_ut(jd, sweObj); - const sweList = Array.isArray(res) ? res : res[0]; - return sweList[0]; -} - -export function sweNextTransit(obj, jd, lat, lon, flag) { - const sweObj = SWE_OBJECTS[obj]; - const swFlag = flag === 'RISE' ? swisseph.CALC_RISE : swisseph.CALC_SET; - const trans = swisseph.swe_rise_trans(jd, sweObj, lon, lat, 0, 0, 0, swFlag); - // returns [status, [jdRise, jdSet]] - return trans[1][0]; -} - -// === Houses and angles === -export function sweHouses(jd, lat, lon, hsys) { - const code = SWE_HOUSESYS[hsys]; - const res = swisseph.swe_houses(jd, lat, lon, code); - // result is [hlist, ascmc] - const hlist = res[0].slice(); - const ascmc = res[1].slice(); - hlist.push(hlist[0]); - const houses = []; - for (let i = 0; i < 12; i++) { - houses.push({ - id: consts.LIST_HOUSES[i], - lon: hlist[i], - size: angle.distance(hlist[i], hlist[i + 1]), - }); - } - const angles = [ - { id: consts.ASC, lon: ascmc[0] }, - { id: consts.MC, lon: ascmc[1] }, - { id: consts.DESC, lon: angle.norm(ascmc[0] + 180) }, - { id: consts.IC, lon: angle.norm(ascmc[1] + 180) }, - ]; - return [houses, angles]; -} - -export function sweHousesLon(jd, lat, lon, hsys) { - const code = SWE_HOUSESYS[hsys]; - const res = swisseph.swe_houses(jd, lat, lon, code); - const ascmc = res[1]; - const angles = [ - ascmc[0], - ascmc[1], - angle.norm(ascmc[0] + 180), - angle.norm(ascmc[1] + 180), - ]; - return [res[0], angles]; -} - -// === Fixed stars === -export function sweFixedStar(star, jd) { - const sweList = swisseph.swe_fixstar_ut(star, jd); - const mag = swisseph.swe_fixstar_mag(star); - return { - id: star, - mag, - lon: sweList[0], - lat: sweList[1], - }; -} diff --git a/js/src/flatlib/ephem/tools.js b/js/src/flatlib/ephem/tools.js deleted file mode 100644 index 08bdd75..0000000 --- a/js/src/flatlib/ephem/tools.js +++ /dev/null @@ -1,61 +0,0 @@ -// Port of flatlib/ephem/tools.py to JavaScript - -import * as swe from './swe.js'; -import * as angle from '../angle.js'; -import * as consts from '../const.js'; -import * as utils from '../utils.js'; - -export const MAX_ERROR = 0.0003; - -// === Object positions === -export function pfLon(jd, lat, lon) { - const sun = swe.sweObjectLon(consts.SUN, jd); - const moon = swe.sweObjectLon(consts.MOON, jd); - const asc = swe.sweHousesLon(jd, lat, lon, consts.HOUSES_DEFAULT)[1][0]; - - if (isDiurnal(jd, lat, lon)) { - return angle.norm(asc + moon - sun); - } else { - return angle.norm(asc + sun - moon); - } -} - -// === Diurnal === -export function isDiurnal(jd, lat, lon) { - const sun = swe.sweObject(consts.SUN, jd); - const mc = swe.sweHousesLon(jd, lat, lon, consts.HOUSES_DEFAULT)[1][1]; - const [ra, decl] = utils.eqCoords(sun.lon, sun.lat); - const [mcRA] = utils.eqCoords(mc, 0.0); - return utils.isAboveHorizon(ra, decl, mcRA, lat); -} - -// === Iterative algorithms === -export function syzygyJD(jd) { - let sun = swe.sweObjectLon(consts.SUN, jd); - let moon = swe.sweObjectLon(consts.MOON, jd); - let dist = angle.distance(sun, moon); - const offset = dist >= 180 ? 180 : 0; - while (Math.abs(dist) > MAX_ERROR) { - jd = jd - dist / 13.1833; - sun = swe.sweObjectLon(consts.SUN, jd); - moon = swe.sweObjectLon(consts.MOON, jd); - dist = angle.closestdistance(sun - offset, moon); - } - return jd; -} - -export function solarReturnJD(jd, lon, forward = true) { - let sun = swe.sweObjectLon(consts.SUN, jd); - let dist; - if (forward) { - dist = angle.distance(sun, lon); - } else { - dist = angle.distance(lon, sun); - } - while (Math.abs(dist) > MAX_ERROR) { - jd = jd + dist / 0.9833; - sun = swe.sweObjectLon(consts.SUN, jd); - dist = angle.closestdistance(sun, lon); - } - return jd; -} diff --git a/js/src/flatlib/geopos.js b/js/src/flatlib/geopos.js deleted file mode 100644 index 5f0ad03..0000000 --- a/js/src/flatlib/geopos.js +++ /dev/null @@ -1,58 +0,0 @@ -// Port of flatlib/geopos.py to JavaScript - -import * as angle from './angle.js'; - -export const LAT = 0; -export const LON = 1; - -export const SIGN = { N: '+', S: '-', E: '+', W: '-' }; -export const CHAR = { - [LAT]: { '+': 'N', '-': 'S' }, - [LON]: { '+': 'E', '-': 'W' }, -}; - -export function toFloat(value) { - if (typeof value === 'string') { - let v = value.toUpperCase(); - for (const char of ['N', 'S', 'E', 'W']) { - if (v.includes(char)) { - v = SIGN[char] + v.replace(char, ':'); - break; - } - } - value = v; - } - return angle.toFloat(value); -} - -export function toList(value) { - return angle.toList(value); -} - -export function toString(value, mode) { - let string = angle.toString(value); - const sign = string[0]; - const separator = CHAR[mode][sign]; - string = string.replace(':', separator); - return string.slice(1); -} - -export class GeoPos { - constructor(lat, lon) { - this.lat = toFloat(lat); - this.lon = toFloat(lon); - } - - slists() { - return [toList(this.lat), toList(this.lon)]; - } - - strings() { - return [toString(this.lat, LAT), toString(this.lon, LON)]; - } - - toString() { - const [la, lo] = this.strings(); - return `<${la} ${lo}>`; - } -} diff --git a/js/src/flatlib/lists.js b/js/src/flatlib/lists.js deleted file mode 100644 index 9517b36..0000000 --- a/js/src/flatlib/lists.js +++ /dev/null @@ -1,15 +0,0 @@ -// Port of flatlib/lists.py to JavaScript - -// The Python version defines specialized list classes; here we -// provide simple wrappers that behave like arrays but also -// carry a type identifier if needed. - -export class GenericList extends Array { - constructor(items = []) { - super(...items); - } -} - -export class ObjectList extends GenericList {} -export class HouseList extends GenericList {} -export class FixedStarList extends GenericList {} diff --git a/js/src/flatlib/object.js b/js/src/flatlib/object.js deleted file mode 100644 index 27da0a1..0000000 --- a/js/src/flatlib/object.js +++ /dev/null @@ -1,209 +0,0 @@ -// Port of flatlib/object.py to JavaScript - -import * as consts from './const.js'; -import * as angle from './angle.js'; -import * as utils from './utils.js'; - -// For now props is stubbed; real implementation should mirror Python's props -import { objectProps, houseProps } from './props.js'; - -// ------------------ // -// Generic Object // -// ------------------ // - -export class GenericObject { - constructor() { - this.id = consts.NO_PLANET; - this.type = consts.OBJ_GENERIC; - this.lon = 0.0; - this.lat = 0.0; - this.sign = consts.ARIES; - this.signlon = 0.0; - } - - static fromDict(dict) { - const obj = new this(); - Object.assign(obj, dict); - return obj; - } - - copy() { - return this.constructor.fromDict({ ...this }); - } - - toString() { - return `<${this.id} ${this.sign} ${angle.toString(this.signlon)}>`; - } - - orb() { - return -1.0; - } - - isPlanet() { - return this.type === consts.OBJ_PLANET; - } - - eqCoords(zerolat = false) { - const lat = zerolat ? 0.0 : this.lat; - return utils.eqCoords(this.lon, lat); - } - - relocate(lon) { - this.lon = angle.norm(lon); - this.signlon = this.lon % 30; - this.sign = consts.LIST_SIGNS[Math.floor(this.lon / 30.0)]; - } - - antiscia() { - const obj = this.copy(); - obj.type = consts.OBJ_GENERIC; - obj.relocate(360 - obj.lon + 180); - return obj; - } - - cantiscia() { - const obj = this.copy(); - obj.type = consts.OBJ_GENERIC; - obj.relocate(360 - obj.lon); - return obj; - } -} - -// -------------------- // -// Astrology Object // -// -------------------- // - -export class Object extends GenericObject { - constructor() { - super(); - this.type = consts.OBJ_PLANET; - this.lonspeed = 0.0; - this.latspeed = 0.0; - } - - toString() { - const base = super.toString().slice(0, -1); - return `${base} ${angle.toString(this.lonspeed)}>`; - } - - orb() { - return objectProps.orb[this.id]; - } - - meanMotion() { - return objectProps.meanMotion[this.id]; - } - - movement() { - if (Math.abs(this.lonspeed) < 0.0003) { - return consts.STATIONARY; - } else if (this.lonspeed > 0) { - return consts.DIRECT; - } - return consts.RETROGRADE; - } - - gender() { - return objectProps.gender[this.id]; - } - - faction() { - return objectProps.faction[this.id]; - } - - element() { - return objectProps.element[this.id]; - } - - isDirect() { - return this.movement() === consts.DIRECT; - } - - isRetrograde() { - return this.movement() === consts.RETROGRADE; - } - - isStationary() { - return this.movement() === consts.STATIONARY; - } - - isFast() { - return Math.abs(this.lonspeed) >= this.meanMotion(); - } -} - -// ------------------ // -// House Cusp // -// ------------------ // - -export class House extends GenericObject { - static _OFFSET = 0.0; - - constructor() { - super(); - this.type = consts.OBJ_HOUSE; - this.size = 30.0; - } - - toString() { - const base = super.toString().slice(0, -1); - return `${base} ${this.size}>`; - } - - num() { - return parseInt(this.id.slice(5), 10); - } - - condition() { - return houseProps.condition[this.id]; - } - - gender() { - return houseProps.gender[this.id]; - } - - isAboveHorizon() { - return houseProps.aboveHorizon.includes(this.id); - } - - inHouse(lon) { - const dist = angle.distance(this.lon + House._OFFSET, lon); - return dist < this.size; - } - - hasObject(obj) { - return this.inHouse(obj.lon); - } -} - -// ------------------ // -// Fixed Star // -// ------------------ // - -export class FixedStar extends GenericObject { - constructor() { - super(); - this.type = consts.OBJ_FIXED_STAR; - this.mag = 0.0; - } - - toString() { - const base = super.toString().slice(0, -1); - return `${base} ${this.mag}>`; - } - - orb() { - const ORBS = [[2, 7.5], [3, 5.5], [4, 3.5], [5, 1.5]]; - for (const [mag, orb] of ORBS) { - if (this.mag < mag) { - return orb; - } - } - return 0.5; - } - - aspects(obj) { - const dist = angle.closestdistance(this.lon, obj.lon); - return Math.abs(dist) < this.orb(); - } -} diff --git a/js/src/flatlib/props.js b/js/src/flatlib/props.js deleted file mode 100644 index b48777a..0000000 --- a/js/src/flatlib/props.js +++ /dev/null @@ -1,16 +0,0 @@ -// Stub for flatlib/props.py -// Real data should be ported from Python props module. - -export const objectProps = { - orb: {}, - meanMotion: {}, - gender: {}, - faction: {}, - element: {}, -}; - -export const houseProps = { - condition: {}, - gender: {}, - aboveHorizon: [], -}; diff --git a/js/src/flatlib/utils.js b/js/src/flatlib/utils.js deleted file mode 100644 index 098098a..0000000 --- a/js/src/flatlib/utils.js +++ /dev/null @@ -1,58 +0,0 @@ -// Port of flatlib/utils.py to JavaScript - -import * as angle from './angle.js'; - -// One arc-second error for iterative algorithms -export const MAX_ERROR = 0.0003; - -// === Diurnal and nocturnal arcs === -export function ascdiff(decl, lat) { - const delta = (decl * Math.PI) / 180; - const phi = (lat * Math.PI) / 180; - const ad = Math.asin(Math.tan(delta) * Math.tan(phi)); - return (ad * 180) / Math.PI; -} - -export function dnarcs(decl, lat) { - const dArc = 180 + 2 * ascdiff(decl, lat); - const nArc = 360 - dArc; - return [dArc, nArc]; -} - -// === Above horizon === -export function isAboveHorizon(ra, decl, mcRA, lat) { - const [dArc] = dnarcs(decl, lat); - const dist = Math.abs(angle.closestdistance(mcRA, ra)); - return dist <= dArc / 2.0 + 0.0003; -} - -// === Coordinate systems === -export function eqCoords(lon, lat) { - const _lambda = (lon * Math.PI) / 180; - const _beta = (lat * Math.PI) / 180; - const _epson = (23.44 * Math.PI) / 180; - - const decl = Math.asin( - Math.sin(_epson) * Math.sin(_lambda) * Math.cos(_beta) + - Math.cos(_epson) * Math.sin(_beta) - ); - const ED = Math.acos( - (Math.cos(_lambda) * Math.cos(_beta)) / Math.cos(decl) - ); - let ra = (lon < 180 ? ED : 2 * Math.PI - ED); - - if ( - Math.abs(angle.closestdistance(lon, 0)) < 5 || - Math.abs(angle.closestdistance(lon, 180)) < 5 - ) { - const a = Math.sin(ra) * Math.cos(decl); - const b = - Math.cos(_epson) * Math.sin(_lambda) * Math.cos(_beta) - - Math.sin(_epson) * Math.sin(_beta); - if (Math.abs(a - b) > 0.0003) { - ra = 2 * Math.PI - ra; - } - } - - return [(ra * 180) / Math.PI, (decl * 180) / Math.PI]; -} diff --git a/js/tests/chart.test.js b/js/tests/chart.test.js deleted file mode 100644 index ef5483a..0000000 --- a/js/tests/chart.test.js +++ /dev/null @@ -1,22 +0,0 @@ -import { HOUSES_MORINUS } from '../src/flatlib/const.js'; - -// Placeholder imports; real implementations will be added once modules are ported -import { Chart } from '../src/flatlib/chart.js'; -import { Datetime } from '../src/flatlib/datetime.js'; -import { GeoPos } from '../src/flatlib/geopos.js'; - -describe('Chart', () => { - let date; - let pos; - - beforeEach(() => { - date = new Datetime('2015/03/13', '17:00', '+00:00'); - pos = new GeoPos('38n32', '8w54'); - }); - - test('solar return hsys', () => { - const chart = new Chart(date, pos, { hsys: HOUSES_MORINUS }); - const sr = chart.solarReturn(2018); - expect(chart.hsys).toBe(sr.hsys); - }); -}); \ No newline at end of file diff --git a/js/tests/ephem.test.js b/js/tests/ephem.test.js deleted file mode 100644 index 075ebc7..0000000 --- a/js/tests/ephem.test.js +++ /dev/null @@ -1,17 +0,0 @@ -import { ephem, swe, eph, tools } from '../src/flatlib/ephem/index.js'; -import * as consts from '../src/flatlib/const.js'; - -describe('ephem package', () => { - test('basic exports are present', () => { - expect(typeof ephem.getObject).toBe('function'); - expect(typeof ephem.getHouses).toBe('function'); - expect(typeof swe.sweObject).toBe('function'); - expect(typeof eph.getObject).toBe('function'); - expect(typeof tools.pfLon).toBe('function'); - }); - - test('object map includes Sun and Moon', () => { - expect(swe.SWE_OBJECTS[consts.SUN]).toBeDefined(); - expect(swe.SWE_OBJECTS[consts.MOON]).toBeDefined(); - }); -}); From b868bf69d92003811e5c4c9fe86953b1fe8e2294 Mon Sep 17 00:00:00 2001 From: Marek Hewelt <22530856+lightflicker@users.noreply.github.com> Date: Sun, 1 Mar 2026 09:36:31 +0000 Subject: [PATCH 14/14] Setting up project skeleton --- js/package.json | 19 +++++++++++++++++++ js/src/flatlib/const.js | 3 +++ js/src/flatlib/index.js | 17 +++++++++++++++++ js/tests/initial.test.js | 3 +++ 4 files changed, 42 insertions(+) create mode 100644 js/package.json create mode 100644 js/src/flatlib/const.js create mode 100644 js/src/flatlib/index.js create mode 100644 js/tests/initial.test.js diff --git a/js/package.json b/js/package.json new file mode 100644 index 0000000..8e3f616 --- /dev/null +++ b/js/package.json @@ -0,0 +1,19 @@ +{ + "name": "flatlib-js", + "version": "0.1.0", + "description": "JavaScript port of the flatlib astrology library", + "main": "src/flatlib/index.js", + "type": "module", + "scripts": { + "test": "jest" + }, + "keywords": [ + "astrology", + "flatlib" + ], + "author": "Port", + "license": "MIT", + "devDependencies": { + "jest": "^29.0.0" + } +} \ No newline at end of file diff --git a/js/src/flatlib/const.js b/js/src/flatlib/const.js new file mode 100644 index 0000000..4ea26a3 --- /dev/null +++ b/js/src/flatlib/const.js @@ -0,0 +1,3 @@ +// constant definitions will be ported later + +export const ARIES = 'Aries'; diff --git a/js/src/flatlib/index.js b/js/src/flatlib/index.js new file mode 100644 index 0000000..d71386f --- /dev/null +++ b/js/src/flatlib/index.js @@ -0,0 +1,17 @@ +// Entry point for flatlib-js. Re-export common modules. + +export * from './const.js'; +export * from './angle.js'; +export * from './utils.js'; +export * from './datetime.js'; +export * from './geopos.js'; +export * from './object.js'; +export * from './lists.js'; +export * from './chart.js'; + +// subpackages can be imported directly +export * as ephem from './ephem/index.js'; +export * as dignities from './dignities/index.js'; +export * as predictives from './predictives/index.js'; +export * as protocols from './protocols/index.js'; +export * as tools from './tools/index.js'; diff --git a/js/tests/initial.test.js b/js/tests/initial.test.js new file mode 100644 index 0000000..973b6af --- /dev/null +++ b/js/tests/initial.test.js @@ -0,0 +1,3 @@ +test('environment set up', () => { + expect(true).toBe(true); +}); \ No newline at end of file