astro_chart 0.2.0 → 0.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +97 -0
- data/README.md +417 -0
- data/astro_chart.gemspec +8 -4
- data/lib/astro_chart/aspects.rb +31 -2
- data/lib/astro_chart/chart.rb +94 -12
- data/lib/astro_chart/composite.rb +101 -0
- data/lib/astro_chart/dignities.rb +220 -0
- data/lib/astro_chart/draconic.rb +64 -0
- data/lib/astro_chart/ephemeris.rb +78 -29
- data/lib/astro_chart/houses.rb +2 -1
- data/lib/astro_chart/lunar_return.rb +90 -0
- data/lib/astro_chart/patterns.rb +248 -0
- data/lib/astro_chart/points.rb +82 -0
- data/lib/astro_chart/profection.rb +56 -0
- data/lib/astro_chart/progressions.rb +68 -0
- data/lib/astro_chart/pure/houses.rb +91 -6
- data/lib/astro_chart/pure.rb +11 -6
- data/lib/astro_chart/solar_arc.rb +59 -0
- data/lib/astro_chart/solar_return.rb +193 -0
- data/lib/astro_chart/stats.rb +34 -0
- data/lib/astro_chart/transit_timing.rb +186 -0
- data/lib/astro_chart/transits.rb +108 -0
- data/lib/astro_chart/version.rb +1 -1
- data/lib/astro_chart.rb +13 -0
- metadata +24 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1ddcb896268ae30564a7041611a7811b707a7547affa849de1d4498bf2e1553f
|
|
4
|
+
data.tar.gz: 7773d2a228e636ad928e90f408fa36375e87bc0fc1a7745d6534a861269ea121
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cee34349b951431356fb2a776d40e0ede18cfeafbdb6564221aed0e80374a623a4ed58fab8de96bd6b99b98a7593518fd7f6a9737a508dd0aba0be12a128a749
|
|
7
|
+
data.tar.gz: dff024c7b6f9dcfa20f5882eecc95ad99fcf0fc6381f187f2c072485694caabd094f107fd2a09865086700c24cfc72a7326b4b11dfffb800fd2ae3b03e284376
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,102 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0 (2026-08-06)
|
|
4
|
+
|
|
5
|
+
**Traditional dignities, transit timing, two more house systems, minor
|
|
6
|
+
aspects, four new configurations, antiscia, draconic charts, solar arc,
|
|
7
|
+
lunar & annual predictive tools.** All pure Ruby, additive and
|
|
8
|
+
backward-compatible.
|
|
9
|
+
|
|
10
|
+
- **Minor aspects**: `Aspects.calculate(a, b, minor: true)` also matches
|
|
11
|
+
十二分相 (30°), 半四分相 (45°), 補八分相 (135°) and 補十二分相 (quincunx,
|
|
12
|
+
150°). Default stays major-only; minor aspects carry tighter orbs and never
|
|
13
|
+
shadow a major one.
|
|
14
|
+
- **New aspect patterns** in `Patterns.detect` (and every `Chart#generate`
|
|
15
|
+
output): 上帝之指 (Yod: sextile base + quincunx apex), 風箏 (Kite: a 大三角
|
|
16
|
+
plus an opposing/sextiling focal body, reported alongside the trine), 神祕
|
|
17
|
+
矩形 (Mystic Rectangle: two oppositions joined by sextiles and trines) and
|
|
18
|
+
星群 (Stellium: 3+ bodies in one sign). Existing 大三角/T三角/大十字 output is
|
|
19
|
+
unchanged; the node-axis exclusion rules extend to the new configurations.
|
|
20
|
+
- **Antiscia**: `Points.antiscion` (reflection across the 巨蟹–摩羯 solstice
|
|
21
|
+
axis) and `Points.contra_antiscion` (across the 牡羊–天秤 equinox axis).
|
|
22
|
+
- **Draconic charts (龍盤)**: new `AstroChart::Draconic` — `.positions` shifts
|
|
23
|
+
every longitude so the natal 北交點 sits at 0° 牡羊; `.chart` adds draconic
|
|
24
|
+
signs, in-sign degrees and inter-aspects.
|
|
25
|
+
- **Two more house systems**: Equal (`"E"`, from the ascendant) and Porphyry
|
|
26
|
+
(`"O"`, quadrant trisection), joining Placidus (`"P"`) and Whole Sign
|
|
27
|
+
(`"W"`). Both are pure geometry off the ASC/MC and are defined at every
|
|
28
|
+
latitude |lat| < 90° (including inside the polar circle, where Placidus
|
|
29
|
+
raises). Ord aliases 69/79 mirror the C-extension int argument.
|
|
30
|
+
- **Essential dignities (必然尊貴)**: new `AstroChart::Dignities` — 廟/旺/三分性/
|
|
31
|
+
界/外觀 with the traditional rulerships and Dorothean triplicities.
|
|
32
|
+
`.of(planet, longitude, sect:)` reports a planet's dignities/debilities and
|
|
33
|
+
Lilly-weighted score (+5/+4/+3/+2/+1); `.almuten(longitude, sect:)` returns
|
|
34
|
+
the winning planet at a degree; plus `.domicile_ruler`/`detriment_ruler`/
|
|
35
|
+
`exaltation_ruler`/`fall_ruler`/`triplicity_ruler`/`term_ruler`/`face_ruler`.
|
|
36
|
+
界 ships both 埃及界 (Egyptian, `scheme: :egyptian`, the default) and 托勒密界
|
|
37
|
+
(Ptolemaic, `scheme: :ptolemaic`); both are cross-validated against Astrolog
|
|
38
|
+
and the Egyptian table matches several independent references.
|
|
39
|
+
- **Annual profection (小限法)**: new `AstroChart::Profection` — `.annual(asc,
|
|
40
|
+
age)` and `.at(asc, birth_date, target_date)` give the profected house/sign
|
|
41
|
+
and Lord of the Year (年主星).
|
|
42
|
+
- **Transit timing (行運精確時點)**: new `AstroChart::TransitTiming.events` —
|
|
43
|
+
the exact UTC instants in a date range when a transiting body perfects an
|
|
44
|
+
aspect to a natal point, found by bracket-and-bisect on the ephemeris
|
|
45
|
+
(robust through retrograde stations, so all three passes of an outer-planet
|
|
46
|
+
contact are caught). `minor:`, `step_days:` and `bodies:` options.
|
|
47
|
+
- **Solar arc directions (太陽弧推運)**: new `AstroChart::SolarArc.directions`
|
|
48
|
+
— directs the whole chart rigidly by the secondary-progressed Sun's arc and
|
|
49
|
+
reports directed-to-natal aspects.
|
|
50
|
+
- **Lunar returns (月亮回歸)**: new `AstroChart::LunarReturn.for_date` — the
|
|
51
|
+
chart at the Moon's monthly return to its natal longitude, relocatable like
|
|
52
|
+
the solar return.
|
|
53
|
+
|
|
54
|
+
## 0.3.0 (2026-07-24)
|
|
55
|
+
|
|
56
|
+
**Predictive & comparison modules, whole-sign houses, retrograde flags,
|
|
57
|
+
derived points, pattern detection.** All pure Ruby, MIT.
|
|
58
|
+
|
|
59
|
+
- **Whole-sign houses**: `Chart.new(..., house_system: "W")` ("P" Placidus
|
|
60
|
+
remains the default; anything else raises `ArgumentError`). Whole-sign
|
|
61
|
+
works at all latitudes |lat| < 90° (including polar latitudes, where
|
|
62
|
+
Placidus still raises); Placidus output is byte-identical to 0.2.0.
|
|
63
|
+
`Ephemeris.houses` / `Pure.houses` accept `"W"` too.
|
|
64
|
+
- **Retrograde flags**: every planet entry in `Chart#generate` gains
|
|
65
|
+
`"retrograde" => true/false` (central-difference daily motion via the new
|
|
66
|
+
`Ephemeris.speed(jd, id)` / `Ephemeris.retrograde?(jd, id)`). 太陽/月亮
|
|
67
|
+
are hard-false, 南交點 mirrors 北交點, derived/ruler points are false.
|
|
68
|
+
Within half a day of a body's valid ephemeris window edge (e.g. the
|
|
69
|
+
Pluto series' 1885–2099 range) the speed stencil falls back to a
|
|
70
|
+
one-sided difference, so edge-of-range charts still generate.
|
|
71
|
+
- **Derived points** (`AstroChart::Points`): 福點 (Part of Fortune,
|
|
72
|
+
day/night formula; sect judged from the horizon — ASC–DSC axis — so it
|
|
73
|
+
is independent of the display house system) and 莉莉絲 (mean Black Moon
|
|
74
|
+
Lilith, Meeus mean lunar apogee + 180°, verified < 0.13° vs Swiss
|
|
75
|
+
Ephemeris across 1900–2050). Both appended to `Chart#generate` planets,
|
|
76
|
+
before the ruler points.
|
|
77
|
+
- **Transits** (`AstroChart::Transits`): `.at(jd)` sky snapshot;
|
|
78
|
+
`.against(natal, jd, orb_limit: 3.0)` places transiting bodies in natal
|
|
79
|
+
houses and lists transit-to-natal aspects sorted by orb.
|
|
80
|
+
- **Secondary progressions** (`AstroChart::Progressions`):
|
|
81
|
+
`.secondary(natal, "YYYY-MM-DD", orb_limit: 1.0)` — day-for-a-year
|
|
82
|
+
progressed positions, natal house placement, aspects to natal.
|
|
83
|
+
- **Composite charts** (`AstroChart::Composite`): `.between(a, b)` —
|
|
84
|
+
shorter-arc midpoint chart of two natal charts, with internal aspects.
|
|
85
|
+
- **Solar returns** (`AstroChart::SolarReturn`): `.for_year(natal, year,
|
|
86
|
+
latitude:, longitude:, timezone:)` — Newton-iterated exact return
|
|
87
|
+
instant, full relocated chart at that moment.
|
|
88
|
+
- **Aspect patterns** (`AstroChart::Patterns.detect`): 大三角 (with
|
|
89
|
+
element), T三角 (with apex), 大十字; node-axis oppositions excluded
|
|
90
|
+
from opposition legs; 大十字 subsumes its own T三角s; an opposition
|
|
91
|
+
squared by the node axis is reported as a single T三角 (apex 北交點)
|
|
92
|
+
rather than mirrored 北/南交點 twins. `Chart#generate` now includes
|
|
93
|
+
`"patterns"`.
|
|
94
|
+
- **Element statistics** (`AstroChart::Stats.elements`): 火土風水 element
|
|
95
|
+
and 基本固定變動 modality counts over the 10 classical planets.
|
|
96
|
+
`Chart#generate` now includes `"element_stats"` and `"house_system"`.
|
|
97
|
+
- `Houses.calculate` now takes the house system as a keyword
|
|
98
|
+
(`system: "P"`).
|
|
99
|
+
|
|
3
100
|
## 0.2.0 (2026-07-24)
|
|
4
101
|
|
|
5
102
|
**License change: AGPL-3.0 → MIT.**
|
data/README.md
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="logo.png" alt="AstroChart — Pure-Ruby astrology chart calculation" width="600">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# AstroChart
|
|
6
|
+
|
|
7
|
+
Pure-Ruby gem for astrology chart calculation: apparent planetary
|
|
8
|
+
longitudes, four house systems (Placidus, whole-sign, equal, Porphyry),
|
|
9
|
+
retrograde flags, major & minor aspects, derived points (福點/莉莉絲/映點),
|
|
10
|
+
aspect patterns, element statistics, essential dignities & almuten,
|
|
11
|
+
synastry, transits, transit timing, secondary progressions, solar arc
|
|
12
|
+
directions, composite & draconic charts, solar & lunar returns, and annual
|
|
13
|
+
profection.
|
|
14
|
+
|
|
15
|
+
**No C extension, no external data files, MIT licensed.** Implemented from
|
|
16
|
+
public formulas (Jean Meeus, *Astronomical Algorithms* 2nd ed.; VSOP87D;
|
|
17
|
+
ELP-2000/82B), verified against Swiss Ephemeris to < 0.014° (most bodies
|
|
18
|
+
< 0.001°) across 1900–2026 with zero zodiac sign flips.
|
|
19
|
+
|
|
20
|
+
## Hosted Service(線上服務)
|
|
21
|
+
|
|
22
|
+
A live web UI and open JSON API built on this gem:
|
|
23
|
+
|
|
24
|
+
- **Web 星盤查詢**: https://astro-chart-api.fly.dev/ (本命盤、合盤、行運、二次推運、組合盤、太陽回歸,繁體中文)
|
|
25
|
+
- **API 文件**: https://astro-chart-api.fly.dev/docs
|
|
26
|
+
- Currently in open beta: no API key, no rate limit, free. CORS enabled.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
curl -X POST https://astro-chart-api.fly.dev/api/v1/charts \
|
|
30
|
+
-H "Content-Type: application/json" \
|
|
31
|
+
-d '{"birth_date":"1990-01-01","birth_time":"12:00",
|
|
32
|
+
"latitude":25.033,"longitude":121.5654,"timezone":"Asia/Taipei"}'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Endpoints: `POST /api/v1/charts` (natal chart), `POST /api/v1/synastry`
|
|
36
|
+
(合盤), `POST /api/v1/transits` (行運), `POST /api/v1/progressions`
|
|
37
|
+
(二次推運), `POST /api/v1/composite` (組合盤), `POST /api/v1/solar-return`
|
|
38
|
+
(太陽回歸), `GET /api/v1/cities` (城市搜尋), `GET /api/v1/health`.
|
|
39
|
+
The hosted web app and MCP server live in a separate repository and depend
|
|
40
|
+
on `astro_chart` as a normal gem.
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
gem "astro_chart", "~> 0.3"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then `bundle install`. Nothing to compile.
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
require "astro_chart"
|
|
54
|
+
|
|
55
|
+
chart = AstroChart::Chart.new(
|
|
56
|
+
birth_date: "1990-01-01",
|
|
57
|
+
birth_time: "12:00",
|
|
58
|
+
latitude: 25.0330,
|
|
59
|
+
longitude: 121.5654,
|
|
60
|
+
timezone: "Asia/Taipei",
|
|
61
|
+
house_system: "P" # "P" Placidus (default) or "W" Whole Sign
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
result = chart.generate
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Return Value
|
|
68
|
+
|
|
69
|
+
`generate` returns a Hash with string keys:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
{
|
|
73
|
+
"input" => {
|
|
74
|
+
"birth_date" => "1990-01-01",
|
|
75
|
+
"birth_time" => "12:00",
|
|
76
|
+
"coordinates" => { "latitude" => 25.033, "longitude" => 121.5654 },
|
|
77
|
+
"timezone" => "Asia/Taipei"
|
|
78
|
+
},
|
|
79
|
+
"chart" => {
|
|
80
|
+
"ascendant" => {
|
|
81
|
+
"zodiac" => "牡羊座",
|
|
82
|
+
"degree" => 16.4422,
|
|
83
|
+
"total_degree" => 16.4422
|
|
84
|
+
},
|
|
85
|
+
"house_system" => "P",
|
|
86
|
+
"planets" => [
|
|
87
|
+
{
|
|
88
|
+
"planet" => "太陽",
|
|
89
|
+
"zodiac" => "摩羯座",
|
|
90
|
+
"house" => 9,
|
|
91
|
+
"degree" => 10.4744,
|
|
92
|
+
"total_degree" => 280.4744,
|
|
93
|
+
"retrograde" => false,
|
|
94
|
+
"aspects" => [
|
|
95
|
+
{ "planet" => "土星", "aspect_type" => "合相", "orb" => 5.14 }
|
|
96
|
+
]
|
|
97
|
+
},
|
|
98
|
+
# ... 月亮, 水星, 金星, 火星, 木星, 土星, 天王星, 海王星, 冥王星,
|
|
99
|
+
# 北交點, 南交點, 福點, 莉莉絲,
|
|
100
|
+
# 北交點定位星, 南交點定位星, 上升星座定位星 (17 entries)
|
|
101
|
+
],
|
|
102
|
+
"houses" => [
|
|
103
|
+
{ "house_number" => 1, "degree" => 16.4422, "zodiac" => "牡羊座" },
|
|
104
|
+
# ... 2-12
|
|
105
|
+
],
|
|
106
|
+
"patterns" => [
|
|
107
|
+
{ "pattern_type" => "T三角", "planets" => ["太陽", "月亮", "火星"],
|
|
108
|
+
"apex" => "火星" }
|
|
109
|
+
# 大三角 (with "element"), T三角/上帝之指/風箏 (with "apex"), 大十字,
|
|
110
|
+
# 神祕矩形, 星群 (with "zodiac")
|
|
111
|
+
],
|
|
112
|
+
"element_stats" => {
|
|
113
|
+
"elements" => { "火" => 1, "土" => 5, "風" => 2, "水" => 2 },
|
|
114
|
+
"modalities" => { "基本" => 6, "固定" => 3, "變動" => 1 }
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Planets Included
|
|
121
|
+
|
|
122
|
+
太陽, 月亮, 水星, 金星, 火星, 木星, 土星, 天王星, 海王星, 冥王星, 北交點(真交點), 南交點
|
|
123
|
+
|
|
124
|
+
Plus derived points 福點 (Part of Fortune) and 莉莉絲 (mean Black Moon
|
|
125
|
+
Lilith), and three ruler points: 北交點定位星, 南交點定位星, 上升星座定位星
|
|
126
|
+
— 17 entries total, in that order.
|
|
127
|
+
|
|
128
|
+
Every entry carries `"retrograde"` (central-difference daily motion;
|
|
129
|
+
太陽/月亮 and derived/ruler points are always `false`, 南交點 mirrors
|
|
130
|
+
北交點).
|
|
131
|
+
|
|
132
|
+
### House Systems
|
|
133
|
+
|
|
134
|
+
Four systems, via `Chart.new(..., house_system: ...)`:
|
|
135
|
+
|
|
136
|
+
| Code | System | Notes |
|
|
137
|
+
|------|--------|-------|
|
|
138
|
+
| `"P"` | Placidus (default) | time-based; undefined inside the polar circle (raises) |
|
|
139
|
+
| `"W"` | Whole Sign | cusps on sign boundaries from the ascendant's sign |
|
|
140
|
+
| `"E"` | Equal | cusp 1 = ASC, then +30°; the true MC is still reported |
|
|
141
|
+
| `"O"` | Porphyry | quadrant trisection between the four angles |
|
|
142
|
+
|
|
143
|
+
Any other value raises `ArgumentError`. `W`/`E`/`O` are pure ASC/MC geometry
|
|
144
|
+
and are defined at every latitude |lat| < 90° — including polar latitudes,
|
|
145
|
+
where Placidus is undefined. (`Ephemeris.houses` also accepts the ord aliases
|
|
146
|
+
`80`/`87`/`69`/`79`.)
|
|
147
|
+
|
|
148
|
+
### Aspects
|
|
149
|
+
|
|
150
|
+
Major: 合相 (0°, orb 15°), 六分相 (60°, orb 6°), 四分相 (90°, orb 8°), 三分相 (120°, orb 8°), 對分相 (180°, orb 10°).
|
|
151
|
+
|
|
152
|
+
Minor (opt-in via `Aspects.calculate(a, b, minor: true)`): 十二分相 (30°), 半四分相 (45°), 補八分相 (135°), 補十二分相/quincunx (150°) — tighter orbs, and they never shadow a major aspect.
|
|
153
|
+
|
|
154
|
+
### Synastry (合盤)
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
result = AstroChart::Synastry.between(chart_a, chart_b, orb_limit: 6.0)
|
|
158
|
+
|
|
159
|
+
result["aspects"]
|
|
160
|
+
#=> [{ "a_planet" => "太陽", "b_planet" => "月亮",
|
|
161
|
+
# "aspect_type" => "三分相", "orb" => 1.23 }, ...] # sorted by orb
|
|
162
|
+
|
|
163
|
+
result["a_planets_in_b_houses"] #=> { "太陽" => 7, ... } (house overlay)
|
|
164
|
+
result["b_planets_in_a_houses"] #=> { "月亮" => 12, ... }
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Lower-level: `Synastry.cross_aspects(positions_a, positions_b)` and
|
|
168
|
+
`Synastry.house_overlay(positions, cusps)` work on raw longitude hashes.
|
|
169
|
+
|
|
170
|
+
### Transits (行運)
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
jd = AstroChart::TimeConversion.to_julian_day("2026-07-24", "12:00", "Asia/Taipei")
|
|
174
|
+
|
|
175
|
+
AstroChart::Transits.at(jd)
|
|
176
|
+
#=> { "太陽" => 121.9, ... } # sky snapshot, 12 bodies
|
|
177
|
+
|
|
178
|
+
result = AstroChart::Transits.against(natal_chart, jd, orb_limit: 3.0)
|
|
179
|
+
result["planets"] # 12 transiting bodies with "natal_house"
|
|
180
|
+
result["aspects"] # [{ "transit_planet" => "土星", "natal_planet" => "太陽",
|
|
181
|
+
# "aspect_type" => "四分相", "orb" => 1.23 }, ...] sorted by orb
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Secondary Progressions (二次推運)
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
result = AstroChart::Progressions.secondary(natal_chart, "2026-07-24", orb_limit: 1.0)
|
|
188
|
+
result["progressed_jd"] # day-for-a-year progressed Julian Day
|
|
189
|
+
result["years_elapsed"] # e.g. 36.56
|
|
190
|
+
result["planets"] # progressed positions + natal house placement
|
|
191
|
+
result["aspects_to_natal"] # [{ "progressed_planet" => ..., "natal_planet" => ... }]
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Composite Charts (組合盤)
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
result = AstroChart::Composite.between(chart_a, chart_b)
|
|
198
|
+
result["planets"] # shorter-arc midpoints of the 12 bodies
|
|
199
|
+
result["aspects"] # aspects among the composite positions, sorted by orb
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
(No composite houses — midpoint houses are not well-defined.)
|
|
203
|
+
|
|
204
|
+
### Solar Returns (太陽回歸)
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
result = AstroChart::SolarReturn.for_year(natal_chart, 2026)
|
|
208
|
+
result["return_jd"] # exact JD (UT) the Sun returns to its natal longitude
|
|
209
|
+
result["return_time_utc"] # "2026-07-03T05:12:34Z"
|
|
210
|
+
result["chart"] # full chart at that instant (relocate with
|
|
211
|
+
# latitude:/longitude:/timezone: overrides)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Transit Timing (行運精確時點)
|
|
215
|
+
|
|
216
|
+
The exact UTC instants, within a date range, when a transiting body perfects
|
|
217
|
+
an aspect to a natal point — the "when" that `Transits.against` (a snapshot)
|
|
218
|
+
can't give:
|
|
219
|
+
|
|
220
|
+
```ruby
|
|
221
|
+
events = AstroChart::TransitTiming.events(natal_chart, "2026-01-01", "2026-12-31")
|
|
222
|
+
events.first
|
|
223
|
+
#=> { "transit_planet" => "土星", "natal_planet" => "太陽",
|
|
224
|
+
# "aspect_type" => "四分相", "jd" => 2461...,
|
|
225
|
+
# "time_utc" => "2026-03-14T07:22:10Z", "transit_zodiac" => "牡羊座",
|
|
226
|
+
# "retrograde" => false }
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Found by bracket-and-bisect on the ephemeris, so it is robust through
|
|
230
|
+
retrograde stations — all three passes of an outer-planet contact are caught.
|
|
231
|
+
Options: `minor:` (also time the minor aspects), `step_days:` (sampling
|
|
232
|
+
stride, default 1.0), `bodies:` (restrict transiting bodies, e.g. drop `"月亮"`
|
|
233
|
+
to avoid the Moon's monthly hits). Events are sorted by time.
|
|
234
|
+
|
|
235
|
+
### Solar Arc Directions (太陽弧推運)
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
result = AstroChart::SolarArc.directions(natal_chart, "2026-07-24")
|
|
239
|
+
result["arc"] # degrees the chart is directed (~1° per year of life)
|
|
240
|
+
result["planets"] # every natal point advanced rigidly by the arc
|
|
241
|
+
result["aspects_to_natal"] # directed→natal aspects, sorted by orb
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
The arc is the secondary-progressed Sun's travel since birth; unlike secondary
|
|
245
|
+
progressions, the whole chart moves at that single rate.
|
|
246
|
+
|
|
247
|
+
### Lunar Returns (月亮回歸)
|
|
248
|
+
|
|
249
|
+
```ruby
|
|
250
|
+
result = AstroChart::LunarReturn.for_date(natal_chart, "2026-07-24")
|
|
251
|
+
result["return_jd"] # JD (UT) of the lunar return nearest the date
|
|
252
|
+
result["return_time_utc"] # "2026-07-31T09:54:22Z"
|
|
253
|
+
result["chart"] # full chart at that instant (relocatable)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The monthly (~27.32-day) analogue of the solar return.
|
|
257
|
+
|
|
258
|
+
### Draconic Charts (龍盤)
|
|
259
|
+
|
|
260
|
+
```ruby
|
|
261
|
+
positions = AstroChart::Planets.calculate_positions(jd) # or any { name => longitude }
|
|
262
|
+
AstroChart::Draconic.positions(positions, north_node) # shift so 北交點 = 0° 牡羊
|
|
263
|
+
AstroChart::Draconic.chart(positions, north_node)
|
|
264
|
+
#=> { "planets" => [{ "planet" =>, "zodiac" =>, "degree" =>, "total_degree" => }...],
|
|
265
|
+
# "aspects" => [...] } # draconic signs + inter-aspects
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Essential Dignities (必然尊貴)
|
|
269
|
+
|
|
270
|
+
Traditional 廟/旺/三分性/界/外觀 with the traditional rulerships, Dorothean
|
|
271
|
+
triplicities, and both Egyptian and Ptolemaic terms. Pure lookup tables.
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
D = AstroChart::Dignities
|
|
275
|
+
|
|
276
|
+
D.of("火星", 5.0) # 火星 at 5° 牡羊
|
|
277
|
+
#=> { "planet" => "火星", "dignities" => ["廟", "外觀"],
|
|
278
|
+
# "debilities" => [], "score" => 6 }
|
|
279
|
+
|
|
280
|
+
D.almuten(5.0, sect: :day) #=> { "planet" => "太陽", "score" => 7, "tied" => ["太陽"] }
|
|
281
|
+
|
|
282
|
+
D.term_ruler(13.0, scheme: :ptolemaic) #=> "金星" (:egyptian is the default)
|
|
283
|
+
D.domicile_ruler(215) #=> "火星" D.triplicity_ruler(5, sect: :night) #=> "木星"
|
|
284
|
+
D.face_ruler(120) #=> "土星" D.exaltation_ruler(5) #=> "太陽"
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Dignities score +5/+4/+3/+2/+1 (廟/旺/三分性/界/外觀); debilities 陷 (detriment),
|
|
288
|
+
弱 (fall). Rulers are the seven traditional planets (dignity theory predates
|
|
289
|
+
the outer planets), so these differ from `Zodiac.ruler`'s modern rulerships.
|
|
290
|
+
|
|
291
|
+
### Annual Profection (小限法)
|
|
292
|
+
|
|
293
|
+
```ruby
|
|
294
|
+
AstroChart::Profection.annual(ascendant_longitude, 36)
|
|
295
|
+
#=> { "age" => 36, "profected_house" => 1, "profected_sign" => "牡羊座",
|
|
296
|
+
# "year_lord" => "火星" }
|
|
297
|
+
|
|
298
|
+
AstroChart::Profection.at(ascendant_longitude, "1990-01-01", "2026-08-05")
|
|
299
|
+
# derives the age from the two dates, then profects
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Each completed year advances one whole sign from the ascendant; the
|
|
303
|
+
traditional ruler of the profected sign is the Lord of the Year (年主星).
|
|
304
|
+
|
|
305
|
+
### Patterns & Element Stats
|
|
306
|
+
|
|
307
|
+
Included in `Chart#generate` output, or usable standalone on any
|
|
308
|
+
`{ name => longitude }` hash:
|
|
309
|
+
|
|
310
|
+
```ruby
|
|
311
|
+
AstroChart::Patterns.detect(positions)
|
|
312
|
+
#=> [{ "pattern_type" => "大三角", "planets" => [...], "element" => "火" },
|
|
313
|
+
# { "pattern_type" => "T三角", "planets" => [...], "apex" => "火星" },
|
|
314
|
+
# { "pattern_type" => "大十字", "planets" => [...] },
|
|
315
|
+
# { "pattern_type" => "上帝之指", "planets" => [...], "apex" => "水星" }, # Yod
|
|
316
|
+
# { "pattern_type" => "風箏", "planets" => [...], "apex" => "月亮" }, # Kite
|
|
317
|
+
# { "pattern_type" => "神祕矩形", "planets" => [...] }, # Mystic Rectangle
|
|
318
|
+
# { "pattern_type" => "星群", "planets" => [...], "zodiac" => "牡羊座" }] # Stellium
|
|
319
|
+
|
|
320
|
+
AstroChart::Stats.elements(positions)
|
|
321
|
+
#=> { "elements" => { "火" => 3, "土" => 2, "風" => 3, "水" => 2 },
|
|
322
|
+
# "modalities" => { "基本" => 4, "固定" => 3, "變動" => 3 } }
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Derived Points
|
|
326
|
+
|
|
327
|
+
```ruby
|
|
328
|
+
AstroChart::Points.lilith(jd) #=> 216.47 (mean Black Moon Lilith, 0-360)
|
|
329
|
+
AstroChart::Points.fortune(asc: asc, sun: sun, moon: moon, day_chart: true)
|
|
330
|
+
AstroChart::Points.day_chart?(sun_longitude, cusps) #=> true / false / nil
|
|
331
|
+
AstroChart::Points.day_chart_from_horizon?(sun_longitude, ascendant)
|
|
332
|
+
# Chart#generate determines 福點 sect from the horizon (ASC-DSC axis),
|
|
333
|
+
# so the same birth instant yields the same 福點 under "P" and "W".
|
|
334
|
+
|
|
335
|
+
# 映點 / 反映點 (antiscia / contra-antiscia)
|
|
336
|
+
AstroChart::Points.antiscion(15.0) #=> 165.0 (across the 巨蟹–摩羯 axis)
|
|
337
|
+
AstroChart::Points.contra_antiscion(15.0) #=> 345.0 (across the 牡羊–天秤 axis)
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Individual Modules
|
|
341
|
+
|
|
342
|
+
```ruby
|
|
343
|
+
# Zodiac sign from ecliptic longitude
|
|
344
|
+
AstroChart::Zodiac.sign_name(280.5) #=> "摩羯座"
|
|
345
|
+
AstroChart::Zodiac.ruler("摩羯座") #=> "土星"
|
|
346
|
+
|
|
347
|
+
# Aspect between two positions
|
|
348
|
+
AstroChart::Aspects.calculate(0, 90) #=> ["四分相", 0.0]
|
|
349
|
+
|
|
350
|
+
# Julian Day conversion
|
|
351
|
+
jd = AstroChart::TimeConversion.to_julian_day("1990-01-01", "12:00", "Asia/Taipei")
|
|
352
|
+
|
|
353
|
+
# Raw ephemeris access (planet ids follow the SE convention)
|
|
354
|
+
AstroChart::Ephemeris.julday(2000, 1, 1, 12.0)
|
|
355
|
+
AstroChart::Ephemeris.calc_ut(jd, AstroChart::Ephemeris::PLANETS["太陽"])
|
|
356
|
+
AstroChart::Ephemeris.houses(jd, 25.033, 121.565) # Placidus
|
|
357
|
+
AstroChart::Ephemeris.houses(jd, 25.033, 121.565, "W") # Whole Sign
|
|
358
|
+
AstroChart::Ephemeris.houses(jd, 25.033, 121.565, "E") # Equal
|
|
359
|
+
AstroChart::Ephemeris.houses(jd, 25.033, 121.565, "O") # Porphyry
|
|
360
|
+
|
|
361
|
+
# Daily motion / retrograde detection
|
|
362
|
+
AstroChart::Ephemeris.speed(jd, 2) #=> degrees/day (negative = retrograde)
|
|
363
|
+
AstroChart::Ephemeris.retrograde?(jd, 2) #=> true / false
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Backends
|
|
367
|
+
|
|
368
|
+
The default backend is `:pure` (pure Ruby, always available). The legacy
|
|
369
|
+
Swiss Ephemeris C extension backend can still be selected **if you compile
|
|
370
|
+
and provide the extension yourself** — it is no longer shipped with this gem
|
|
371
|
+
(it is AGPL-licensed, see 0.1.x):
|
|
372
|
+
|
|
373
|
+
```ruby
|
|
374
|
+
AstroChart.backend #=> :pure
|
|
375
|
+
AstroChart.backend = :swiss # raises LoadError unless the extension is present
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## Accuracy & Limits
|
|
379
|
+
|
|
380
|
+
- Verified against Swiss Ephemeris (Moshier), 1900–2026, 400 samples per
|
|
381
|
+
body: Sun ≤ 0.0002°, Moon ≤ 0.0013°, planets ≤ 0.0006°, Pluto ≤ 0.0005°,
|
|
382
|
+
true node ≤ 0.014°, house cusps ≤ 0.0003°. Zero zodiac sign flips.
|
|
383
|
+
- 莉莉絲 (mean Black Moon Lilith) uses the Meeus mean lunar perigee
|
|
384
|
+
polynomial + 180°; verified ≤ 0.13° vs Swiss Ephemeris across 1900–2050.
|
|
385
|
+
- Whole-sign cusps verified identical to Swiss Ephemeris, including inside
|
|
386
|
+
the polar circle. Retrograde flags verified against Swiss central-difference
|
|
387
|
+
speeds (Mercury 2020–2024, every sample matches).
|
|
388
|
+
- Transit / progression / solar-return positions verified within 0.003° of
|
|
389
|
+
Swiss Ephemeris at sampled instants (1962–2033).
|
|
390
|
+
- Pluto series is valid 1885–2099 (raises outside this range).
|
|
391
|
+
- **House systems: Placidus (`"P"`), Whole Sign (`"W"`), Equal (`"E"`) and
|
|
392
|
+
Porphyry (`"O"`)** on the pure backend. Other systems raise `ArgumentError`.
|
|
393
|
+
Polar latitudes (|lat| ≳ 66.5°) raise `AstroChart::Pure::Core::DomainError`
|
|
394
|
+
on Placidus — it is undefined there; use whole-sign, equal or Porphyry for
|
|
395
|
+
polar charts. Porphyry shares the four angles with Placidus and trisects
|
|
396
|
+
each quadrant; verified against those invariants.
|
|
397
|
+
|
|
398
|
+
## Geocoding
|
|
399
|
+
|
|
400
|
+
This gem does **not** handle geocoding. Pass latitude, longitude, and
|
|
401
|
+
timezone directly. For city-to-coordinate conversion, use the
|
|
402
|
+
[geocoder](https://github.com/alexreisner/geocoder) gem or your own lookup
|
|
403
|
+
table.
|
|
404
|
+
|
|
405
|
+
## Development
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
bundle install
|
|
409
|
+
rake spec
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
The hosted web app and MCP server that build on this gem live in a
|
|
413
|
+
separate repository and depend on `astro_chart` as a normal gem.
|
|
414
|
+
|
|
415
|
+
## License
|
|
416
|
+
|
|
417
|
+
MIT
|
data/astro_chart.gemspec
CHANGED
|
@@ -4,16 +4,20 @@ Gem::Specification.new do |spec|
|
|
|
4
4
|
spec.name = "astro_chart"
|
|
5
5
|
spec.version = AstroChart::VERSION
|
|
6
6
|
spec.authors = ["Huang Yudi"]
|
|
7
|
-
spec.summary = "Pure-Ruby
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
spec.summary = "Pure-Ruby astrology chart calculation (planets, Placidus/whole-sign houses, " \
|
|
8
|
+
"aspects, synastry, transits, progressions, composite, solar returns)"
|
|
9
|
+
spec.description = "Astrology chart calculation in pure Ruby: apparent planetary longitudes " \
|
|
10
|
+
"(VSOP87D, ELP-2000/82B moon, Meeus Pluto), Placidus and whole-sign houses, " \
|
|
11
|
+
"retrograde flags, aspects, derived points (Part of Fortune, mean Lilith), " \
|
|
12
|
+
"aspect-pattern detection and element statistics, plus synastry, transits, " \
|
|
13
|
+
"secondary progressions, composite charts and solar returns. " \
|
|
10
14
|
"No C extension, no external data files. Implemented from public formulas " \
|
|
11
15
|
"(Meeus, Astronomical Algorithms 2nd ed.)."
|
|
12
16
|
spec.homepage = "https://github.com/morriedig/astro_chart"
|
|
13
17
|
spec.license = "MIT"
|
|
14
18
|
spec.required_ruby_version = ">= 3.0"
|
|
15
19
|
|
|
16
|
-
spec.files = Dir["lib/**/*.rb", "LICENSE", "CHANGELOG.md", "astro_chart.gemspec"]
|
|
20
|
+
spec.files = Dir["lib/**/*.rb", "README.md", "LICENSE", "CHANGELOG.md", "astro_chart.gemspec"]
|
|
17
21
|
spec.require_paths = ["lib"]
|
|
18
22
|
|
|
19
23
|
spec.add_dependency "tzinfo", "~> 2.0"
|
data/lib/astro_chart/aspects.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module AstroChart
|
|
2
2
|
module Aspects
|
|
3
|
-
# Major aspects: angle => [name, max_orb]
|
|
3
|
+
# Major aspects (托勒密相位): angle => [name, max_orb]
|
|
4
4
|
MAJOR = {
|
|
5
5
|
60 => ["六分相", 6], # Sextile
|
|
6
6
|
90 => ["四分相", 8], # Square
|
|
@@ -8,11 +8,31 @@ module AstroChart
|
|
|
8
8
|
180 => ["對分相", 10], # Opposition
|
|
9
9
|
}.freeze
|
|
10
10
|
|
|
11
|
+
# Minor aspects (次要相位): angle => [name, max_orb]. Opt-in via
|
|
12
|
+
# `minor: true` — they carry tighter orbs and are checked only after the
|
|
13
|
+
# major set, so no minor aspect can shadow a major one (the angle bands
|
|
14
|
+
# do not overlap given these orbs).
|
|
15
|
+
#
|
|
16
|
+
# 30° 十二分相 (semi-sextile)
|
|
17
|
+
# 45° 半四分相 (semi-square)
|
|
18
|
+
# 135° 補八分相 (sesquiquadrate)
|
|
19
|
+
# 150° 補十二分相 (quincunx / inconjunct)
|
|
20
|
+
MINOR = {
|
|
21
|
+
30 => ["十二分相", 2],
|
|
22
|
+
45 => ["半四分相", 2],
|
|
23
|
+
135 => ["補八分相", 2],
|
|
24
|
+
150 => ["補十二分相", 3],
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
11
27
|
CONJUNCTION_ORB = 15
|
|
12
28
|
|
|
13
29
|
# Calculate the aspect between two ecliptic positions.
|
|
30
|
+
#
|
|
31
|
+
# By default only conjunction + the four major aspects are considered
|
|
32
|
+
# (backward-compatible). Pass `minor: true` to also match the minor set.
|
|
33
|
+
#
|
|
14
34
|
# Returns [aspect_name, orb] or [nil, nil].
|
|
15
|
-
def self.calculate(pos1, pos2)
|
|
35
|
+
def self.calculate(pos1, pos2, minor: false)
|
|
16
36
|
return [nil, nil] if pos1.nil? || pos2.nil?
|
|
17
37
|
|
|
18
38
|
p1 = pos1 % 360.0
|
|
@@ -34,6 +54,15 @@ module AstroChart
|
|
|
34
54
|
end
|
|
35
55
|
end
|
|
36
56
|
|
|
57
|
+
if minor
|
|
58
|
+
MINOR.each do |angle, (name, max_orb)|
|
|
59
|
+
orb = (diff - angle).abs
|
|
60
|
+
if orb <= max_orb
|
|
61
|
+
return [name, orb.round(2)]
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
37
66
|
[nil, nil]
|
|
38
67
|
end
|
|
39
68
|
end
|