astro_chart 0.4.0 → 0.5.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ddcb896268ae30564a7041611a7811b707a7547affa849de1d4498bf2e1553f
4
- data.tar.gz: 7773d2a228e636ad928e90f408fa36375e87bc0fc1a7745d6534a861269ea121
3
+ metadata.gz: 373643f3dc987aeb880aad6a15a5dd6a509367683c7ee1463ce7086b3e06980f
4
+ data.tar.gz: ec28b6b542ae7aaaa03997e744718b62dcf6a69c4372e1fcdb5196cb87110e4e
5
5
  SHA512:
6
- metadata.gz: cee34349b951431356fb2a776d40e0ede18cfeafbdb6564221aed0e80374a623a4ed58fab8de96bd6b99b98a7593518fd7f6a9737a508dd0aba0be12a128a749
7
- data.tar.gz: dff024c7b6f9dcfa20f5882eecc95ad99fcf0fc6381f187f2c072485694caabd094f107fd2a09865086700c24cfc72a7326b4b11dfffb800fd2ae3b03e284376
6
+ metadata.gz: e5a812f264e71b82bbe09489d7098254b29f0e7fb648c2eb84aa244091d7e62cebab9cfaa7420da59b44392227bc3dc1b110c1aaa6f6dbe02de60ab6a80e5a3a
7
+ data.tar.gz: 9c8e709366b24a5c28db55223c68edbfe1e705f9b9ec4d95f1e5dae1a6a72e6d8419597d900c80f832126b2bb53c930ddf636d4e7db93bc4ca7208346f8e8905
data/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.1 (2026-08-07)
4
+
5
+ **Ecliptic latitude → accurate astrocartography.** Additive.
6
+
7
+ - New `AstroChart::Ephemeris.ecliptic_latlon(jd, id)` — apparent geocentric
8
+ ecliptic [longitude, latitude] for the Sun, Moon, VSOP87 planets and Pluto
9
+ (the geocentric vector was already computed; latitude was simply discarded).
10
+ - `Astrocartography.lines` now forms RA/Dec from each body's **true** ecliptic
11
+ latitude instead of β = 0, so the Moon and Pluto lines (β up to ~5° / ~17°)
12
+ are placed correctly. The longitude methods delegate to the new latitude-
13
+ aware ones, so planetary longitudes remain byte-identical (Swiss oracle spec
14
+ still passes).
15
+
16
+ ## 0.5.0 (2026-08-07)
17
+
18
+ **Astrocartography (星象地圖).** All pure Ruby, additive.
19
+
20
+ - New `AstroChart::Astrocartography.lines(jd)` — the relocational map lines for
21
+ the ten bodies at a birth instant: the MC and IC meridian longitudes, and
22
+ the ASC (rising) and DSC (setting) horizon curves as latitude/longitude
23
+ segments (broken where a body is circumpolar). Built from the body's RA/Dec
24
+ and Greenwich apparent sidereal time; verified independently (body altitude
25
+ ≈ 0 along every ASC/DSC point, LST = RA on the MC). Ecliptic latitude is
26
+ approximated as 0 (exact for the Sun; largest error for the Moon/Pluto).
27
+
3
28
  ## 0.4.0 (2026-08-06)
4
29
 
5
30
  **Traditional dignities, transit timing, two more house systems, minor
