iT邦幫忙

2023 iThome 鐵人賽

DAY 8
0

目標:製作一個人類圖模組
輸入:生日日期時間
輸出:一 2x13(共 26 個數字)的人類圖矩陣

演算法實作

前幾天已經實作了 getDesignDate()getPersonalityDate() 兩個函數,接下來需要一個從尤利安曆換算銘刻(imprint)的函數

  1. 先定義 getBodyGraph() 函數,用於取得完整人類圖資訊。
  2. 接著我們需要計算銘刻(imprint),定義 getImprint() 函數,參數為一個尤利安曆時間。這邊我們利用了前幾天實作的兩個計算尤利安曆時間的函數 getDesignDate()getPersonalityDate()
// body-graph.service.ts

public getBodyGraph(birth: IDateTime): IBodyGraph {
  return {
    design: this.getImprint(this.getDesignDate(birth)),
    personality : this.getImprint(this.getPersonalityDate(birth)),
  };
}

public getImprint(jd: number): IImprint {
	throw new Error('method not implemented');
}
  1. 實作 getImprint() 函數。由於地球和南交點的計算需要太陽和北交點的位置(地球和南交點分別位於太陽和北交點在天球軌道上的正對面),這邊先暫時跳過計算。
public getImprint(jd: number): IImprint {
  const sun = this.getSubstructure(jd, SE_SUN);
  const earth = /* to be implemented */;

  const northNode = this.getSubstructure(jd, SE_TRUE_NODE);
  const southNode = /* to be implemented */;

  return {
    sun,
    earth,
    moon: this.getSubstructure(jd, SE_MOON),
    northNode,
    southNode,
    mercury: this.getSubstructure(jd, SE_MERCURY),
    venus: this.getSubstructure(jd, SE_VENUS),
    mars: this.getSubstructure(jd, SE_MARS),
    jupiter: this.getSubstructure(jd, SE_JUPITER),
    saturn: this.getSubstructure(jd, SE_SATURN),
    uranus: this.getSubstructure(jd, SE_URANUS),
    neptune: this.getSubstructure(jd, SE_NEPTUNE),
    pluto: this.getSubstructure(jd, SE_PLUTO),
  };
}

public getSubstructure(jd: number, planet: number): ISubstructure {
	throw new Error('method not implemented');
}
  1. 實作 getSubstructure() 函數,用於計算指定尤利安曆時間下,指定星球的位置。由於 swe_calc_ut() 出來的位置是天球經度,還需要一個函數來轉換為人類圖的閘門和爻。
public getSubstructure(jd: number, planet: number): ISubstructure {
  const calcResult = swe_calc_ut(jd, planet, this.SWE_IFLAG);

  if (!('longitude' in calcResult)) {
    this.assertSwissephResult(calcResult);

    throw new Error('invalid result of swe_calc_ut()');
  }

  return this.getSubstructureFromLongitude(calcResult.longitude);
}

public getSubstructureFromLongitude(longitude: number): ISubstructure {
  throw new Error('method not implemented');
}
  1. 在轉換為閘門和爻之前,先來介紹一點常數:
    1. NUM_GATES 總共 64 個閘門(對應易經六十四卦)
    2. NUM_LINES 一卦有 6 條爻線,總共 384 條
    3. NUM_DEGS 一個圓 360 度
    4. NUM_DEGS_PER_GATE 一個閘門 5.625 度
    5. NUM_DEGS_PER_LINE 一條爻線 0.9375 度
    6. INDEXED_GATES 按照時間(一年之間)依序對應的閘門號碼。人類圖的 41 閘門從水瓶座的 2 度開始對齊,完成一整年的週期(見附圖)
private readonly SWE_IFLAG = SEFLG_JPLEPH | SEFLG_SPEED;

private readonly NUM_GATES = 64;
private readonly NUM_LINES = this.NUM_GATES * 6;
private readonly NUM_DEGS = 360;
private readonly NUM_DEGS_PER_GATE = this.NUM_DEGS / this.NUM_GATES;
private readonly NUM_DEGS_PER_LINE = this.NUM_DEGS / this.NUM_LINES;

private readonly INDEXED_GATES = [
  '41', '19', '13', '49', '30', '55', '37', '63', '22', '36', '25',
  '17', '21', '51', '42', '3',  '27', '24', '2',  '23', '8',  '20',
  '16', '35', '45', '12', '15', '52', '39', '53', '62', '56', '31',
  '33', '7',  '4',  '29', '59', '40', '64', '47', '6',  '46', '18',
  '48', '57', '32', '50', '28', '44', '1',  '43', '14', '34', '9',
  '5',  '26', '11', '10', '58', '38', '54', '61', '6',
] as const;

  1. 實作 getSubstructureFromLongitude() 將天球經度轉換為人類圖的閘門和爻。
