astro_chart 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,189 @@
1
+
2
+ /************************************************************
3
+
4
+ Authors: Dieter Koch and Alois Treindl, Astrodienst Zurich
5
+
6
+ ************************************************************/
7
+ /* Copyright (C) 1997 - 2021 Astrodienst AG, Switzerland. All rights reserved.
8
+
9
+ License conditions
10
+ ------------------
11
+
12
+ This file is part of Swiss Ephemeris.
13
+
14
+ Swiss Ephemeris is distributed with NO WARRANTY OF ANY KIND. No author
15
+ or distributor accepts any responsibility for the consequences of using it,
16
+ or for whether it serves any particular purpose or works at all, unless he
17
+ or she says so in writing.
18
+
19
+ Swiss Ephemeris is made available by its authors under a dual licensing
20
+ system. The software developer, who uses any part of Swiss Ephemeris
21
+ in his or her software, must choose between one of the two license models,
22
+ which are
23
+ a) GNU Affero General Public License (AGPL)
24
+ b) Swiss Ephemeris Professional License
25
+
26
+ The choice must be made before the software developer distributes software
27
+ containing parts of Swiss Ephemeris to others, and before any public
28
+ service using the developed software is activated.
29
+
30
+ If the developer choses the AGPL software license, he or she must fulfill
31
+ the conditions of that license, which includes the obligation to place his
32
+ or her whole software project under the AGPL or a compatible license.
33
+ See https://www.gnu.org/licenses/agpl-3.0.html
34
+
35
+ If the developer choses the Swiss Ephemeris Professional license,
36
+ he must follow the instructions as found in http://www.astro.com/swisseph/
37
+ and purchase the Swiss Ephemeris Professional Edition from Astrodienst
38
+ and sign the corresponding license contract.
39
+
40
+ The License grants you the right to use, copy, modify and redistribute
41
+ Swiss Ephemeris, but only under certain conditions described in the License.
42
+ Among other things, the License requires that the copyright notices and
43
+ this notice be preserved on all copies.
44
+
45
+ Authors of the Swiss Ephemeris: Dieter Koch and Alois Treindl
46
+
47
+ The authors of Swiss Ephemeris have no control or influence over any of
48
+ the derived works, i.e. over software or services created by other
49
+ programmers which use Swiss Ephemeris functions.
50
+
51
+ The names of the authors or of the copyright holder (Astrodienst) must not
52
+ be used for promoting any software, product or service which uses or contains
53
+ the Swiss Ephemeris. This copyright notice is the ONLY place where the
54
+ names of the authors can legally appear, except in cases where they have
55
+ given special permission in writing.
56
+
57
+ The trademarks 'Swiss Ephemeris' and 'Swiss Ephemeris inside' may be used
58
+ for promoting such software, products or services.
59
+ */
60
+
61
+ #define PREC_IAU_1976_CTIES 2.0 /* J2000 +/- two centuries */
62
+ #define PREC_IAU_2000_CTIES 2.0 /* J2000 +/- two centuries */
63
+ /* we use P03 for whole ephemeris */
64
+ #define PREC_IAU_2006_CTIES 75.0 /* J2000 +/- 75 centuries */
65
+
66
+ /* For reproducing JPL Horizons to 2 mas (SEFLG_JPLHOR):
67
+ * The user has to keep the following files up to date which contain
68
+ * the earth orientation parameters related to the IAU 1980 nutation
69
+ * theory.
70
+ * Download the file
71
+ * datacenter.iers.org/eop/-/somos/5Rgv/document/tx13iers.u24/eopc04_08.62-now
72
+ * and rename it as eop_1962_today.txt. For current data and estimations for
73
+ * the near future, also download maia.usno.navy.mil/ser7/finals.all and
74
+ * rename it as eop_finals.txt */
75
+ #define DPSI_DEPS_IAU1980_FILE_EOPC04 "eop_1962_today.txt"
76
+ #define DPSI_DEPS_IAU1980_FILE_FINALS "eop_finals.txt"
77
+ #define DPSI_DEPS_IAU1980_TJD0_HORIZONS 2437684.5
78
+ #define HORIZONS_TJD0_DPSI_DEPS_IAU1980 2437684.5
79
+ #define DPSI_IAU1980_TJD0 (64.284 / 1000.0) // arcsec
80
+ #define DEPS_IAU1980_TJD0 (6.151 / 1000.0) // arcsec
81
+
82
+ /* The above files must be available in order to reproduce JPL Horizons
83
+ * in agreement with IERS Conventions 1996 (1992), p. 22.
84
+ * Call swe_calc_ut() with iflag|SEFLG_JPLHOR.
85
+ * This options works only, if the files DPSI_DEPS_IAU1980_FILE_EOPC04
86
+ * and DPSI_DEPS_IAU1980_FILE_FINALS are in the ephemeris path.
87
+ *
88
+ * If the software does not find the earth orientation files
89
+ * in the ephemeris path, then SEFLG_JPLHOR will run as
90
+ * SEFLG_JPLHOR_APPROX.
91
+ */
92
+
93
+ /* coordinate transformation */
94
+ extern void swi_coortrf(double *xpo, double *xpn, double eps);
95
+
96
+ /* coordinate transformation */
97
+ extern void swi_coortrf2(double *xpo, double *xpn, double sineps, double coseps);
98
+
99
+ /* cartesian to polar coordinates */
100
+ extern void swi_cartpol(double *x, double *l);
101
+
102
+ /* cartesian to polar coordinates with velocity */
103
+ extern void swi_cartpol_sp(double *x, double *l);
104
+ extern void swi_polcart_sp(double *l, double *x);
105
+
106
+ /* polar to cartesian coordinates */
107
+ extern void swi_polcart(double *l, double *x);
108
+
109
+ /* GCRS to J2000 */
110
+ extern void swi_bias(double *x, double tjd, int32 iflag, AS_BOOL backward);
111
+ extern void swi_get_eop_time_range(void);
112
+ /* GCRS to FK5 */
113
+ extern void swi_icrs2fk5(double *x, int32 iflag, AS_BOOL backward);
114
+
115
+ /* precession */
116
+ extern int swi_precess(double *R, double J, int32 iflag, int direction );
117
+ extern void swi_precess_speed(double *xx, double t, int32 iflag, int direction);
118
+
119
+ extern int32 swi_guess_ephe_flag(void);
120
+
121
+ /* from sweph.c, light deflection, aberration, etc. */
122
+ extern void swi_deflect_light(double *xx, double dt, int32 iflag);
123
+ extern void swi_aberr_light(double *xx, double *xe, int32 iflag);
124
+ extern int swi_plan_for_osc_elem(int32 iflag, double tjd, double *xx);
125
+ extern int swi_trop_ra2sid_lon(double *xin, double *xout, double *xoutr, int32 iflag);
126
+ extern int swi_trop_ra2sid_lon_sosy(double *xin, double *xout, int32 iflag);
127
+ extern int swi_get_observer(double tjd, int32 iflag,
128
+ AS_BOOL do_save, double *xobs, char *serr);
129
+ extern void swi_force_app_pos_etc(void);
130
+
131
+ /* obliquity of ecliptic */
132
+ extern void swi_check_ecliptic(double tjd, int32 iflag);
133
+ extern double swi_epsiln(double J, int32 iflag);
134
+ extern void swi_ldp_peps(double J, double *dpre, double *deps);
135
+
136
+ /* nutation */
137
+ extern void swi_check_nutation(double tjd, int32 iflag);
138
+ extern int swi_nutation(double J, int32 iflag, double *nutlo);
139
+ extern void swi_nutate(double *xx, int32 iflag, AS_BOOL backward);
140
+
141
+ extern void swi_mean_lunar_elements(double tjd,
142
+ double *node, double *dnode,
143
+ double *peri, double *dperi);
144
+ /* */
145
+ extern double swi_mod2PI(double x);
146
+
147
+ /* evaluation of chebyshew series and derivative */
148
+ extern double swi_echeb(double x, double *coef, int ncf);
149
+ extern double swi_edcheb(double x, double *coef, int ncf);
150
+
151
+ /* cross product of vectors */
152
+ extern void swi_cross_prod(double *a, double *b, double *x);
153
+ /* dot product of vecotrs */
154
+ extern double swi_dot_prod_unit(double *x, double *y);
155
+
156
+ extern double swi_angnorm(double x);
157
+
158
+ /* generation of SWISSEPH file names */
159
+ extern void swi_gen_filename(double tjd, int ipli, char *fname);
160
+
161
+ /* cyclic redundancy checksum (CRC), 32 bit */
162
+ extern uint32 swi_crc32(unsigned char *buf, int len);
163
+
164
+ extern int swi_cutstr(char *s, char *cutlist, char *cpos[], int nmax);
165
+ extern char *swi_right_trim(char *s);
166
+
167
+ extern double swi_kepler(double E, double M, double ecce);
168
+
169
+ extern char *swi_get_fict_name(int32 ipl, char *s);
170
+
171
+ extern void swi_FK4_FK5(double *xp, double tjd);
172
+
173
+ extern char *swi_strcpy(char *to, char *from);
174
+ extern char *swi_strncpy(char *to, char *from, size_t n);
175
+
176
+ extern double swi_deltat_ephe(double tjd_ut, int32 epheflag);
177
+
178
+ #ifdef TRACE
179
+ # define TRACE_COUNT_MAX 10000
180
+ extern TLS FILE *swi_fp_trace_c;
181
+ extern TLS FILE *swi_fp_trace_out;
182
+ extern TLS int32 swi_trace_count;
183
+ extern void swi_open_trace(char *serr);
184
+ static const char *fname_trace_c = "swetrace.c";
185
+ static const char *fname_trace_out = "swetrace.txt";
186
+ #ifdef FORCE_IFLAG
187
+ static const char *fname_force_flg = "force.flg";
188
+ #endif
189
+ #endif /* TRACE */
@@ -0,0 +1,40 @@
1
+ module AstroChart
2
+ module Aspects
3
+ # Major aspects: angle => [name, max_orb]
4
+ MAJOR = {
5
+ 60 => ["六分相", 6], # Sextile
6
+ 90 => ["四分相", 8], # Square
7
+ 120 => ["三分相", 8], # Trine
8
+ 180 => ["對分相", 10], # Opposition
9
+ }.freeze
10
+
11
+ CONJUNCTION_ORB = 15
12
+
13
+ # Calculate the aspect between two ecliptic positions.
14
+ # Returns [aspect_name, orb] or [nil, nil].
15
+ def self.calculate(pos1, pos2)
16
+ return [nil, nil] if pos1.nil? || pos2.nil?
17
+
18
+ p1 = pos1 % 360.0
19
+ p2 = pos2 % 360.0
20
+
21
+ diff = (p1 - p2).abs % 360.0
22
+ diff = 360.0 - diff if diff > 180.0
23
+
24
+ # Conjunction (widest orb)
25
+ if diff <= CONJUNCTION_ORB
26
+ return ["合相", diff.round(2)]
27
+ end
28
+
29
+ # Check major aspects
30
+ MAJOR.each do |angle, (name, max_orb)|
31
+ orb = (diff - angle).abs
32
+ if orb <= max_orb
33
+ return [name, orb.round(2)]
34
+ end
35
+ end
36
+
37
+ [nil, nil]
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,78 @@
1
+ module AstroChart
2
+ class Chart
3
+ attr_reader :birth_date, :birth_time, :latitude, :longitude, :timezone
4
+
5
+ # birth_date: "YYYY-MM-DD"
6
+ # birth_time: "HH:MM"
7
+ # latitude / longitude: Float
8
+ # timezone: IANA timezone string (e.g. "Asia/Taipei")
9
+ def initialize(birth_date:, birth_time:, latitude:, longitude:, timezone:)
10
+ @birth_date = birth_date
11
+ @birth_time = birth_time
12
+ @latitude = latitude.to_f
13
+ @longitude = longitude.to_f
14
+ @timezone = timezone
15
+ end
16
+
17
+ # Generate complete chart data.
18
+ # Returns a Hash with string keys, compatible with the existing JSON API.
19
+ def generate
20
+ jd = TimeConversion.to_julian_day(birth_date, birth_time, timezone)
21
+
22
+ cusps, ascendant = Houses.calculate(jd, latitude, longitude)
23
+ asc_zodiac = Zodiac.sign_name(ascendant)
24
+
25
+ positions = Planets.calculate_positions(jd)
26
+ planet_details = Planets.build_details(positions, cusps)
27
+
28
+ kp = Planets.key_points_data(positions, cusps, ascendant)
29
+
30
+ # Merge aspects into planet details
31
+ aspect_map = {
32
+ "太陽" => "sun_aspects",
33
+ "月亮" => "moon_aspects",
34
+ "土星" => "saturn_aspects",
35
+ "金星" => "venus_aspects",
36
+ "北交點" => "north_node_aspects",
37
+ "南交點" => "south_node_aspects",
38
+ }
39
+
40
+ planet_details.each do |planet|
41
+ key = aspect_map[planet["planet"]]
42
+ planet["aspects"] = kp[key] if key
43
+ end
44
+
45
+ # Append ruler points
46
+ planet_details.concat(kp["additional_points"])
47
+
48
+ houses_data = cusps.each_with_index.map do |deg, i|
49
+ {
50
+ "house_number" => i + 1,
51
+ "degree" => deg.round(4),
52
+ "zodiac" => Zodiac.sign_name(deg),
53
+ }
54
+ end
55
+
56
+ {
57
+ "input" => {
58
+ "birth_date" => birth_date,
59
+ "birth_time" => birth_time,
60
+ "coordinates" => {
61
+ "latitude" => latitude,
62
+ "longitude" => longitude,
63
+ },
64
+ "timezone" => timezone,
65
+ },
66
+ "chart" => {
67
+ "ascendant" => {
68
+ "zodiac" => asc_zodiac,
69
+ "degree" => (ascendant % 30).round(4),
70
+ "total_degree" => ascendant.round(4),
71
+ },
72
+ "planets" => planet_details,
73
+ "houses" => houses_data,
74
+ },
75
+ }
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,36 @@
1
+ module AstroChart
2
+ # Thin Ruby wrapper around the C extension (AstroChart::Ext).
3
+ # All public callers should go through this module so we can
4
+ # swap the backend without touching the rest of the code.
5
+ module Ephemeris
6
+ PLANETS = {
7
+ "太陽" => Ext::SUN,
8
+ "月亮" => Ext::MOON,
9
+ "水星" => Ext::MERCURY,
10
+ "金星" => Ext::VENUS,
11
+ "火星" => Ext::MARS,
12
+ "木星" => Ext::JUPITER,
13
+ "土星" => Ext::SATURN,
14
+ "天王星" => Ext::URANUS,
15
+ "海王星" => Ext::NEPTUNE,
16
+ "冥王星" => Ext::PLUTO,
17
+ "北交點" => Ext::TRUE_NODE,
18
+ }.freeze
19
+
20
+ # Convert date/time to Julian Day number.
21
+ def self.julday(year, month, day, hour)
22
+ Ext.julday(year, month, day, hour)
23
+ end
24
+
25
+ # Calculate planet ecliptic longitude (degrees 0-360).
26
+ def self.calc_ut(jd, planet_id)
27
+ Ext.calc_ut(jd, planet_id)
28
+ end
29
+
30
+ # Calculate house cusps + ascendant.
31
+ # Returns { "cusps" => [12 floats], "ascendant" => float, "mc" => float }
32
+ def self.houses(jd, latitude, longitude, system = "P")
33
+ Ext.houses(jd, latitude, longitude, system.ord)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,30 @@
1
+ module AstroChart
2
+ module Houses
3
+ # Calculate house cusps and ascendant from Julian Day + coordinates.
4
+ # Returns [cusps_array(12), ascendant_degree].
5
+ def self.calculate(jd, latitude, longitude, system = "P")
6
+ data = Ephemeris.houses(jd, latitude, longitude, system)
7
+ [data["cusps"], data["ascendant"]]
8
+ end
9
+
10
+ # Determine which house (1-12) a planet falls in based on house cusps.
11
+ def self.find_house(planet_pos, cusps)
12
+ return nil if planet_pos.nil? || cusps.nil? || cusps.empty?
13
+
14
+ n = cusps.length
15
+ base = cusps[0]
16
+
17
+ # Normalize house cusps relative to first house cusp
18
+ norm_cusps = cusps.map { |h| h >= base ? h : h + 360.0 }
19
+ norm_pos = planet_pos >= base ? planet_pos : planet_pos + 360.0
20
+
21
+ n.times do |i|
22
+ start_deg = norm_cusps[i]
23
+ end_deg = i < n - 1 ? norm_cusps[i + 1] : norm_cusps[0] + 360.0
24
+ return i + 1 if norm_pos >= start_deg && norm_pos < end_deg
25
+ end
26
+
27
+ nil
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,122 @@
1
+ module AstroChart
2
+ module Planets
3
+ # Calculate raw ecliptic longitudes for all planets + nodes.
4
+ # Returns Hash: { "太陽" => 123.45, "月亮" => 67.89, ... }
5
+ def self.calculate_positions(jd)
6
+ positions = {}
7
+
8
+ Ephemeris::PLANETS.each do |name, planet_id|
9
+ positions[name] = Ephemeris.calc_ut(jd, planet_id)
10
+ end
11
+
12
+ # South node = North node + 180
13
+ positions["南交點"] = (positions["北交點"] + 180.0) % 360.0
14
+
15
+ positions
16
+ end
17
+
18
+ # Build detailed planet info list with zodiac, house, degree.
19
+ def self.build_details(positions, cusps)
20
+ positions.map do |planet, pos|
21
+ house = Houses.find_house(pos, cusps)
22
+ next if house.nil?
23
+
24
+ {
25
+ "planet" => planet,
26
+ "zodiac" => Zodiac.sign_name(pos),
27
+ "house" => house,
28
+ "degree" => (pos % 30).round(4),
29
+ "total_degree" => pos.round(4),
30
+ }
31
+ end.compact
32
+ end
33
+
34
+ # Calculate aspects for a given point against all planet positions.
35
+ def self.aspects_for(point_pos, point_name, positions)
36
+ return [] if point_pos.nil?
37
+
38
+ results = []
39
+ positions.each do |planet, pos|
40
+ next if planet == point_name
41
+
42
+ aspect_type, orb = Aspects.calculate(point_pos, pos)
43
+ if aspect_type
44
+ results << {
45
+ "planet" => planet,
46
+ "aspect_type" => aspect_type,
47
+ "orb" => orb,
48
+ }
49
+ end
50
+ end
51
+ results
52
+ end
53
+
54
+ # Calculate key points data: aspects for major planets + ruler info.
55
+ def self.key_points_data(positions, cusps, ascendant)
56
+ sun_aspects = aspects_for(positions["太陽"], "太陽", positions)
57
+ moon_aspects = aspects_for(positions["月亮"], "月亮", positions)
58
+ saturn_aspects = aspects_for(positions["土星"], "土星", positions)
59
+ venus_aspects = aspects_for(positions["金星"], "金星", positions)
60
+ north_node_aspects = aspects_for(positions["北交點"], "北交點", positions)
61
+ south_node_aspects = aspects_for(positions["南交點"], "南交點", positions)
62
+
63
+ additional_points = []
64
+
65
+ # North Node ruler
66
+ nn_pos = positions["北交點"]
67
+ if nn_pos
68
+ nn_zodiac = Zodiac.sign_name(nn_pos)
69
+ nn_ruler = Zodiac.ruler(nn_zodiac)
70
+ nn_ruler_pos = positions[nn_ruler]
71
+ if nn_ruler_pos
72
+ additional_points << build_ruler_point("北交點定位星", nn_ruler, nn_ruler_pos, cusps, positions)
73
+ end
74
+ end
75
+
76
+ # South Node ruler
77
+ sn_pos = positions["南交點"]
78
+ if sn_pos
79
+ sn_zodiac = Zodiac.sign_name(sn_pos)
80
+ sn_ruler = Zodiac.ruler(sn_zodiac)
81
+ sn_ruler_pos = positions[sn_ruler]
82
+ if sn_ruler_pos
83
+ additional_points << build_ruler_point("南交點定位星", sn_ruler, sn_ruler_pos, cusps, positions)
84
+ end
85
+ end
86
+
87
+ # Ascendant ruler
88
+ if ascendant
89
+ asc_zodiac = Zodiac.sign_name(ascendant)
90
+ asc_ruler = Zodiac.ruler(asc_zodiac)
91
+ asc_ruler_pos = positions[asc_ruler]
92
+ if asc_ruler_pos
93
+ additional_points << build_ruler_point("上升星座定位星", asc_ruler, asc_ruler_pos, cusps, positions)
94
+ end
95
+ end
96
+
97
+ {
98
+ "sun_aspects" => sun_aspects,
99
+ "moon_aspects" => moon_aspects,
100
+ "saturn_aspects" => saturn_aspects,
101
+ "venus_aspects" => venus_aspects,
102
+ "north_node_aspects" => north_node_aspects,
103
+ "south_node_aspects" => south_node_aspects,
104
+ "additional_points" => additional_points,
105
+ }
106
+ end
107
+
108
+ private_class_method
109
+
110
+ def self.build_ruler_point(label, ruling_planet, pos, cusps, positions)
111
+ {
112
+ "planet" => label,
113
+ "ruling_planet" => ruling_planet,
114
+ "zodiac" => Zodiac.sign_name(pos),
115
+ "house" => Houses.find_house(pos, cusps),
116
+ "degree" => (pos % 30).round(4),
117
+ "total_degree" => pos.round(4),
118
+ "aspects" => aspects_for(pos, ruling_planet, positions),
119
+ }
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,30 @@
1
+ require "tzinfo"
2
+
3
+ module AstroChart
4
+ module TimeConversion
5
+ # Convert a local date/time + timezone to Julian Day.
6
+ #
7
+ # birth_date: "YYYY-MM-DD"
8
+ # birth_time: "HH:MM"
9
+ # timezone: IANA timezone string, e.g. "Asia/Taipei"
10
+ def self.to_julian_day(birth_date, birth_time, timezone)
11
+ tz = TZInfo::Timezone.get(timezone)
12
+
13
+ parts = birth_date.split("-").map(&:to_i)
14
+ year, month, day = parts
15
+
16
+ time_parts = birth_time.split(":").map(&:to_i)
17
+ hour, minute = time_parts
18
+
19
+ # Build local time and convert to UTC
20
+ local_time = Time.new(year, month, day, hour, minute, 0, tz.observed_utc_offset)
21
+
22
+ # TZInfo gives us the proper UTC offset; convert to UTC
23
+ utc_time = tz.local_to_utc(Time.new(year, month, day, hour, minute, 0))
24
+
25
+ ut_hour = utc_time.hour + utc_time.min / 60.0
26
+
27
+ Ephemeris.julday(utc_time.year, utc_time.month, utc_time.day, ut_hour)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ module AstroChart
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,37 @@
1
+ module AstroChart
2
+ module Zodiac
3
+ SIGNS = [
4
+ "牡羊座", "金牛座", "雙子座", "巨蟹座",
5
+ "獅子座", "處女座", "天秤座", "天蠍座",
6
+ "射手座", "摩羯座", "水瓶座", "雙魚座"
7
+ ].freeze
8
+
9
+ RULERS = {
10
+ "牡羊座" => "火星",
11
+ "金牛座" => "金星",
12
+ "雙子座" => "水星",
13
+ "巨蟹座" => "月亮",
14
+ "獅子座" => "太陽",
15
+ "處女座" => "水星",
16
+ "天秤座" => "金星",
17
+ "天蠍座" => "冥王星",
18
+ "射手座" => "木星",
19
+ "摩羯座" => "土星",
20
+ "水瓶座" => "天王星",
21
+ "雙魚座" => "海王星",
22
+ }.freeze
23
+
24
+ # Return zodiac sign name for a given ecliptic longitude.
25
+ def self.sign_name(degree)
26
+ index = (degree % 360).floor / 30
27
+ SIGNS[index]
28
+ end
29
+
30
+ # Return the ruling planet of a zodiac sign.
31
+ def self.ruler(zodiac)
32
+ r = RULERS[zodiac]
33
+ raise ArgumentError, "無效的星座名稱: #{zodiac}" if r.nil?
34
+ r
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,12 @@
1
+ require_relative "astro_chart/version"
2
+ require_relative "astro_chart/astro_chart_ext"
3
+ require_relative "astro_chart/ephemeris"
4
+ require_relative "astro_chart/zodiac"
5
+ require_relative "astro_chart/aspects"
6
+ require_relative "astro_chart/houses"
7
+ require_relative "astro_chart/time_conversion"
8
+ require_relative "astro_chart/planets"
9
+ require_relative "astro_chart/chart"
10
+
11
+ module AstroChart
12
+ end