@@ -0,0 +1,109 @@
1
+ require_relative "ephemeris"
2
+ require_relative "pure/core"
3
+
4
+ module AstroChart
5
+ # Astrocartography (星象地圖 / relocational lines).
6
+ #
7
+ # For a fixed birth instant, every body is angular (on the horizon or the
8
+ # meridian) along particular curves across the globe. We return, per body,
9
+ # four lines in geographic coordinates (longitude east-positive, −180..180):
10
+ #
11
+ # MC — the body culminates on the upper meridian (a vertical meridian).
12
+ # IC — the body is on the lower meridian (a vertical meridian).
13
+ # ASC — the body rises on the eastern horizon (a curve of latitude points).
14
+ # DSC — the body sets on the western horizon (a curve of latitude points).
15
+ #
16
+ # Method (Meeus, positional astronomy): from the body's apparent ecliptic
17
+ # longitude λ and the true obliquity ε we form its equatorial coordinates
18
+ # (RA α, Dec δ); with the Greenwich apparent sidereal time (GST):
19
+ # MC longitude = α − GST
20
+ # IC longitude = α − GST + 180°
21
+ # and at latitude φ the body is on the horizon when its hour angle is
22
+ # H = ±arccos(−tanφ·tanδ) (no solution ⇒ circumpolar, the line ends),
23
+ # rising at H = −H0 (eastern), setting at H = +H0 (western), giving
24
+ # longitude = α + H − GST.
25
+ #
26
+ # RA/Dec use each body's true apparent ecliptic latitude (via
27
+ # Ephemeris.ecliptic_latlon), so the lines are accurate for the high-latitude
28
+ # bodies (Moon, Pluto) too — not just longitude-only approximations.
29
+ module Astrocartography
30
+ Core = Pure::Core
31
+ DEG2RAD = Core::DEG2RAD
32
+ RAD2DEG = Core::RAD2DEG
33
+
34
+ # The ten bodies conventionally mapped.
35
+ BODIES = ["太陽", "月亮", "水星", "金星", "火星", "木星",
36
+ "土星", "天王星", "海王星", "冥王星"].freeze
37
+
38
+ # Latitude span and step for the rising/setting curves (degrees).
39
+ LAT_MIN = -75
40
+ LAT_MAX = 75
41
+ LAT_STEP = 1
42
+
43
+ module_function
44
+
45
+ # jd_ut: birth Julian Day (UT).
46
+ # Returns Array of:
47
+ # { "planet" => "太陽",
48
+ # "mc" => <lon>, "ic" => <lon>, # single geographic longitudes
49
+ # "asc" => [ [ [lat,lon], ... ], ... ], # rising curve, as segments
50
+ # "dsc" => [ [ [lat,lon], ... ], ... ] } # setting curve, as segments
51
+ def lines(jd_ut)
52
+ gst = Core.apparent_sidereal_deg(jd_ut)
53
+ eps = Core.true_obliquity(Core.jd_tt(jd_ut)) * DEG2RAD
54
+
55
+ BODIES.map do |name|
56
+ lam_deg, beta_deg = Ephemeris.ecliptic_latlon(jd_ut, Ephemeris::PLANETS[name])
57
+ lam = lam_deg * DEG2RAD
58
+ beta = beta_deg * DEG2RAD
59
+ # RA/Dec from apparent ecliptic longitude, latitude and obliquity
60
+ # (Meeus, Astronomical Algorithms, eqs. 13.3–13.4).
61
+ ra = Core.norm360(Math.atan2(
62
+ Math.sin(lam) * Math.cos(eps) - Math.tan(beta) * Math.sin(eps),
63
+ Math.cos(lam)
64
+ ) * RAD2DEG)
65
+ dec = Math.asin(Math.sin(beta) * Math.cos(eps) + Math.cos(beta) * Math.sin(eps) * Math.sin(lam))
66
+
67
+ {
68
+ "planet" => name,
69
+ "mc" => norm180(ra - gst),
70
+ "ic" => norm180(ra - gst + 180.0),
71
+ "asc" => horizon_curve(ra, dec, gst, :rising),
72
+ "dsc" => horizon_curve(ra, dec, gst, :setting),
73
+ }
74
+ end
75
+ end
76
+
77
+ # Segments of [lat, lon] points where the body is on the eastern (rising)
78
+ # or western (setting) horizon. A circumpolar latitude breaks the curve, so
79
+ # the result is an array of contiguous segments (each a polyline).
80
+ def horizon_curve(ra_deg, dec_rad, gst, mode)
81
+ tan_dec = Math.tan(dec_rad)
82
+ segments = []
83
+ current = []
84
+ lat = LAT_MIN.to_f
85
+ while lat <= LAT_MAX
86
+ x = -Math.tan(lat * DEG2RAD) * tan_dec
87
+ if x.abs <= 1.0
88
+ h0 = Math.acos(x) * RAD2DEG
89
+ hour = mode == :rising ? -h0 : h0
90
+ current << [lat.round(2), norm180(ra_deg + hour - gst)]
91
+ elsif !current.empty?
92
+ segments << current
93
+ current = []
94
+ end
95
+ lat += LAT_STEP
96
+ end
97
+ segments << current unless current.empty?
98
+ segments
99
+ end
100
+
101
+ # Normalize degrees to (−180, 180].
102
+ def norm180(deg)
103
+ d = Core.norm360(deg)
104
+ d > 180.0 ? d - 360.0 : d
105
+ end
106
+
107
+ private_class_method :horizon_curve, :norm180
108
+ end
109
+ end
@@ -102,6 +102,12 @@ module AstroChart
102
102
  end