public getSubstructureFromLongitude(longitude: number): ISubstructure {
  // Gate 41 begins at 02º Aquarius while longitude 0 is at 0º Aries.
  // The offset is 58º = (30º * 2) - 2º
  // 30º per sign, there are 2 signs from Aquarius to Aries
  const { x360 } = swe_degnorm(longitude + 58);

  // gate
  const gateIndex = Math.trunc(x360 / this.NUM_DEGS_PER_GATE);
  const gate = this.INDEXED_GATES[gateIndex];

  // line
  const remain = x360 % this.NUM_DEGS_PER_GATE;
  const line = String(Math.trunc(remain / this.NUM_DEGS_PER_LINE) + 1);

  return { longitude, gate, line };
}
  1. 回頭計算地球和南交點位置,由於地球位於太陽的對面,南交點位於北交點對面。我們需要準備一個計算對面位置的函數 getOppositePosition()
public getOppositePosition(longitude: number): number {
  return swe_degnorm(longitude + 180).x360;
}
  1. 完整的 getImprint() 如下:
public getImprint(jd: number): IImprint {
  const sun = this.getSubstructure(jd, SE_SUN);
  const earth = this.getSubstructureFromLongitude(
    this.getOppositePosition(sun.longitude),
  );

  const northNode = this.getSubstructure(jd, SE_TRUE_NODE);
  const southNode = this.getSubstructureFromLongitude(
    this.getOppositePosition(northNode.longitude),
  );

  return {
    sun,
    earth,
    moon: this.getSubstructure(jd, SE_MOON),
    northNode,
    southNode,
    mercury: this.getSubstructure(jd, SE_MERCURY),
    venus: this.getSubstructure(jd, SE_VENUS),
    mars: this.getSubstructure(jd, SE_MARS),
    jupiter: this.getSubstructure(jd, SE_JUPITER),
    saturn: this.getSubstructure(jd, SE_SATURN),
    uranus: this.getSubstructure(jd, SE_URANUS),
    neptune: this.getSubstructure(jd, SE_NEPTUNE),
    pluto: this.getSubstructure(jd, SE_PLUTO),
  };
}

撰寫測試

利用亞洲人類圖學院帶入 1990.01.01T00:00:00UTC 取得人類圖作為測資參考資料。

describe('BodyGraphService', () => {
  it('getBodyGraph()', () => {
    expect(
      service.getBodyGraph({
        year: 1990,
        month: 1,
        day: 1,
        hour: 0,
        minute: 0,
        second: 0,
        millisecond: 0,
      }),
    ).toEqual(
      expect.objectContaining({
        design: expect.objectContaining({
          sun: expect.objectContaining({ gate: '48', line: '3' }),
          earth: expect.objectContaining({ gate: '21', line: '3' }),
          moon: expect.objectContaining({ gate: '5', line: '4' }),
          northNode: expect.objectContaining({ gate: '30', line: '1' }),
          southNode: expect.objectContaining({ gate: '29', line: '1' }),
          mercury: expect.objectContaining({ gate: '6', line: '4' }),
          venus: expect.objectContaining({ gate: '14', line: '3' }),
          mars: expect.objectContaining({ gate: '48', line: '1' }),
          jupiter: expect.objectContaining({ gate: '39', line: '1' }),
          saturn: expect.objectContaining({ gate: '58', line: '5' }),
          uranus: expect.objectContaining({ gate: '10', line: '4' }),
          neptune: expect.objectContaining({ gate: '38', line: '1' }),
          pluto: expect.objectContaining({ gate: '1', line: '1' }),
        }),
        personality: expect.objectContaining({
          sun: expect.objectContaining({ gate: '38', line: '1' }),
          earth: expect.objectContaining({ gate: '39', line: '1' }),
          moon: expect.objectContaining({ gate: '30', line: '3' }),
          northNode: expect.objectContaining({ gate: '13', line: '4' }),
          southNode: expect.objectContaining({ gate: '7', line: '4' }),
          mercury: expect.objectContaining({ gate: '61', line: '6' }),
          venus: expect.objectContaining({ gate: '41', line: '5' }),
          mars: expect.objectContaining({ gate: '9', line: '5' }),
          jupiter: expect.objectContaining({ gate: '52', line: '2' }),
          saturn: expect.objectContaining({ gate: '54', line: '1' }),
          uranus: expect.objectContaining({ gate: '58', line: '3' }),
          neptune: expect.objectContaining({ gate: '38', line: '3' }),
          pluto: expect.objectContaining({ gate: '1', line: '5' }),
        }),
      }),
    );
  });
})

測試成功:

> jest

 PASS  src/body-graph/body-graph.service.spec.ts
  BodyGraphService
    ✓ getDesignDate() (8 ms)
    ✓ getDesignDate() (3 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        1.334 s
Ran all test suites.

「人類圖基礎」系列功能,到這邊算是大功告成。


晚安,瑪卡巴卡。


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

尚未有邦友留言

立即登入留言