@orrery/core 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.
- package/README.md +221 -0
- package/dist/chunk-6PJQHSY6.js +292 -0
- package/dist/chunk-DGSIRAXF.js +698 -0
- package/dist/chunk-JSBRDJBE.js +30 -0
- package/dist/chunk-KD6TCTRW.js +12084 -0
- package/dist/chunk-OCPJGMZC.js +331 -0
- package/dist/chunk-TRSIZZ73.js +81 -0
- package/dist/chunk-VJDUZB5T.js +870 -0
- package/dist/cities.d.ts +19 -0
- package/dist/cities.js +15 -0
- package/dist/constants.d.ts +57 -0
- package/dist/constants.js +105 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +137 -0
- package/dist/natal.d.ts +29 -0
- package/dist/natal.js +31 -0
- package/dist/pillars.d.ts +67 -0
- package/dist/pillars.js +42 -0
- package/dist/saju.d.ts +6 -0
- package/dist/saju.js +9 -0
- package/dist/types.d.ts +249 -0
- package/dist/types.js +0 -0
- package/dist/ziwei.d.ts +13 -0
- package/dist/ziwei.js +12 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# @orrery/core
|
|
2
|
+
|
|
3
|
+
브라우저/Node.js 환경에서 동작하는 동양·서양 점술 계산 엔진입니다.
|
|
4
|
+
|
|
5
|
+
- **사주팔자(四柱八字)** — 60갑자, 십신, 12운성, 12신살, 대운, 간지 관계 분석
|
|
6
|
+
- **자미두수(紫微斗數)** — 명반(命盤) 생성, 대한(大限), 유년(流年)/유월(流月) 분석
|
|
7
|
+
- **서양 점성술 출생차트(Natal Chart)** — 행성 위치, 하우스, 앵글, 애스펙트 (Swiss Ephemeris WASM)
|
|
8
|
+
|
|
9
|
+
백엔드 불필요. 모든 계산이 클라이언트에서 수행됩니다.
|
|
10
|
+
|
|
11
|
+
**[라이브 데모 →](https://rath.github.io/orrery/)**
|
|
12
|
+
|
|
13
|
+
## 크레딧
|
|
14
|
+
|
|
15
|
+
- **사주 만세력** — 고영창님의 Perl [진짜만세력](http://afnmp3.homeip.net/~kohyc/calendar/cal20000.html)를 김정균님이 [PHP로 포팅](https://github.com/OOPS-ORG-PHP/Lunar)한 것을, 2018년 11월 황장호가 Java와 Python으로 재포팅하여 사용해오다가, 2026년 2월 Claude Code (Opus 4.6)로 TypeScript로 포팅
|
|
16
|
+
- **자미두수 명반** — [lunar-javascript](https://www.npmjs.com/package/lunar-javascript) 라이브러리 기반으로 Claude (Opus 4.5)가 중국어 문헌을 리서치하며 구현
|
|
17
|
+
- **점성술 출생차트** — [Swiss Ephemeris](https://github.com/arturania/swisseph)를 WASM으로 빌드한 [swisseph-wasm](https://www.npmjs.com/package/swisseph-wasm) 기반
|
|
18
|
+
|
|
19
|
+
## 설치
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @orrery/core
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
서양 점성술(natal) 기능을 사용하려면 `swisseph-wasm`도 설치하세요:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install swisseph-wasm
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> `swisseph-wasm`은 optional peer dependency입니다. 사주/자미두수만 사용한다면 설치하지 않아도 됩니다.
|
|
32
|
+
|
|
33
|
+
## 사용법
|
|
34
|
+
|
|
35
|
+
### 사주팔자 (四柱八字)
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { calculateSaju } from '@orrery/core/saju'
|
|
39
|
+
import type { BirthInput } from '@orrery/core/types'
|
|
40
|
+
|
|
41
|
+
const input: BirthInput = {
|
|
42
|
+
year: 1993, month: 3, day: 12,
|
|
43
|
+
hour: 9, minute: 45,
|
|
44
|
+
gender: 'M',
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const result = calculateSaju(input)
|
|
48
|
+
|
|
49
|
+
// 사주 4주 (시, 일, 월, 년 순서)
|
|
50
|
+
for (const p of result.pillars) {
|
|
51
|
+
console.log(p.pillar.ganzi) // '乙巳', '壬辰', '乙卯', '癸酉'
|
|
52
|
+
console.log(p.stemSipsin) // 천간 십신
|
|
53
|
+
console.log(p.branchSipsin) // 지지 십신
|
|
54
|
+
console.log(p.unseong) // 12운성
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 대운
|
|
58
|
+
for (const dw of result.daewoon) {
|
|
59
|
+
console.log(`${dw.ganzi} (${dw.age}세~)`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 간지 관계 (합, 충, 형, 파, 해)
|
|
63
|
+
for (const [key, pair] of result.relations.pairs) {
|
|
64
|
+
console.log(key, pair.stem, pair.branch)
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 자미두수 (紫微斗數)
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { createChart, calculateLiunian, getDaxianList } from '@orrery/core/ziwei'
|
|
72
|
+
|
|
73
|
+
// 명반 생성
|
|
74
|
+
const chart = createChart(1993, 3, 12, 9, 45, true)
|
|
75
|
+
|
|
76
|
+
console.log(chart.mingGongZhi) // 명궁 지지
|
|
77
|
+
console.log(chart.shenGongZhi) // 신궁 지지
|
|
78
|
+
console.log(chart.wuXingJu.name) // 오행국 (예: '水二局')
|
|
79
|
+
|
|
80
|
+
// 각 궁위의 성요 확인
|
|
81
|
+
for (const [name, palace] of Object.entries(chart.palaces)) {
|
|
82
|
+
const stars = palace.stars.map(s => {
|
|
83
|
+
const sihua = s.siHua ? `(${s.siHua})` : ''
|
|
84
|
+
return `${s.name}${s.brightness}${sihua}`
|
|
85
|
+
})
|
|
86
|
+
console.log(`${name} [${palace.ganZhi}]: ${stars.join(', ')}`)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 대한 (大限)
|
|
90
|
+
const daxianList = getDaxianList(chart)
|
|
91
|
+
for (const dx of daxianList) {
|
|
92
|
+
console.log(`${dx.ageStart}~${dx.ageEnd}세: ${dx.palaceName} ${dx.ganZhi}`)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 유년 (流年) — 특정 연도의 운세
|
|
96
|
+
const liunian = calculateLiunian(chart, 2026)
|
|
97
|
+
console.log(liunian.natalPalaceAtMing) // 유년 명궁이 위치한 본명반 궁위
|
|
98
|
+
console.log(liunian.siHua) // 유년 사화
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 서양 점성술 (Natal Chart)
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { calculateNatal } from '@orrery/core/natal'
|
|
105
|
+
import type { BirthInput } from '@orrery/core/types'
|
|
106
|
+
|
|
107
|
+
const input: BirthInput = {
|
|
108
|
+
year: 1993, month: 3, day: 12,
|
|
109
|
+
hour: 9, minute: 45,
|
|
110
|
+
gender: 'M',
|
|
111
|
+
latitude: 37.5665, // 서울 (선택사항, 기본값: 서울)
|
|
112
|
+
longitude: 126.9780,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const chart = await calculateNatal(input)
|
|
116
|
+
|
|
117
|
+
// 행성 위치
|
|
118
|
+
for (const planet of chart.planets) {
|
|
119
|
+
console.log(`${planet.id}: ${planet.sign} ${planet.degreeInSign.toFixed(1)}°`)
|
|
120
|
+
// 'Sun: Pisces 21.6°'
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ASC / MC
|
|
124
|
+
console.log(`ASC: ${chart.angles.asc.sign}`)
|
|
125
|
+
console.log(`MC: ${chart.angles.mc.sign}`)
|
|
126
|
+
|
|
127
|
+
// 하우스 (기본: Placidus)
|
|
128
|
+
for (const house of chart.houses) {
|
|
129
|
+
console.log(`House ${house.number}: ${house.sign} ${house.degreeInSign.toFixed(1)}°`)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 애스펙트
|
|
133
|
+
for (const aspect of chart.aspects) {
|
|
134
|
+
console.log(`${aspect.planet1} ${aspect.type} ${aspect.planet2} (orb: ${aspect.orb.toFixed(1)}°)`)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 하우스 시스템 변경 (Koch)
|
|
138
|
+
const kochChart = await calculateNatal(input, 'K')
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 저수준 API
|
|
142
|
+
|
|
143
|
+
개별 함수를 직접 사용할 수도 있습니다:
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import { getFourPillars, getDaewoon, getRelation } from '@orrery/core/pillars'
|
|
147
|
+
import { STEM_INFO, ELEMENT_HANJA } from '@orrery/core/constants'
|
|
148
|
+
|
|
149
|
+
// 사주 4주만 계산
|
|
150
|
+
const [년주, 월주, 일주, 시주] = getFourPillars(1993, 3, 12, 9, 45)
|
|
151
|
+
console.log(년주, 월주, 일주, 시주) // '癸酉', '乙卯', '壬辰', '乙巳'
|
|
152
|
+
|
|
153
|
+
// 십신 관계
|
|
154
|
+
const relation = getRelation('壬', '乙')
|
|
155
|
+
console.log(relation?.hanja) // '傷官'
|
|
156
|
+
|
|
157
|
+
// 천간 오행 정보
|
|
158
|
+
console.log(STEM_INFO['壬']) // { yinyang: '+', element: 'water' }
|
|
159
|
+
console.log(ELEMENT_HANJA['water']) // '水'
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### 도시 데이터
|
|
163
|
+
|
|
164
|
+
출생 위치 입력에 활용할 수 있는 한국/세계 주요 도시 데이터를 제공합니다:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { SEOUL, filterCities, formatCityName } from '@orrery/core/cities'
|
|
168
|
+
|
|
169
|
+
console.log(SEOUL) // { name: '서울', lat: 37.5665, lon: 126.9780 }
|
|
170
|
+
|
|
171
|
+
// 한글 초성 검색 지원
|
|
172
|
+
const results = filterCities('ㅂㅅ') // '부산' 매칭
|
|
173
|
+
console.log(formatCityName(results[0])) // '부산'
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## 예제 실행
|
|
177
|
+
|
|
178
|
+
저장소를 클론한 후 바로 실행해볼 수 있는 예제 스크립트가 있습니다:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
git clone https://github.com/rath/orrery.git
|
|
182
|
+
cd orrery
|
|
183
|
+
bun install
|
|
184
|
+
|
|
185
|
+
# 사주팔자
|
|
186
|
+
bun packages/core/examples/saju.ts
|
|
187
|
+
|
|
188
|
+
# 자미두수
|
|
189
|
+
bun packages/core/examples/ziwei.ts
|
|
190
|
+
|
|
191
|
+
# 서양 점성술 출생차트
|
|
192
|
+
bun packages/core/examples/natal.ts
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Subpath Exports
|
|
196
|
+
|
|
197
|
+
필요한 모듈만 선택적으로 import할 수 있습니다:
|
|
198
|
+
|
|
199
|
+
| 경로 | 설명 |
|
|
200
|
+
|------|------|
|
|
201
|
+
| `@orrery/core` | 전체 barrel export |
|
|
202
|
+
| `@orrery/core/saju` | `calculateSaju()` |
|
|
203
|
+
| `@orrery/core/ziwei` | `createChart()`, `calculateLiunian()`, `getDaxianList()` |
|
|
204
|
+
| `@orrery/core/natal` | `calculateNatal()`, 별자리/행성 심볼, 포맷 함수 |
|
|
205
|
+
| `@orrery/core/pillars` | `getFourPillars()`, `getDaewoon()` 등 저수준 API |
|
|
206
|
+
| `@orrery/core/types` | 모든 TypeScript 타입/인터페이스 |
|
|
207
|
+
| `@orrery/core/constants` | 천간/지지, 십신, 궁위명 등 상수 테이블 |
|
|
208
|
+
| `@orrery/core/cities` | 도시 데이터, 검색 함수 |
|
|
209
|
+
|
|
210
|
+
## 의존성
|
|
211
|
+
|
|
212
|
+
| 패키지 | 유형 | 용도 |
|
|
213
|
+
|--------|------|------|
|
|
214
|
+
| `lunar-javascript` | dependency | 음력 변환 (자미두수) |
|
|
215
|
+
| `swisseph-wasm` | optional peer | Swiss Ephemeris WASM (서양 점성술) |
|
|
216
|
+
|
|
217
|
+
`swisseph-wasm`을 설치하지 않으면 사주/자미두수만 사용 가능합니다.
|
|
218
|
+
|
|
219
|
+
## 라이선스
|
|
220
|
+
|
|
221
|
+
MIT
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// src/natal.ts
|
|
2
|
+
import SwissEph from "swisseph-wasm";
|
|
3
|
+
var DEFAULT_LAT = 37.5194;
|
|
4
|
+
var DEFAULT_LON = 127.0992;
|
|
5
|
+
var ZODIAC_SIGNS = [
|
|
6
|
+
"Aries",
|
|
7
|
+
"Taurus",
|
|
8
|
+
"Gemini",
|
|
9
|
+
"Cancer",
|
|
10
|
+
"Leo",
|
|
11
|
+
"Virgo",
|
|
12
|
+
"Libra",
|
|
13
|
+
"Scorpio",
|
|
14
|
+
"Sagittarius",
|
|
15
|
+
"Capricorn",
|
|
16
|
+
"Aquarius",
|
|
17
|
+
"Pisces"
|
|
18
|
+
];
|
|
19
|
+
var ZODIAC_SYMBOLS = {
|
|
20
|
+
Aries: "\u2648",
|
|
21
|
+
Taurus: "\u2649",
|
|
22
|
+
Gemini: "\u264A",
|
|
23
|
+
Cancer: "\u264B",
|
|
24
|
+
Leo: "\u264C",
|
|
25
|
+
Virgo: "\u264D",
|
|
26
|
+
Libra: "\u264E",
|
|
27
|
+
Scorpio: "\u264F",
|
|
28
|
+
Sagittarius: "\u2650",
|
|
29
|
+
Capricorn: "\u2651",
|
|
30
|
+
Aquarius: "\u2652",
|
|
31
|
+
Pisces: "\u2653"
|
|
32
|
+
};
|
|
33
|
+
var ZODIAC_KO = {
|
|
34
|
+
Aries: "\uC591\uC790\uB9AC",
|
|
35
|
+
Taurus: "\uD669\uC18C\uC790\uB9AC",
|
|
36
|
+
Gemini: "\uC30D\uB465\uC774\uC790\uB9AC",
|
|
37
|
+
Cancer: "\uAC8C\uC790\uB9AC",
|
|
38
|
+
Leo: "\uC0AC\uC790\uC790\uB9AC",
|
|
39
|
+
Virgo: "\uCC98\uB140\uC790\uB9AC",
|
|
40
|
+
Libra: "\uCC9C\uCE6D\uC790\uB9AC",
|
|
41
|
+
Scorpio: "\uC804\uAC08\uC790\uB9AC",
|
|
42
|
+
Sagittarius: "\uAD81\uC218\uC790\uB9AC",
|
|
43
|
+
Capricorn: "\uC5FC\uC18C\uC790\uB9AC",
|
|
44
|
+
Aquarius: "\uBB3C\uBCD1\uC790\uB9AC",
|
|
45
|
+
Pisces: "\uBB3C\uACE0\uAE30\uC790\uB9AC"
|
|
46
|
+
};
|
|
47
|
+
var PLANET_SYMBOLS = {
|
|
48
|
+
Sun: "\u2609",
|
|
49
|
+
Moon: "\u263D",
|
|
50
|
+
Mercury: "\u263F",
|
|
51
|
+
Venus: "\u2640",
|
|
52
|
+
Mars: "\u2642",
|
|
53
|
+
Jupiter: "\u2643",
|
|
54
|
+
Saturn: "\u2644",
|
|
55
|
+
Uranus: "\u2645",
|
|
56
|
+
Neptune: "\u2646",
|
|
57
|
+
Pluto: "\u2647",
|
|
58
|
+
Chiron: "\u26B7",
|
|
59
|
+
NorthNode: "\u260A",
|
|
60
|
+
SouthNode: "\u260B"
|
|
61
|
+
};
|
|
62
|
+
var PLANET_KO = {
|
|
63
|
+
Sun: "\uD0DC\uC591",
|
|
64
|
+
Moon: "\uB2EC",
|
|
65
|
+
Mercury: "\uC218\uC131",
|
|
66
|
+
Venus: "\uAE08\uC131",
|
|
67
|
+
Mars: "\uD654\uC131",
|
|
68
|
+
Jupiter: "\uBAA9\uC131",
|
|
69
|
+
Saturn: "\uD1A0\uC131",
|
|
70
|
+
Uranus: "\uCC9C\uC655\uC131",
|
|
71
|
+
Neptune: "\uD574\uC655\uC131",
|
|
72
|
+
Pluto: "\uBA85\uC655\uC131",
|
|
73
|
+
Chiron: "\uD0A4\uB860",
|
|
74
|
+
NorthNode: "\uBD81\uAD50\uC810",
|
|
75
|
+
SouthNode: "\uB0A8\uAD50\uC810"
|
|
76
|
+
};
|
|
77
|
+
var PLANET_BODIES = [
|
|
78
|
+
["Sun", 0],
|
|
79
|
+
// SE_SUN
|
|
80
|
+
["Moon", 1],
|
|
81
|
+
// SE_MOON
|
|
82
|
+
["Mercury", 2],
|
|
83
|
+
// SE_MERCURY
|
|
84
|
+
["Venus", 3],
|
|
85
|
+
// SE_VENUS
|
|
86
|
+
["Mars", 4],
|
|
87
|
+
// SE_MARS
|
|
88
|
+
["Jupiter", 5],
|
|
89
|
+
// SE_JUPITER
|
|
90
|
+
["Saturn", 6],
|
|
91
|
+
// SE_SATURN
|
|
92
|
+
["Uranus", 7],
|
|
93
|
+
// SE_URANUS
|
|
94
|
+
["Neptune", 8],
|
|
95
|
+
// SE_NEPTUNE
|
|
96
|
+
["Pluto", 9],
|
|
97
|
+
// SE_PLUTO
|
|
98
|
+
["Chiron", 15],
|
|
99
|
+
// SE_CHIRON
|
|
100
|
+
["NorthNode", 10]
|
|
101
|
+
// SE_MEAN_NODE
|
|
102
|
+
];
|
|
103
|
+
var ASPECT_SYMBOLS = {
|
|
104
|
+
conjunction: "\u260C",
|
|
105
|
+
sextile: "\u26B9",
|
|
106
|
+
square: "\u25A1",
|
|
107
|
+
trine: "\u25B3",
|
|
108
|
+
opposition: "\u260D"
|
|
109
|
+
};
|
|
110
|
+
var ASPECT_DEFS = [
|
|
111
|
+
{ type: "conjunction", angle: 0, maxOrb: 8 },
|
|
112
|
+
{ type: "sextile", angle: 60, maxOrb: 6 },
|
|
113
|
+
{ type: "square", angle: 90, maxOrb: 8 },
|
|
114
|
+
{ type: "trine", angle: 120, maxOrb: 8 },
|
|
115
|
+
{ type: "opposition", angle: 180, maxOrb: 8 }
|
|
116
|
+
];
|
|
117
|
+
var ROMAN = ["I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII"];
|
|
118
|
+
var HOUSE_SYSTEMS = [
|
|
119
|
+
["P", "Placidus"],
|
|
120
|
+
["K", "Koch"],
|
|
121
|
+
["O", "Porphyrius"],
|
|
122
|
+
["R", "Regiomontanus"],
|
|
123
|
+
["C", "Campanus"],
|
|
124
|
+
["E", "Equal"],
|
|
125
|
+
["W", "Whole Sign"],
|
|
126
|
+
["B", "Alcabitus"],
|
|
127
|
+
["M", "Morinus"],
|
|
128
|
+
["T", "Topocentric"]
|
|
129
|
+
];
|
|
130
|
+
var sweInstance = null;
|
|
131
|
+
var initPromise = null;
|
|
132
|
+
async function getSwissEph() {
|
|
133
|
+
if (sweInstance) return sweInstance;
|
|
134
|
+
if (initPromise) return initPromise;
|
|
135
|
+
initPromise = (async () => {
|
|
136
|
+
const swe = new SwissEph();
|
|
137
|
+
await swe.initSwissEph();
|
|
138
|
+
sweInstance = swe;
|
|
139
|
+
return swe;
|
|
140
|
+
})();
|
|
141
|
+
return initPromise;
|
|
142
|
+
}
|
|
143
|
+
function lonToSign(lon) {
|
|
144
|
+
return ZODIAC_SIGNS[Math.trunc(normalizeDeg(lon) / 30)];
|
|
145
|
+
}
|
|
146
|
+
function normalizeDeg(deg) {
|
|
147
|
+
return (deg % 360 + 360) % 360;
|
|
148
|
+
}
|
|
149
|
+
function degreeInSign(lon) {
|
|
150
|
+
return normalizeDeg(lon) % 30;
|
|
151
|
+
}
|
|
152
|
+
function angularDifference(lon1, lon2) {
|
|
153
|
+
let diff = Math.abs(normalizeDeg(lon1) - normalizeDeg(lon2));
|
|
154
|
+
if (diff > 180) diff = 360 - diff;
|
|
155
|
+
return diff;
|
|
156
|
+
}
|
|
157
|
+
function formatDegree(lon) {
|
|
158
|
+
const d = degreeInSign(lon);
|
|
159
|
+
const deg = Math.trunc(d);
|
|
160
|
+
const min = Math.trunc((d - deg) * 60);
|
|
161
|
+
return `${deg}\xB0${String(min).padStart(2, "0")}'`;
|
|
162
|
+
}
|
|
163
|
+
function findHouse(planetLon, cusps) {
|
|
164
|
+
const lon = normalizeDeg(planetLon);
|
|
165
|
+
for (let i = 1; i <= 12; i++) {
|
|
166
|
+
const start = normalizeDeg(cusps[i]);
|
|
167
|
+
const end = normalizeDeg(cusps[i === 12 ? 1 : i + 1]);
|
|
168
|
+
if (start < end) {
|
|
169
|
+
if (lon >= start && lon < end) return i;
|
|
170
|
+
} else {
|
|
171
|
+
if (lon >= start || lon < end) return i;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return 1;
|
|
175
|
+
}
|
|
176
|
+
function calculateAspects(planets) {
|
|
177
|
+
const aspects = [];
|
|
178
|
+
for (let i = 0; i < planets.length; i++) {
|
|
179
|
+
for (let j = i + 1; j < planets.length; j++) {
|
|
180
|
+
const p1 = planets[i];
|
|
181
|
+
const p2 = planets[j];
|
|
182
|
+
const diff = angularDifference(p1.longitude, p2.longitude);
|
|
183
|
+
for (const def of ASPECT_DEFS) {
|
|
184
|
+
const orb = Math.abs(diff - def.angle);
|
|
185
|
+
if (orb <= def.maxOrb) {
|
|
186
|
+
aspects.push({
|
|
187
|
+
planet1: p1.id,
|
|
188
|
+
planet2: p2.id,
|
|
189
|
+
type: def.type,
|
|
190
|
+
angle: def.angle,
|
|
191
|
+
orb: Math.round(orb * 10) / 10
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
aspects.sort((a, b) => a.orb - b.orb);
|
|
198
|
+
return aspects;
|
|
199
|
+
}
|
|
200
|
+
async function calculateNatal(input, houseSystem = "P") {
|
|
201
|
+
const swe = await getSwissEph();
|
|
202
|
+
const lat = input.latitude ?? DEFAULT_LAT;
|
|
203
|
+
const lon = input.longitude ?? DEFAULT_LON;
|
|
204
|
+
const utHourDecimal = input.hour + input.minute / 60 - 9;
|
|
205
|
+
let utYear = input.year;
|
|
206
|
+
let utMonth = input.month;
|
|
207
|
+
let utDay = input.day;
|
|
208
|
+
let utHour = utHourDecimal;
|
|
209
|
+
if (utHour < 0) {
|
|
210
|
+
utHour += 24;
|
|
211
|
+
const d = new Date(utYear, utMonth - 1, utDay - 1);
|
|
212
|
+
utYear = d.getFullYear();
|
|
213
|
+
utMonth = d.getMonth() + 1;
|
|
214
|
+
utDay = d.getDate();
|
|
215
|
+
} else if (utHour >= 24) {
|
|
216
|
+
utHour -= 24;
|
|
217
|
+
const d = new Date(utYear, utMonth - 1, utDay + 1);
|
|
218
|
+
utYear = d.getFullYear();
|
|
219
|
+
utMonth = d.getMonth() + 1;
|
|
220
|
+
utDay = d.getDate();
|
|
221
|
+
}
|
|
222
|
+
const jd = swe.julday(utYear, utMonth, utDay, utHour);
|
|
223
|
+
const housesEx = swe.houses_ex.bind(swe);
|
|
224
|
+
const { cusps, ascmc } = housesEx(jd, 0, lat, lon, houseSystem);
|
|
225
|
+
const calcFlags = swe.SEFLG_SWIEPH | swe.SEFLG_SPEED;
|
|
226
|
+
const planets = [];
|
|
227
|
+
for (const [id, bodyNum] of PLANET_BODIES) {
|
|
228
|
+
const pos = swe.calc_ut(jd, bodyNum, calcFlags);
|
|
229
|
+
const longitude = pos[0];
|
|
230
|
+
planets.push({
|
|
231
|
+
id,
|
|
232
|
+
longitude,
|
|
233
|
+
latitude: pos[1],
|
|
234
|
+
speed: pos[3],
|
|
235
|
+
sign: lonToSign(longitude),
|
|
236
|
+
degreeInSign: degreeInSign(longitude),
|
|
237
|
+
isRetrograde: pos[3] < 0,
|
|
238
|
+
house: findHouse(longitude, cusps)
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
const northNode = planets.find((p) => p.id === "NorthNode");
|
|
242
|
+
const southLon = normalizeDeg(northNode.longitude + 180);
|
|
243
|
+
planets.push({
|
|
244
|
+
id: "SouthNode",
|
|
245
|
+
longitude: southLon,
|
|
246
|
+
latitude: -northNode.latitude,
|
|
247
|
+
speed: northNode.speed,
|
|
248
|
+
sign: lonToSign(southLon),
|
|
249
|
+
degreeInSign: degreeInSign(southLon),
|
|
250
|
+
isRetrograde: false,
|
|
251
|
+
house: findHouse(southLon, cusps)
|
|
252
|
+
});
|
|
253
|
+
const houses = [];
|
|
254
|
+
for (let i = 1; i <= 12; i++) {
|
|
255
|
+
const cuspLon = cusps[i];
|
|
256
|
+
houses.push({
|
|
257
|
+
number: i,
|
|
258
|
+
cuspLongitude: cuspLon,
|
|
259
|
+
sign: lonToSign(cuspLon),
|
|
260
|
+
degreeInSign: degreeInSign(cuspLon)
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
const ascLon = ascmc[0];
|
|
264
|
+
const mcLon = ascmc[1];
|
|
265
|
+
const descLon = normalizeDeg(ascLon + 180);
|
|
266
|
+
const icLon = normalizeDeg(mcLon + 180);
|
|
267
|
+
const angles = {
|
|
268
|
+
asc: { longitude: ascLon, sign: lonToSign(ascLon), degreeInSign: degreeInSign(ascLon) },
|
|
269
|
+
mc: { longitude: mcLon, sign: lonToSign(mcLon), degreeInSign: degreeInSign(mcLon) },
|
|
270
|
+
desc: { longitude: descLon, sign: lonToSign(descLon), degreeInSign: degreeInSign(descLon) },
|
|
271
|
+
ic: { longitude: icLon, sign: lonToSign(icLon), degreeInSign: degreeInSign(icLon) }
|
|
272
|
+
};
|
|
273
|
+
const aspectPlanets = planets.filter((p) => p.id !== "SouthNode");
|
|
274
|
+
const aspects = calculateAspects(aspectPlanets);
|
|
275
|
+
return { input, planets, houses, angles, aspects };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export {
|
|
279
|
+
ZODIAC_SIGNS,
|
|
280
|
+
ZODIAC_SYMBOLS,
|
|
281
|
+
ZODIAC_KO,
|
|
282
|
+
PLANET_SYMBOLS,
|
|
283
|
+
PLANET_KO,
|
|
284
|
+
ASPECT_SYMBOLS,
|
|
285
|
+
ROMAN,
|
|
286
|
+
HOUSE_SYSTEMS,
|
|
287
|
+
getSwissEph,
|
|
288
|
+
lonToSign,
|
|
289
|
+
normalizeDeg,
|
|
290
|
+
formatDegree,
|
|
291
|
+
calculateNatal
|
|
292
|
+
};
|