astro_chart 0.1.0 → 0.2.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 +25 -0
- data/LICENSE +17 -16
- data/astro_chart.gemspec +7 -6
- data/lib/astro_chart/ephemeris.rb +76 -18
- data/lib/astro_chart/pure/core.rb +209 -0
- data/lib/astro_chart/pure/houses.rb +122 -0
- data/lib/astro_chart/pure/moon.rb +341 -0
- data/lib/astro_chart/pure/moon_elp.rb +931 -0
- data/lib/astro_chart/pure/pluto.rb +220 -0
- data/lib/astro_chart/pure/vsop87.rb +152 -0
- data/lib/astro_chart/pure/vsop87_data.rb +210 -0
- data/lib/astro_chart/pure.rb +63 -0
- data/lib/astro_chart/synastry.rb +118 -0
- data/lib/astro_chart/version.rb +1 -1
- data/lib/astro_chart.rb +6 -1
- metadata +19 -45
- data/ext/astro_chart/astro_chart_ext.c +0 -99
- data/ext/astro_chart/extconf.rb +0 -8
- data/ext/astro_chart/swecl.c +0 -6428
- data/ext/astro_chart/swedate.c +0 -588
- data/ext/astro_chart/swedate.h +0 -81
- data/ext/astro_chart/swedll.h +0 -403
- data/ext/astro_chart/sweephe4.c +0 -702
- data/ext/astro_chart/sweephe4.h +0 -239
- data/ext/astro_chart/swehel.c +0 -3511
- data/ext/astro_chart/swehouse.c +0 -3143
- data/ext/astro_chart/swehouse.h +0 -98
- data/ext/astro_chart/swejpl.c +0 -958
- data/ext/astro_chart/swejpl.h +0 -103
- data/ext/astro_chart/swemmoon.c +0 -1930
- data/ext/astro_chart/swemplan.c +0 -967
- data/ext/astro_chart/swemptab.h +0 -10640
- data/ext/astro_chart/swenut2000a.h +0 -2819
- data/ext/astro_chart/sweodef.h +0 -326
- data/ext/astro_chart/sweph.c +0 -8614
- data/ext/astro_chart/sweph.h +0 -849
- data/ext/astro_chart/swephexp.h +0 -1020
- data/ext/astro_chart/swephlib.c +0 -4634
- data/ext/astro_chart/swephlib.h +0 -189
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 203914237dcef72fb6ebc852749cf78811321155c0a509c0c8f711c1646cf7da
|
|
4
|
+
data.tar.gz: 87661219e2c6f29aa8b9330a5af0a4408ce65d342899a3e085ea3dee9a9f43f2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 81b2fd730e85708ecb9a9264bbfbd7bee4d6ea848c3554b12f4f98303a6f1f9432e7713e071191193035facb0a46f3c886efc1d7767c27711eb9384990766c6c
|
|
7
|
+
data.tar.gz: bd6534cf3b77dcd81e9b800a343cc594a08e48bfbc8419024b897e59af9f0beef41c4c6d3bdc01d1011c7568659c8322c9b71ff3f2f1cefb8c2931eb4e17aca3
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 (2026-07-24)
|
|
4
|
+
|
|
5
|
+
**License change: AGPL-3.0 → MIT.**
|
|
6
|
+
|
|
7
|
+
- New pure-Ruby ephemeris backend, now the default (`AstroChart.backend == :pure`):
|
|
8
|
+
- Planets: VSOP87D truncated series (Sun, Mercury–Neptune)
|
|
9
|
+
- Moon & true node: ELP-2000/82B truncated series (CDS VizieR VI/79)
|
|
10
|
+
- Pluto: Meeus Astronomical Algorithms Ch. 37 series (valid 1885–2099)
|
|
11
|
+
- Placidus houses, ΔT (Espenak & Meeus), IAU 1980 nutation
|
|
12
|
+
- Verified against Swiss Ephemeris 1900–2026: all bodies within
|
|
13
|
+
0.014° (most < 0.001°), zero zodiac sign flips
|
|
14
|
+
- New `AstroChart::Synastry`: cross-chart aspects + house overlay
|
|
15
|
+
- **Removed** the bundled Swiss Ephemeris C extension (AGPL). The
|
|
16
|
+
`:swiss` backend now raises `LoadError` unless you compile and provide
|
|
17
|
+
the extension yourself (it is no longer shipped with this gem).
|
|
18
|
+
- Pure backend supports Placidus ("P") houses only; polar latitudes
|
|
19
|
+
raise `AstroChart::Pure::Core::DomainError` explicitly.
|
|
20
|
+
- Public interface unchanged: `Ephemeris.julday / calc_ut / houses`,
|
|
21
|
+
`Chart#generate` output structure identical.
|
|
22
|
+
|
|
23
|
+
## 0.1.0
|
|
24
|
+
|
|
25
|
+
- Initial release (Swiss Ephemeris C extension, AGPL-3.0).
|
data/LICENSE
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
Version 3, 19 November 2007
|
|
1
|
+
MIT License
|
|
3
2
|
|
|
4
|
-
Copyright (
|
|
3
|
+
Copyright (c) 2025-2026 Huang Yudi
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
-
GNU Affero General Public License for more details.
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/astro_chart.gemspec
CHANGED
|
@@ -4,19 +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 = "
|
|
8
|
-
spec.description = "
|
|
7
|
+
spec.summary = "Pure-Ruby natal chart calculation (planets, Placidus houses, aspects, synastry)"
|
|
8
|
+
spec.description = "Natal astrology chart calculation in pure Ruby: apparent planetary longitudes " \
|
|
9
|
+
"(VSOP87D, ELP-2000/82B moon, Meeus Pluto), Placidus houses, aspects and synastry. " \
|
|
10
|
+
"No C extension, no external data files. Implemented from public formulas " \
|
|
11
|
+
"(Meeus, Astronomical Algorithms 2nd ed.)."
|
|
9
12
|
spec.homepage = "https://github.com/morriedig/astro_chart"
|
|
10
|
-
spec.license = "
|
|
13
|
+
spec.license = "MIT"
|
|
11
14
|
spec.required_ruby_version = ">= 3.0"
|
|
12
15
|
|
|
13
|
-
spec.files = Dir["lib/**/*.rb", "
|
|
16
|
+
spec.files = Dir["lib/**/*.rb", "LICENSE", "CHANGELOG.md", "astro_chart.gemspec"]
|
|
14
17
|
spec.require_paths = ["lib"]
|
|
15
|
-
spec.extensions = ["ext/astro_chart/extconf.rb"]
|
|
16
18
|
|
|
17
19
|
spec.add_dependency "tzinfo", "~> 2.0"
|
|
18
20
|
|
|
19
21
|
spec.add_development_dependency "rspec", "~> 3.0"
|
|
20
22
|
spec.add_development_dependency "rake", "~> 13.0"
|
|
21
|
-
spec.add_development_dependency "rake-compiler", "~> 1.2"
|
|
22
23
|
end
|
|
@@ -1,36 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module AstroChart
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
DEFAULT_BACKEND = :pure
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
# Current ephemeris backend (:pure or :swiss). Defaults to :pure.
|
|
8
|
+
def backend
|
|
9
|
+
@backend ||= DEFAULT_BACKEND
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Switch the ephemeris backend.
|
|
13
|
+
#
|
|
14
|
+
# AstroChart.backend = :pure # pure-Ruby (default, no C extension needed)
|
|
15
|
+
# AstroChart.backend = :swiss # Swiss Ephemeris C extension (AGPL)
|
|
16
|
+
#
|
|
17
|
+
# Selecting :swiss loads the C extension on demand; a missing/uncompiled
|
|
18
|
+
# extension raises LoadError with an explicit message instead of failing
|
|
19
|
+
# silently at call time.
|
|
20
|
+
def backend=(name)
|
|
21
|
+
case name
|
|
22
|
+
when :pure
|
|
23
|
+
@backend = :pure
|
|
24
|
+
when :swiss
|
|
25
|
+
load_swiss_extension!
|
|
26
|
+
@backend = :swiss
|
|
27
|
+
else
|
|
28
|
+
raise ArgumentError,
|
|
29
|
+
"unknown backend #{name.inspect} (expected :pure or :swiss)"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def load_swiss_extension!
|
|
34
|
+
return if defined?(AstroChart::Ext)
|
|
35
|
+
|
|
36
|
+
begin
|
|
37
|
+
require_relative "astro_chart_ext"
|
|
38
|
+
rescue LoadError => e
|
|
39
|
+
raise LoadError,
|
|
40
|
+
"AstroChart :swiss backend requires the compiled Swiss Ephemeris C extension " \
|
|
41
|
+
"(astro_chart_ext). Build it with `rake compile` (or `ruby ext/astro_chart/extconf.rb && make`), " \
|
|
42
|
+
"or use the default pure-Ruby backend (AstroChart.backend = :pure). " \
|
|
43
|
+
"Original error: #{e.message}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Backend-agnostic ephemeris facade. All public callers go through this
|
|
49
|
+
# module so the backend (:pure / :swiss) can be swapped without touching
|
|
50
|
+
# the rest of the code.
|
|
5
51
|
module Ephemeris
|
|
52
|
+
# SE-convention planet ids (numeric literals so the :pure default
|
|
53
|
+
# works without the C extension loaded; values match AstroChart::Ext
|
|
54
|
+
# constants when the extension is present).
|
|
6
55
|
PLANETS = {
|
|
7
|
-
"太陽" =>
|
|
8
|
-
"月亮" =>
|
|
9
|
-
"水星" =>
|
|
10
|
-
"金星" =>
|
|
11
|
-
"火星" =>
|
|
12
|
-
"木星" =>
|
|
13
|
-
"土星" =>
|
|
14
|
-
"天王星" =>
|
|
15
|
-
"海王星" =>
|
|
16
|
-
"冥王星" =>
|
|
17
|
-
"北交點" =>
|
|
56
|
+
"太陽" => 0, # SUN
|
|
57
|
+
"月亮" => 1, # MOON
|
|
58
|
+
"水星" => 2, # MERCURY
|
|
59
|
+
"金星" => 3, # VENUS
|
|
60
|
+
"火星" => 4, # MARS
|
|
61
|
+
"木星" => 5, # JUPITER
|
|
62
|
+
"土星" => 6, # SATURN
|
|
63
|
+
"天王星" => 7, # URANUS
|
|
64
|
+
"海王星" => 8, # NEPTUNE
|
|
65
|
+
"冥王星" => 9, # PLUTO
|
|
66
|
+
"北交點" => 11, # TRUE_NODE
|
|
18
67
|
}.freeze
|
|
19
68
|
|
|
20
69
|
# Convert date/time to Julian Day number.
|
|
21
70
|
def self.julday(year, month, day, hour)
|
|
22
|
-
|
|
71
|
+
case AstroChart.backend
|
|
72
|
+
when :swiss then Ext.julday(year, month, day, hour)
|
|
73
|
+
else Pure.julday(year, month, day, hour)
|
|
74
|
+
end
|
|
23
75
|
end
|
|
24
76
|
|
|
25
|
-
# Calculate planet ecliptic longitude (degrees 0-360).
|
|
77
|
+
# Calculate planet apparent ecliptic longitude (degrees 0-360).
|
|
26
78
|
def self.calc_ut(jd, planet_id)
|
|
27
|
-
|
|
79
|
+
case AstroChart.backend
|
|
80
|
+
when :swiss then Ext.calc_ut(jd, planet_id)
|
|
81
|
+
else Pure.calc_ut(jd, planet_id)
|
|
82
|
+
end
|
|
28
83
|
end
|
|
29
84
|
|
|
30
85
|
# Calculate house cusps + ascendant.
|
|
31
86
|
# Returns { "cusps" => [12 floats], "ascendant" => float, "mc" => float }
|
|
32
87
|
def self.houses(jd, latitude, longitude, system = "P")
|
|
33
|
-
|
|
88
|
+
case AstroChart.backend
|
|
89
|
+
when :swiss then Ext.houses(jd, latitude, longitude, system.ord)
|
|
90
|
+
else Pure.houses(jd, latitude, longitude, system)
|
|
91
|
+
end
|
|
34
92
|
end
|
|
35
93
|
end
|
|
36
94
|
end
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AstroChart
|
|
4
|
+
module Pure
|
|
5
|
+
# Core — MIT-safe 純 Ruby 天文基建(零外部依賴)。
|
|
6
|
+
#
|
|
7
|
+
# 只使用公開文獻公式,不參考 Swiss Ephemeris C 原始碼:
|
|
8
|
+
# - 儒略日:Jean Meeus, "Astronomical Algorithms" 2nd ed., Ch. 7 (eq. 7.1)
|
|
9
|
+
# - ΔT(TT−UT):Espenak & Meeus 多項式擬合
|
|
10
|
+
# (NASA Eclipse website, "Polynomial Expressions for Delta T (ΔT)",
|
|
11
|
+
# Five Millennium Canon of Solar Eclipses: -1999 to +3000)
|
|
12
|
+
# - 章動(Δψ / Δε):Meeus Ch. 22(IAU 1980 序列截斷,振幅最大 13 項)
|
|
13
|
+
# - 平黃赤交角:Meeus eq. 22.2
|
|
14
|
+
# - 平恆星時 GMST:Meeus Ch. 12 (eq. 12.4,等同 IAU 1982 GMST)
|
|
15
|
+
# - 視恆星時 = GMST + Δψ·cos(ε_true)(Meeus Ch. 12)
|
|
16
|
+
#
|
|
17
|
+
# 時間尺度慣例(呼叫者必須遵守,弄錯會差 ~ΔT ≈ 70s → 月亮 ~0.01°):
|
|
18
|
+
# - julday / gmst_deg / apparent_sidereal_deg 吃 **JD(UT)**
|
|
19
|
+
# - nutation / mean_obliquity / true_obliquity 吃 **JD(TT)**(先過 jd_tt 換算)
|
|
20
|
+
#
|
|
21
|
+
# 公開 API(module_function,皆回傳 Float 或 [Float, Float]):
|
|
22
|
+
# Core.julday(year, month, day, hour) -> JD(UT);hour 為十進位 UT 小時
|
|
23
|
+
# Core.delta_t(jd_ut) -> ΔT 秒(TT − UT)
|
|
24
|
+
# Core.jd_tt(jd_ut) -> JD(TT) = jd_ut + ΔT/86400
|
|
25
|
+
# Core.nutation(jd_tt) -> [Δψ_deg, Δε_deg](度)
|
|
26
|
+
# Core.mean_obliquity(jd_tt) -> ε₀(度)
|
|
27
|
+
# Core.true_obliquity(jd_tt) -> ε = ε₀ + Δε(度)
|
|
28
|
+
# Core.gmst_deg(jd_ut) -> 格林威治平恆星時(度,[0,360))
|
|
29
|
+
# Core.apparent_sidereal_deg(jd_ut) -> 格林威治視恆星時(度,[0,360))
|
|
30
|
+
# Core.norm360(deg) -> 正規化到 [0, 360)
|
|
31
|
+
# Core::DomainError -> 超出適用範圍時 raise(顯性失敗)
|
|
32
|
+
module Core
|
|
33
|
+
DEG2RAD = Math::PI / 180.0
|
|
34
|
+
RAD2DEG = 180.0 / Math::PI
|
|
35
|
+
|
|
36
|
+
# 超出公式適用範圍或演算法未收斂時丟出——失敗必須顯性,
|
|
37
|
+
# 不可靜默回傳一個看似正常、實則錯誤的值。
|
|
38
|
+
class DomainError < StandardError; end
|
|
39
|
+
|
|
40
|
+
module_function
|
|
41
|
+
|
|
42
|
+
# --- 儒略日(Meeus 7.1,Gregorian) ---
|
|
43
|
+
# hour 為十進位 UT 小時(例 1.5 = 01:30 UT)
|
|
44
|
+
def julday(year, month, day, hour)
|
|
45
|
+
y = year
|
|
46
|
+
m = month
|
|
47
|
+
if m <= 2
|
|
48
|
+
y -= 1
|
|
49
|
+
m += 12
|
|
50
|
+
end
|
|
51
|
+
a = (y / 100.0).floor
|
|
52
|
+
b = 2 - a + (a / 4.0).floor
|
|
53
|
+
(365.25 * (y + 4716)).floor + (30.6001 * (m + 1)).floor +
|
|
54
|
+
day + hour / 24.0 + b - 1524.5
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# --- ΔT = TT − UT(秒) ---
|
|
58
|
+
# Espenak & Meeus 分段多項式(NASA Eclipse website,涵蓋 -1999..+3000)。
|
|
59
|
+
# 決策年 y 用儒略年近似(y = 2000 + (JD-J2000)/365.25),與 E&M 定義的
|
|
60
|
+
# y = year + (month-0.5)/12 差 < 半個月,對逐年平滑的 ΔT 曲線影響 < 0.01s。
|
|
61
|
+
# 注意:2005 之後為預測值,與實測(IERS)可差數秒 → 月亮 ~0.001°,
|
|
62
|
+
# 在本專案 0.01° 精度預算內。
|
|
63
|
+
def delta_t(jd_ut)
|
|
64
|
+
y = 2000.0 + (jd_ut - 2451545.0) / 365.25
|
|
65
|
+
|
|
66
|
+
if y < -500.0
|
|
67
|
+
u = (y - 1820.0) / 100.0
|
|
68
|
+
-20.0 + 32.0 * u * u
|
|
69
|
+
elsif y < 500.0
|
|
70
|
+
u = y / 100.0
|
|
71
|
+
10583.6 - 1014.41 * u + 33.78311 * u**2 - 5.952053 * u**3 -
|
|
72
|
+
0.1798452 * u**4 + 0.022174192 * u**5 + 0.0090316521 * u**6
|
|
73
|
+
elsif y < 1600.0
|
|
74
|
+
u = (y - 1000.0) / 100.0
|
|
75
|
+
1574.2 - 556.01 * u + 71.23472 * u**2 + 0.319781 * u**3 -
|
|
76
|
+
0.8503463 * u**4 - 0.005050998 * u**5 + 0.0083572073 * u**6
|
|
77
|
+
elsif y < 1700.0
|
|
78
|
+
t = y - 1600.0
|
|
79
|
+
120.0 - 0.9808 * t - 0.01532 * t**2 + t**3 / 7129.0
|
|
80
|
+
elsif y < 1800.0
|
|
81
|
+
t = y - 1700.0
|
|
82
|
+
8.83 + 0.1603 * t - 0.0059285 * t**2 + 0.00013336 * t**3 - t**4 / 1_174_000.0
|
|
83
|
+
elsif y < 1860.0
|
|
84
|
+
t = y - 1800.0
|
|
85
|
+
13.72 - 0.332447 * t + 0.0068612 * t**2 + 0.0041116 * t**3 -
|
|
86
|
+
0.00037436 * t**4 + 0.0000121272 * t**5 - 0.0000001699 * t**6 +
|
|
87
|
+
0.000000000875 * t**7
|
|
88
|
+
elsif y < 1900.0
|
|
89
|
+
t = y - 1860.0
|
|
90
|
+
7.62 + 0.5737 * t - 0.251754 * t**2 + 0.01680668 * t**3 -
|
|
91
|
+
0.0004473624 * t**4 + t**5 / 233_174.0
|
|
92
|
+
elsif y < 1920.0
|
|
93
|
+
t = y - 1900.0
|
|
94
|
+
-2.79 + 1.494119 * t - 0.0598939 * t**2 + 0.0061966 * t**3 - 0.000197 * t**4
|
|
95
|
+
elsif y < 1941.0
|
|
96
|
+
t = y - 1920.0
|
|
97
|
+
21.20 + 0.84493 * t - 0.076100 * t**2 + 0.0020936 * t**3
|
|
98
|
+
elsif y < 1961.0
|
|
99
|
+
t = y - 1950.0
|
|
100
|
+
29.07 + 0.407 * t - t**2 / 233.0 + t**3 / 2547.0
|
|
101
|
+
elsif y < 1986.0
|
|
102
|
+
t = y - 1975.0
|
|
103
|
+
45.45 + 1.067 * t - t**2 / 260.0 - t**3 / 718.0
|
|
104
|
+
elsif y < 2005.0
|
|
105
|
+
t = y - 2000.0
|
|
106
|
+
63.86 + 0.3345 * t - 0.060374 * t**2 + 0.0017275 * t**3 +
|
|
107
|
+
0.000651814 * t**4 + 0.00002373599 * t**5
|
|
108
|
+
elsif y < 2050.0
|
|
109
|
+
t = y - 2000.0
|
|
110
|
+
62.92 + 0.32217 * t + 0.005589 * t**2
|
|
111
|
+
elsif y < 2150.0
|
|
112
|
+
-20.0 + 32.0 * ((y - 1820.0) / 100.0)**2 - 0.5628 * (2150.0 - y)
|
|
113
|
+
else
|
|
114
|
+
u = (y - 1820.0) / 100.0
|
|
115
|
+
-20.0 + 32.0 * u * u
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# --- JD(TT) = JD(UT) + ΔT/86400 ---
|
|
120
|
+
def jd_tt(jd_ut)
|
|
121
|
+
jd_ut + delta_t(jd_ut) / 86_400.0
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# --- 章動(Meeus Ch. 22,IAU 1980 截斷序列) ---
|
|
125
|
+
# 係數單位:0.0001 角秒。取振幅最大的 13 項(Δψ 主項 -17.1996" sinΩ 起),
|
|
126
|
+
# 截斷誤差 < 0.0004",遠小於 0.01° 目標。
|
|
127
|
+
# 各項引數為 [D, M, M', F, Ω] 的整數倍組合(Meeus Table 22.A 前段)。
|
|
128
|
+
NUTATION_TERMS = [
|
|
129
|
+
# [D, M, Mp, F, Om, psi_sin, psi_sin_t, eps_cos, eps_cos_t]
|
|
130
|
+
[0, 0, 0, 0, 1, -171_996.0, -174.2, 92_025.0, 8.9],
|
|
131
|
+
[-2, 0, 0, 2, 2, -13_187.0, -1.6, 5736.0, -3.1],
|
|
132
|
+
[0, 0, 0, 2, 2, -2274.0, -0.2, 977.0, -0.5],
|
|
133
|
+
[0, 0, 0, 0, 2, 2062.0, 0.2, -895.0, 0.5],
|
|
134
|
+
[0, 1, 0, 0, 0, 1426.0, -3.4, 54.0, -0.1],
|
|
135
|
+
[0, 0, 1, 0, 0, 712.0, 0.1, -7.0, 0.0],
|
|
136
|
+
[-2, 1, 0, 2, 2, -517.0, 1.2, 224.0, -0.6],
|
|
137
|
+
[0, 0, 0, 2, 1, -386.0, -0.4, 200.0, 0.0],
|
|
138
|
+
[0, 0, 1, 2, 2, -301.0, 0.0, 129.0, -0.1],
|
|
139
|
+
[-2, -1, 0, 2, 2, 217.0, -0.5, -95.0, 0.3],
|
|
140
|
+
[-2, 0, 1, 0, 0, -158.0, 0.0, 0.0, 0.0],
|
|
141
|
+
[-2, 0, 0, 2, 1, 129.0, 0.1, -70.0, 0.0],
|
|
142
|
+
[0, 0, -1, 2, 2, 123.0, 0.0, -53.0, 0.0]
|
|
143
|
+
].freeze
|
|
144
|
+
|
|
145
|
+
# 回傳 [Δψ_deg, Δε_deg](度)。引數時間尺度為 **TT**。
|
|
146
|
+
def nutation(jd_tt)
|
|
147
|
+
t = (jd_tt - 2451545.0) / 36525.0
|
|
148
|
+
|
|
149
|
+
# 基本引數(Meeus Ch. 22,度)
|
|
150
|
+
d = 297.85036 + 445_267.111480 * t - 0.0019142 * t * t + t**3 / 189_474.0
|
|
151
|
+
m = 357.52772 + 35_999.050340 * t - 0.0001603 * t * t - t**3 / 300_000.0
|
|
152
|
+
mp = 134.96298 + 477_198.867398 * t + 0.0086972 * t * t + t**3 / 56_250.0
|
|
153
|
+
f = 93.27191 + 483_202.017538 * t - 0.0036825 * t * t + t**3 / 327_270.0
|
|
154
|
+
om = 125.04452 - 1934.136261 * t + 0.0020708 * t * t + t**3 / 450_000.0
|
|
155
|
+
|
|
156
|
+
dpsi_01as = 0.0 # 單位 0.0001"
|
|
157
|
+
deps_01as = 0.0
|
|
158
|
+
NUTATION_TERMS.each do |cd, cm, cmp, cf, com, ps, pst, ec, ect|
|
|
159
|
+
arg = (cd * d + cm * m + cmp * mp + cf * f + com * om) * DEG2RAD
|
|
160
|
+
dpsi_01as += (ps + pst * t) * Math.sin(arg)
|
|
161
|
+
deps_01as += (ec + ect * t) * Math.cos(arg)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
[dpsi_01as * 0.0001 / 3600.0, deps_01as * 0.0001 / 3600.0]
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# --- 平黃赤交角(Meeus 22.2,度)。引數時間尺度為 **TT**。 ---
|
|
168
|
+
# 23°26'21.448" - 46.8150"T - 0.00059"T² + 0.001813"T³
|
|
169
|
+
def mean_obliquity(jd_tt)
|
|
170
|
+
t = (jd_tt - 2451545.0) / 36525.0
|
|
171
|
+
23.0 + 26.0 / 60.0 + 21.448 / 3600.0 -
|
|
172
|
+
(46.8150 * t + 0.00059 * t * t - 0.001813 * t**3) / 3600.0
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# --- 真黃赤交角 ε = ε₀ + Δε(度)。引數時間尺度為 **TT**。 ---
|
|
176
|
+
def true_obliquity(jd_tt)
|
|
177
|
+
mean_obliquity(jd_tt) + nutation(jd_tt)[1]
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# --- 平恆星時 GMST(Meeus 12.4 / IAU 1982),回傳度。引數為 **UT**。 ---
|
|
181
|
+
def gmst_deg(jd_ut)
|
|
182
|
+
t = (jd_ut - 2451545.0) / 36525.0
|
|
183
|
+
norm360(
|
|
184
|
+
280.46061837 +
|
|
185
|
+
360.98564736629 * (jd_ut - 2451545.0) +
|
|
186
|
+
0.000387933 * t * t -
|
|
187
|
+
t * t * t / 38_710_000.0
|
|
188
|
+
)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# --- 視恆星時 = GMST + Δψ·cos(ε_true)(度)。引數為 **UT**。 ---
|
|
192
|
+
# 章動與交角的引數在內部換算為 TT。
|
|
193
|
+
def apparent_sidereal_deg(jd_ut)
|
|
194
|
+
tt = jd_tt(jd_ut)
|
|
195
|
+
dpsi_deg, = nutation(tt)
|
|
196
|
+
eps_rad = true_obliquity(tt) * DEG2RAD
|
|
197
|
+
norm360(gmst_deg(jd_ut) + dpsi_deg * Math.cos(eps_rad))
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# --- 正規化到 [0, 360) ---
|
|
201
|
+
def norm360(deg)
|
|
202
|
+
deg %= 360.0
|
|
203
|
+
# Ruby 的 float % 對正模數永不為負,但極小負數(如 -1e-16 % 360.0)
|
|
204
|
+
# 會因浮點捨入得到恰好 360.0,違反 [0, 360) 契約 → 下游 floor(deg/30) 會得 12。
|
|
205
|
+
deg >= 360.0 ? 0.0 : deg
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "core"
|
|
4
|
+
|
|
5
|
+
module AstroChart
|
|
6
|
+
module Pure
|
|
7
|
+
# Houses — MIT-safe 純 Ruby Placidus 宮位(ASC / MC / 12 宮頭)。
|
|
8
|
+
#
|
|
9
|
+
# 零外部依賴、不參考 Swiss Ephemeris C 原始碼,僅使用公開文獻公式:
|
|
10
|
+
# - 視恆星時 / 章動 / 黃赤交角:見 Core(Meeus Ch. 12 / 22)
|
|
11
|
+
# - ASC / MC:標準球面天文公式(Meeus Ch. 13 座標轉換推得)
|
|
12
|
+
# - Placidus 宮頭:古典半弧迭代法(半日弧 SA = arccos(-tanφ·tanδ),
|
|
13
|
+
# 宮頭 11/12 位於 RAMC 東側 1/3、2/3 半日弧處;宮頭 2/3 以半夜弧對應)
|
|
14
|
+
#
|
|
15
|
+
# 公開 API:
|
|
16
|
+
# Houses.calc(jd_ut, lat, lon)
|
|
17
|
+
# -> { "cusps" => [12 floats], "ascendant" => Float, "mc" => Float }
|
|
18
|
+
# 欄位與 AstroChart::Ext.houses(jd, lat, lon, "P".ord) 相同。
|
|
19
|
+
#
|
|
20
|
+
# 適用範圍:|lat| < ~66°(Placidus 在極圈內無定義)。
|
|
21
|
+
# 超出範圍或迭代未收斂時 raise Core::DomainError(顯性失敗,不靜默 clamp)。
|
|
22
|
+
module Houses
|
|
23
|
+
DEG2RAD = Core::DEG2RAD
|
|
24
|
+
RAD2DEG = Core::RAD2DEG
|
|
25
|
+
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
# jd_ut: UT 儒略日;lat: 地理緯度(北正);lon: 地理經度(東正)
|
|
29
|
+
def calc(jd_ut, lat, lon)
|
|
30
|
+
tt = Core.jd_tt(jd_ut)
|
|
31
|
+
eps = Core.true_obliquity(tt) * DEG2RAD # 真黃赤交角(弧度)
|
|
32
|
+
ast_deg = Core.apparent_sidereal_deg(jd_ut) # 視恆星時(度)
|
|
33
|
+
armc = Core.norm360(ast_deg + lon) # 天頂赤經 RAMC
|
|
34
|
+
|
|
35
|
+
phi = lat * DEG2RAD
|
|
36
|
+
mc = mc_longitude(armc, eps)
|
|
37
|
+
asc = asc_longitude(armc, eps, phi)
|
|
38
|
+
|
|
39
|
+
# Placidus 半弧迭代:宮頭 11、12(地平上,RAMC 東側)與 2、3(地平下)
|
|
40
|
+
c11 = placidus_cusp(armc, eps, phi, 30.0, 1.0 / 3.0, :diurnal)
|
|
41
|
+
c12 = placidus_cusp(armc, eps, phi, 60.0, 2.0 / 3.0, :diurnal)
|
|
42
|
+
c2 = placidus_cusp(armc, eps, phi, 120.0, 2.0 / 3.0, :nocturnal)
|
|
43
|
+
c3 = placidus_cusp(armc, eps, phi, 150.0, 1.0 / 3.0, :nocturnal)
|
|
44
|
+
|
|
45
|
+
cusps = Array.new(12)
|
|
46
|
+
cusps[0] = asc # 第 1 宮 = ASC
|
|
47
|
+
cusps[1] = c2
|
|
48
|
+
cusps[2] = c3
|
|
49
|
+
cusps[3] = Core.norm360(mc + 180.0) # 第 4 宮 = MC 對沖
|
|
50
|
+
cusps[4] = Core.norm360(c11 + 180.0)
|
|
51
|
+
cusps[5] = Core.norm360(c12 + 180.0)
|
|
52
|
+
cusps[6] = Core.norm360(asc + 180.0) # 第 7 宮 = ASC 對沖
|
|
53
|
+
cusps[7] = Core.norm360(c2 + 180.0)
|
|
54
|
+
cusps[8] = Core.norm360(c3 + 180.0)
|
|
55
|
+
cusps[9] = mc # 第 10 宮 = MC
|
|
56
|
+
cusps[10] = c11
|
|
57
|
+
cusps[11] = c12
|
|
58
|
+
|
|
59
|
+
{ "cusps" => cusps, "ascendant" => asc, "mc" => mc }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# --- MC:天頂赤經 → 黃道經度 ---
|
|
63
|
+
# tan λ_MC = tan(ARMC) / cos ε,以 atan2 處理象限
|
|
64
|
+
def mc_longitude(armc_deg, eps)
|
|
65
|
+
ra = armc_deg * DEG2RAD
|
|
66
|
+
Core.norm360(Math.atan2(Math.sin(ra), Math.cos(ra) * Math.cos(eps)) * RAD2DEG)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# --- ASC:標準公式 ---
|
|
70
|
+
# λ_ASC = atan2( cos ARMC, -(sin ARMC · cos ε + tan φ · sin ε) )
|
|
71
|
+
def asc_longitude(armc_deg, eps, phi)
|
|
72
|
+
ra = armc_deg * DEG2RAD
|
|
73
|
+
Core.norm360(
|
|
74
|
+
Math.atan2(
|
|
75
|
+
Math.cos(ra),
|
|
76
|
+
-(Math.sin(ra) * Math.cos(eps) + Math.tan(phi) * Math.sin(eps))
|
|
77
|
+
) * RAD2DEG
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# --- Placidus 宮頭半弧迭代 ---
|
|
82
|
+
# 定義:黃道上一點 (α, δ),半日弧 SA_d = arccos(-tanφ·tanδ)、半夜弧 SA_n = arccos(tanφ·tanδ)。
|
|
83
|
+
# 宮頭 11:α = RAMC + (1/3)·SA_d 宮頭 12:α = RAMC + (2/3)·SA_d
|
|
84
|
+
# 宮頭 2 :α = RAMC + 180 - (2/3)·SA_n 宮頭 3 :α = RAMC + 180 - (1/3)·SA_n
|
|
85
|
+
# 黃道點的赤緯由 tan δ = tan ε · sin α 得出,固定點迭代至收斂。
|
|
86
|
+
# offset_deg:初值偏移(赤道情形的精確解);frac:半弧比例;mode:地平上/下。
|
|
87
|
+
def placidus_cusp(armc_deg, eps, phi, offset_deg, frac, mode)
|
|
88
|
+
tan_phi = Math.tan(phi)
|
|
89
|
+
tan_eps = Math.tan(eps)
|
|
90
|
+
ra = (armc_deg + offset_deg) * DEG2RAD
|
|
91
|
+
armc_rad = armc_deg * DEG2RAD
|
|
92
|
+
|
|
93
|
+
converged = false
|
|
94
|
+
60.times do
|
|
95
|
+
dec = Math.atan(tan_eps * Math.sin(ra)) # 黃道點赤緯
|
|
96
|
+
x = tan_phi * Math.tan(dec)
|
|
97
|
+
if x.abs > 1.0
|
|
98
|
+
# 極圈內該點無升落,Placidus 無定義。顯性失敗,不靜默 clamp。
|
|
99
|
+
raise Core::DomainError,
|
|
100
|
+
"Placidus 在此緯度無定義(|tanφ·tanδ| = #{x.abs.round(4)} > 1,lat=#{(phi * RAD2DEG).round(4)}°)"
|
|
101
|
+
end
|
|
102
|
+
new_ra =
|
|
103
|
+
if mode == :diurnal
|
|
104
|
+
armc_rad + frac * Math.acos(-x)
|
|
105
|
+
else
|
|
106
|
+
armc_rad + Math::PI - frac * Math.acos(x)
|
|
107
|
+
end
|
|
108
|
+
step = (new_ra - ra).abs
|
|
109
|
+
ra = new_ra # 先採納本次結果,收斂時回傳的才是最終值
|
|
110
|
+
if step < 1e-9 * DEG2RAD
|
|
111
|
+
converged = true
|
|
112
|
+
break
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
raise Core::DomainError, "Placidus 宮頭迭代 60 次未收斂" unless converged
|
|
116
|
+
|
|
117
|
+
# 赤經 → 黃道經度:tan λ = tan α / cos ε
|
|
118
|
+
Core.norm360(Math.atan2(Math.sin(ra), Math.cos(ra) * Math.cos(eps)) * RAD2DEG)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|