-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
162 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { multiplyTransposed, type Matrix3x3 } from "./matrix.js"; | ||
import { ROTATION_MATRIX_e, rotate } from "./rotation.js"; | ||
import type { Vector3 } from "./vector.js"; | ||
|
||
/** | ||
* Calculates the rotation matrix R_EN from an n-vector. | ||
* | ||
* @param n_E - An n-vector decomposed in E. | ||
* @param R_Ee - A rotation matrix defining the axes of the coordinate frame E. | ||
* | ||
* @returns The resulting rotation matrix. | ||
*/ | ||
export function n_E2R_EN( | ||
n_E: Vector3, | ||
R_Ee: Matrix3x3 = ROTATION_MATRIX_e, | ||
): Matrix3x3 { | ||
// Based on https://github.com/pbrod/nvector/blob/b8afd89a860a4958d499789607aacb4168dcef87/src/nvector/rotation.py#L478 | ||
const [n_e_x, n_e_y, n_e_z] = rotate(R_Ee, n_E); | ||
|
||
// The z-axis of N (down) points opposite to n-vector | ||
const Nz_e_x = -n_e_x; | ||
const Nz_e_y = -n_e_y; | ||
const Nz_e_z = -n_e_z; | ||
|
||
// Find y-axis of N (East) | ||
// Remember that N is singular at poles | ||
// Ny points perpendicular to the plane formed by n-vector and Earth's spin | ||
// axis | ||
const Ny_e_direction_y = -n_e_z; | ||
const Ny_e_direction_z = n_e_y; | ||
const Ny_e_direction_norm = Math.hypot(Ny_e_direction_y, Ny_e_direction_z); | ||
const on_poles = Math.hypot(Ny_e_direction_y, Ny_e_direction_z) === 0; | ||
// Ny_e_x is always 0, so it's factored out in the following equations | ||
const Ny_e_y = on_poles ? 1 : Ny_e_direction_y / Ny_e_direction_norm; | ||
const Ny_e_z = on_poles ? 0 : Ny_e_direction_z / Ny_e_direction_norm; | ||
|
||
// Find x-axis of N (North) | ||
const Nx_e_x = Ny_e_y * Nz_e_z - Ny_e_z * Nz_e_y; | ||
const Nx_e_y = Ny_e_z * Nz_e_x; | ||
const Nx_e_z = -Ny_e_y * Nz_e_x; | ||
|
||
// Use each component as a column vector, then multiply by the transpose of | ||
// R_Ee to get the rotation matrix R_EN | ||
return multiplyTransposed(R_Ee, [ | ||
[Nx_e_x, 0, Nz_e_x], | ||
[Nx_e_y, Ny_e_y, Nz_e_y], | ||
[Nx_e_z, Ny_e_z, Nz_e_z], | ||
]); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import { fc, it } from "@fast-check/vitest"; | ||
import { afterAll, beforeAll, describe, expect } from "vitest"; | ||
import { n_E2R_EN } from "../../src/index.js"; | ||
import { ROTATION_MATRIX_e, rotate } from "../../src/rotation.js"; | ||
import { | ||
arbitrary3dRotationMatrix, | ||
arbitrary3dUnitVector, | ||
} from "../arbitrary.js"; | ||
import { | ||
NvectorTestClient, | ||
createNvectorTestClient, | ||
} from "../nvector-test-api.js"; | ||
|
||
const TEST_DURATION = 5000; | ||
|
||
describe("n_E2R_EN()", () => { | ||
let nvectorTestClient: NvectorTestClient; | ||
|
||
beforeAll(async () => { | ||
nvectorTestClient = await createNvectorTestClient(); | ||
}); | ||
|
||
afterAll(() => { | ||
nvectorTestClient?.close(); | ||
}); | ||
|
||
it.prop( | ||
[ | ||
fc | ||
.tuple( | ||
arbitrary3dUnitVector(), | ||
fc.option(arbitrary3dRotationMatrix(), { nil: undefined }), | ||
) | ||
.filter(([n_E, R_Ee = ROTATION_MATRIX_e]) => { | ||
// Avoid situations where very close to poles | ||
// Python implementation rounds to zero in these cases, which causes | ||
// the Y axis to be [0, 1, 0] instead of the calculated value, | ||
// producing very different results. | ||
const [, n_e_y, n_e_z] = rotate(R_Ee, n_E); | ||
const Ny_e_direction_norm = Math.hypot(-n_e_z, n_e_y); | ||
if (Ny_e_direction_norm > 0 && Ny_e_direction_norm <= 1e-100) { | ||
return false; | ||
} | ||
|
||
return true; | ||
}), | ||
], | ||
{ interruptAfterTimeLimit: TEST_DURATION, numRuns: Infinity }, | ||
)( | ||
"matches the Python implementation", | ||
async ([n_E, R_Ee]) => { | ||
const expected = await nvectorTestClient.n_E2R_EN(n_E, R_Ee); | ||
|
||
expect(expected).toMatchObject([ | ||
[expect.any(Number), expect.any(Number), expect.any(Number)], | ||
[expect.any(Number), expect.any(Number), expect.any(Number)], | ||
[expect.any(Number), expect.any(Number), expect.any(Number)], | ||
]); | ||
|
||
const actual = n_E2R_EN(n_E, R_Ee); | ||
|
||
expect(actual).toMatchObject([ | ||
[expect.any(Number), expect.any(Number), expect.any(Number)], | ||
[expect.any(Number), expect.any(Number), expect.any(Number)], | ||
[expect.any(Number), expect.any(Number), expect.any(Number)], | ||
]); | ||
expect(actual[0][0]).toBeCloseTo(expected[0][0], 14); | ||
expect(actual[0][1]).toBeCloseTo(expected[0][1], 14); | ||
expect(actual[0][2]).toBeCloseTo(expected[0][2], 14); | ||
expect(actual[1][0]).toBeCloseTo(expected[1][0], 14); | ||
expect(actual[1][1]).toBeCloseTo(expected[1][1], 14); | ||
expect(actual[1][2]).toBeCloseTo(expected[1][2], 14); | ||
expect(actual[2][0]).toBeCloseTo(expected[2][0], 14); | ||
expect(actual[2][1]).toBeCloseTo(expected[2][1], 14); | ||
expect(actual[2][2]).toBeCloseTo(expected[2][2], 14); | ||
}, | ||
TEST_DURATION + 1000, | ||
); | ||
}); |