目標:製作一個人類圖模組
輸入:生日日期時間
輸出:一 2x13(共 26 個數字)的人類圖矩陣
swe_solcross_ut()
is not a function
swe_solcross_ut()
是後來版本的 Swiss Ephemeris 新加入的方法,目前 NodeJS 的 swisseph 模組並未更新至該版本,導致找不到swe_solcross_ut()
方法。
昨天的 src/pos.cc
內容有誤,更新為以下程式碼即可順利通過測試:
// src/pos.cc
NAN_METHOD(node_swe_solcross_ut) {
Nan::HandleScope scope;
if (info.Length () < 3) {
Nan::ThrowTypeError ("Wrong number of arguments");
};
if (
!info [0]->IsNumber () ||
!info [1]->IsNumber () ||
!info [2]->IsNumber ()
) {
Nan::ThrowTypeError ("Wrong type of arguments");
};
double x2cross = Nan::To<double>(info[0]).FromJust();
double tjd_ut = Nan::To<double>(info[1]).FromJust();
int32_t iflag = Nan::To<int32_t>(info[2]).FromJust();
char serr [AS_MAXCH];
double jx = ::swe_solcross_ut(x2cross, tjd_ut, iflag, serr);
Local <Object> result = Nan::New<Object> ();
if (jx < tjd_ut) {
Nan::Set(result,Nan::New<String> ("error").ToLocalChecked(), Nan::New<String> (serr).ToLocalChecked());
} else {
Nan::Set(result,Nan::New<String> ("jx").ToLocalChecked(), Nan::New<Number> (jx));
}
info.GetReturnValue().Set (result);
}
主要是 iflag
誤取了 info[1]
作為值,應該是 info[2]
。
這部分的原始碼放在 GitHub: momocow/swisseph.js
將測試改寫為
(function test_swe_solcross_ut() {
const result = swisseph.swe_solcross_ut(0, 0, 0);
assert (!result.error, result.error);
console.log('The crossing of the Sun over 0 deg:', result.jx);
})();
可得
The crossing of the Sun over 0 deg: 117.6858939194457
建立 BodyGraphService 包含兩個函數: getPersonalityDate()
和 getDesignDate()
,分別用於計算個性和設計的時間。個性就是出生的尤利安曆時間,設計是出生當時的太陽位置往回推 88 度當時的尤利安曆時間。
@Injectable()
export class BodyGraphService {
public getPersonalityDate(birth: IDateTime): number {}
public getDesignDate(birth: IDateTime): number {}
}
先實作一個處理 swisseph 計算錯誤的函數,這個函數須負責斷言結果的型別。
private assertSwissephResult<T extends object>(
result: T | { error: string },
): asserts result is T {
if ('error' in result) {
throw new Error(result.error);
}
}
接這需要一個從公曆轉尤利安曆的函數:
private getJulianDate({
year,
month,
day,
hour,
minute,
second,
millisecond,
}: IDateTime): number {
const result = swe_utc_to_jd(
year,
month,
day,
hour,
minute,
second + millisecond / 1000,
SE_GREG_CAL,
);
this.assertSwissephResult(result);
return result.julianDayUT;
}
實作個性時間換算:
public getPersonalityDate(birth: IDateTime): number {
return this.getJulianDate(birth);
}
實作設計時間換算:
public getDesignDate(birth: IDateTime): number {
const jd = this.getJulianDate(birth);
const calcResult = swe_calc_ut(jd, SE_SUN, SEFLG_SPEED | SEFLG_JPLEPH);
if (!('longitude' in calcResult)) {
this.assertSwissephResult(calcResult);
throw new Error('invalid result of swe_calc_ut()');
}
const { longitude } = calcResult;
const { x360 } = swe_degnorm(longitude - 88);
const solcrossResult = swe_solcross_ut(
x360,
jd - 100,
SEFLG_SPEED | SEFLG_JPLEPH,
);
this.assertSwissephResult(solcrossResult);
const { jx } = solcrossResult;
return jx;
}
待優化的地方:
this.getJulianDate(birth)
在個性和設計都會使用同一個
birth
去呼叫,因此可以做一些 memoize 的設計優化效能。
describe('BodyGraphService', () => {
let service: BodyGraphService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [BodyGraphService],
}).compile();
service = module.get<BodyGraphService>(BodyGraphService);
});
it('getDesignDate()', () => {
expect(
service.getPersonalityDate({
year: 1990,
month: 1,
day: 1,
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
}),
// percision = 5, mean error is less than 1 second
).toBeCloseTo(2447892.5, 5);
});
it('getDesignDate()', () => {
expect(
service.getDesignDate({
year: 1990,
month: 1,
day: 1,
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
}),
// percision = 5, mean error is less than 1 second
).toBeCloseTo(2447805.0786541915, 5);
});
});
測試成功:
PASS src/body-graph/body-graph.service.spec.ts
BodyGraphService
✓ getDesignDate() (6 ms)
✓ getDesignDate() (2 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 0.953 s, estimated 2 s
Ran all test suites.
今天實現了兩個計算尤利安曆的方法,並通過測試。
明天預計可以開始編寫從尤利安曆時間計算星盤的方法,拭目以待。
晚安,瑪卡巴卡。