103
103
  end
104
104
 
105
+ # Apparent geocentric ecliptic [longitude, latitude] in degrees. Pure
106
+ # backend only (the Swiss extension exposes latitude via its own API).
107
+ def self.ecliptic_latlon(jd, planet_id)
108
+ Pure.ecliptic_latlon(jd, planet_id)
109
+ end
110
+
105
111
  # Apparent daily motion in ecliptic longitude (degrees/day), via central
106
112
  # difference of calc_ut at jd ± 0.5 day. The difference is folded into
107
113
  # (-180, 180] so the 0°/360° wraparound never produces a spurious value.
@@ -182,10 +182,16 @@ module AstroChart
182
182
  # --- 月球視黃經(度,[0,360))。引數 **JD(UT)**。 ---
183
183
  # 幾何黃經(平春分點 of date)+ 章動 Δψ = 視黃經(真春分點 of date)。
184
184
  def apparent_longitude(jd_ut)
185
+ apparent_ecliptic(jd_ut)[0]
186
+ end
187
+
188
+ # Apparent geocentric ecliptic [longitude, latitude] in degrees. JD(UT).
189
+ # ELP-2000 gives β directly; nutation shifts longitude only.
190
+ def apparent_ecliptic(jd_ut)
185
191
  tt = Core.jd_tt(jd_ut)
186
- lam, _beta, _delta = geometric_position(tt)
192
+ lam, beta, = geometric_position(tt)
187
193
  dpsi_deg, = Core.nutation(tt)
188
- Core.norm360(lam + dpsi_deg)
194
+ [Core.norm360(lam + dpsi_deg), beta]
189
195
  end
190
196
 
191
197
  # --- 真(密切)北交點視黃經(度,[0,360))。引數 **JD(UT)**。 ---
@@ -94,6 +94,11 @@ module AstroChart
94
94
  # 冥王星視黃道經度(度,真春分點 of date)。
95
95
  # jd_ut: JD(UT)。超出 Meeus Ch. 37 適用範圍(1885–2099)raise DomainError。
96
96
  def apparent_longitude(jd_ut)
97
+ apparent_ecliptic(jd_ut)[0]
98
+ end
99
+
100
+ # Apparent geocentric ecliptic [longitude, latitude] in degrees. JD(UT).
101
+ def apparent_ecliptic(jd_ut)
97
102
  jd_tt = Core.jd_tt(jd_ut)
98
103
  unless jd_tt >= JD_TT_MIN && jd_tt < JD_TT_MAX
99
104
  raise Core::DomainError,
@@ -121,9 +126,11 @@ module AstroChart
121
126
  tau = new_tau
122
127
  end
123
128
 
124
- lambda_mean = Math.atan2(gy, gx) * RAD2DEG # 平春分點 of date
125
- dpsi, = Core.nutation(jd_tt)
126
- Core.norm360(lambda_mean + dpsi)
129
+ dpsi, = Core.nutation(jd_tt) # 平春分點 of date → 真春分點
130
+ [
131
+ Core.norm360(Math.atan2(gy, gx) * RAD2DEG + dpsi),
132
+ Math.asin(gz / Math.sqrt(gx * gx + gy * gy + gz * gz)) * RAD2DEG,
133
+ ]
127
134
  end
128
135
 
129
136
  # --- 冥王星日心直角座標(AU,黃道座標,旋轉到 jd_frame_tt 的平春分點) ---
@@ -61,6 +61,20 @@ module AstroChart
61
61
  end
62
62
  end
63
63
 
64
+ # Apparent geocentric ecliptic [longitude, latitude] in degrees. The Sun's
65
+ # geocentric ecliptic latitude is ~0 (well under 1″), returned as 0.0.
66
+ def apparent_ecliptic(planet_id, jd_ut)
67
+ tt = Core.jd_tt(jd_ut)
68
+ if planet_id == SUN_ID
69
+ [sun_apparent_longitude(tt), 0.0]
70
+ elsif PLANET_KEYS.key?(planet_id)
71
+ planet_apparent_ecliptic(PLANET_KEYS.fetch(planet_id), tt)
72
+ else
73
+ raise Core::DomainError,
74
+ "Vsop87 不支援 planet_id=#{planet_id}(僅太陽 0 與行星 2..8)"
75
+ end
76
+ end
77
+
64
78
  # --- 太陽(Meeus Ch. 25 高精度法) ---
