iT邦幫忙

2023 iThome 鐵人賽

DAY 6
0

目標:製作一個人類圖模組
輸入:生日日期時間
輸出:一 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

建立 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 的設計優化效能。

測試 BodyGraphService

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.

今天實現了兩個計算尤利安曆的方法,並通過測試。

明天預計可以開始編寫從尤利安曆時間計算星盤的方法,拭目以待。


晚安,瑪卡巴卡。


上一篇
人類圖基礎:從生辰到星盤(3)
下一篇
人類圖基礎:從生辰到星盤(5)
系列文
「莫忘初衷,從猴子到超人」:一個獻給自由精靈的社群媒合引擎18
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言