65
79
  def sun_apparent_longitude(jd_tt)
66
80
  l, _b, r = heliocentric_fk5(:earth, jd_tt)
@@ -75,8 +89,14 @@ module AstroChart
75
89
  # 重算——兩者同取 t-τ 等效把光行時與(地球速度造成的)光行差
76
90
  # 一併涵蓋(planetary aberration;Meeus Ch. 33 註)。τ 迭代兩輪。
77
91
  def planet_apparent_longitude(planet_key, jd_tt)
92
+ planet_apparent_ecliptic(planet_key, jd_tt)[0]
93
+ end
94
+
95
+ # Geocentric apparent ecliptic [longitude, latitude] (degrees) for a VSOP87
96
+ # planet, via the light-time-corrected geocentric rectangular vector.
97
+ def planet_apparent_ecliptic(planet_key, jd_tt)
78
98
  tau = 0.0
79
- x = y = 0.0
99
+ x = y = z = 0.0
80
100
  2.times do
81
101
  ex, ey, ez = heliocentric_rect(:earth, jd_tt - tau)
82
102
  px, py, pz = heliocentric_rect(planet_key, jd_tt - tau)
@@ -86,9 +106,11 @@ module AstroChart
86
106
  delta = Math.sqrt(x * x + y * y + z * z)
87
107
  tau = LIGHT_TIME_DAYS_PER_AU * delta
88
108
  end
89
- lambda_deg = Math.atan2(y, x) * RAD2DEG
90
109
  dpsi_deg, = Core.nutation(jd_tt)
91
- Core.norm360(lambda_deg + dpsi_deg)
110
+ [
111
+ Core.norm360(Math.atan2(y, x) * RAD2DEG + dpsi_deg),
112
+ Math.asin(z / Math.sqrt(x * x + y * y + z * z)) * RAD2DEG,
113
+ ]
92
114
  end
93
115
 
94
116
  # --- 日心黃道直角座標(FK5 修正後;黃道與分點皆為 of date) ---
@@ -49,6 +49,23 @@ module AstroChart
49
49
  end
50
50
  end
51
51
 
52
+ # Apparent geocentric ecliptic [longitude, latitude] in degrees for the Sun,
53
+ # Moon, VSOP87 planets and Pluto (nodes are not physical bodies).
54
+ def ecliptic_latlon(jd_ut, planet_id)
55
+ case planet_id
56
+ when *VSOP87_PLANET_IDS
57
+ Vsop87.apparent_ecliptic(planet_id, jd_ut)
58
+ when MOON_ID
59
+ Moon.apparent_ecliptic(jd_ut)
60
+ when PLUTO_ID
61
+ Pluto.apparent_ecliptic(jd_ut)
62
+ else
63
+ raise ArgumentError,
64
+ "ecliptic_latlon unsupported planet_id #{planet_id.inspect} " \
65
+ "(supported: Sun 0, planets 2-8, Moon, Pluto)"
66
+ end
67
+ end
68
+
52
69
  # House cusps + ascendant + MC. Placidus ("P") and Whole Sign ("W").
53
70
  # hsys accepts "P"/"W" or 80/87 (ord values) to mirror the C extension's int argument.
54
71
  def houses(jd_ut, latitude, longitude, hsys = "P")
@@ -1,3 +1,3 @@
1
1
  module AstroChart
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.1"
3
3
  end
data/lib/astro_chart.rb CHANGED
@@ -20,6 +20,7 @@ require_relative "astro_chart/progressions"
20
20
  require_relative "astro_chart/composite"
21
21
  require_relative "astro_chart/solar_return"
22
22
  require_relative "astro_chart/draconic"
23
+ require_relative "astro_chart/astrocartography"
23
24
  require_relative "astro_chart/dignities"
24
25
  require_relative "astro_chart/profection"
25
26
  require_relative "astro_chart/transit_timing"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: astro_chart
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Huang Yudi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-06 00:00:00.000000000 Z
11
+ date: 2026-08-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tzinfo
@@ -69,6 +69,7 @@ files:
69
69
  - astro_chart.gemspec
70
70
  - lib/astro_chart.rb
71
71
  - lib/astro_chart/aspects.rb
72
+ - lib/astro_chart/astrocartography.rb
72
73
  - lib/astro_chart/chart.rb
73
74
  - lib/astro_chart/composite.rb
74
75
  - lib/astro_chart/dignities.rb