siderust-js 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/.github/workflows/ci.yml +166 -0
- package/.gitmodules +9 -0
- package/CHANGELOG.md +26 -0
- package/LICENSE +661 -0
- package/README.md +138 -0
- package/package.json +12 -0
- package/qtty-js/.github/workflows/ci.yml +151 -0
- package/qtty-js/.gitmodules +3 -0
- package/qtty-js/CHANGELOG.md +31 -0
- package/qtty-js/LICENSE +661 -0
- package/qtty-js/README.md +132 -0
- package/qtty-js/package.json +20 -0
- package/qtty-js/qtty/.github/workflows/ci.yml +155 -0
- package/qtty-js/qtty/CHANGELOG.md +120 -0
- package/qtty-js/qtty/Cargo.lock +1462 -0
- package/qtty-js/qtty/Cargo.toml +12 -0
- package/qtty-js/qtty/LICENSE +661 -0
- package/qtty-js/qtty/README.md +9 -0
- package/qtty-js/qtty/qtty/Cargo.toml +41 -0
- package/qtty-js/qtty/qtty/README.md +8 -0
- package/qtty-js/qtty/qtty/examples/angles.rs +14 -0
- package/qtty-js/qtty/qtty/examples/astronomy.rs +17 -0
- package/qtty-js/qtty/qtty/examples/dimensional_arithmetic.rs +83 -0
- package/qtty-js/qtty/qtty/examples/python_integration.rs +61 -0
- package/qtty-js/qtty/qtty/examples/quickstart.rs +15 -0
- package/qtty-js/qtty/qtty/examples/ratios.rs +12 -0
- package/qtty-js/qtty/qtty/examples/serde_with_unit.rs +234 -0
- package/qtty-js/qtty/qtty/examples/serialization.rs +141 -0
- package/qtty-js/qtty/qtty/examples/serialization_advanced.rs +155 -0
- package/qtty-js/qtty/qtty/src/f32.rs +108 -0
- package/qtty-js/qtty/qtty/src/f64.rs +30 -0
- package/qtty-js/qtty/qtty/src/i128.rs +111 -0
- package/qtty-js/qtty/qtty/src/i16.rs +111 -0
- package/qtty-js/qtty/qtty/src/i32.rs +111 -0
- package/qtty-js/qtty/qtty/src/i64.rs +111 -0
- package/qtty-js/qtty/qtty/src/i8.rs +111 -0
- package/qtty-js/qtty/qtty/src/lib.rs +238 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std/Cargo.lock +83 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std/Cargo.toml +10 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std/src/lib.rs +7 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std-alloc/Cargo.lock +83 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std-alloc/Cargo.toml +10 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std-alloc/src/lib.rs +7 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-std/Cargo.lock +83 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-std/Cargo.toml +10 -0
- package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-std/src/lib.rs +5 -0
- package/qtty-js/qtty/qtty/tests/integration_tests.rs +529 -0
- package/qtty-js/qtty/qtty/tests/qtty_vec_feature_matrix.rs +58 -0
- package/qtty-js/qtty/qtty-core/Cargo.toml +41 -0
- package/qtty-js/qtty/qtty-core/README.md +8 -0
- package/qtty-js/qtty/qtty-core/examples/diesel_integration.rs +145 -0
- package/qtty-js/qtty/qtty-core/examples/quantity_db_serde.rs +215 -0
- package/qtty-js/qtty/qtty-core/src/dimension.rs +249 -0
- package/qtty-js/qtty/qtty-core/src/feature_diesel.rs +318 -0
- package/qtty-js/qtty/qtty-core/src/feature_pyo3.rs +27 -0
- package/qtty-js/qtty/qtty-core/src/feature_serde.rs +203 -0
- package/qtty-js/qtty/qtty-core/src/feature_tiberius.rs +28 -0
- package/qtty-js/qtty/qtty-core/src/lib.rs +744 -0
- package/qtty-js/qtty/qtty-core/src/macros.rs +93 -0
- package/qtty-js/qtty/qtty-core/src/quantity.rs +810 -0
- package/qtty-js/qtty/qtty-core/src/scalar.rs +1742 -0
- package/qtty-js/qtty/qtty-core/src/unit.rs +332 -0
- package/qtty-js/qtty/qtty-core/src/units/angular.rs +1228 -0
- package/qtty-js/qtty/qtty-core/src/units/area.rs +243 -0
- package/qtty-js/qtty/qtty-core/src/units/frequency.rs +179 -0
- package/qtty-js/qtty/qtty-core/src/units/length.rs +1270 -0
- package/qtty-js/qtty/qtty-core/src/units/mass.rs +488 -0
- package/qtty-js/qtty/qtty-core/src/units/mod.rs +26 -0
- package/qtty-js/qtty/qtty-core/src/units/power.rs +324 -0
- package/qtty-js/qtty/qtty-core/src/units/time.rs +667 -0
- package/qtty-js/qtty/qtty-core/src/units/unitless.rs +212 -0
- package/qtty-js/qtty/qtty-core/src/units/velocity.rs +210 -0
- package/qtty-js/qtty/qtty-core/src/units/volume.rs +269 -0
- package/qtty-js/qtty/qtty-core/tests/core.rs +628 -0
- package/qtty-js/qtty/qtty-core/tests/diesel.rs +461 -0
- package/qtty-js/qtty/qtty-core/tests/integers.rs +632 -0
- package/qtty-js/qtty/qtty-core/tests/no_cross_unit_ops.rs +35 -0
- package/qtty-js/qtty/qtty-core/tests/pyo3.rs +334 -0
- package/qtty-js/qtty/qtty-core/tests/quantity_f32.rs +276 -0
- package/qtty-js/qtty/qtty-core/tests/scalar_decimal.rs +258 -0
- package/qtty-js/qtty/qtty-core/tests/scalar_f32.rs +286 -0
- package/qtty-js/qtty/qtty-core/tests/scalar_f64_real.rs +287 -0
- package/qtty-js/qtty/qtty-core/tests/scalar_rational.rs +260 -0
- package/qtty-js/qtty/qtty-core/tests/serde.rs +256 -0
- package/qtty-js/qtty/qtty-core/tests/tiberius.rs +208 -0
- package/qtty-js/qtty/qtty-derive/Cargo.toml +23 -0
- package/qtty-js/qtty/qtty-derive/README.md +8 -0
- package/qtty-js/qtty/qtty-derive/src/lib.rs +340 -0
- package/qtty-js/qtty/qtty-ffi/ARCHITECTURE.md +3 -0
- package/qtty-js/qtty/qtty-ffi/Cargo.toml +31 -0
- package/qtty-js/qtty/qtty-ffi/README.md +9 -0
- package/qtty-js/qtty/qtty-ffi/build.rs +326 -0
- package/qtty-js/qtty/qtty-ffi/cbindgen.toml +105 -0
- package/qtty-js/qtty/qtty-ffi/include/qtty_ffi.h +1126 -0
- package/qtty-js/qtty/qtty-ffi/src/ffi.rs +1251 -0
- package/qtty-js/qtty/qtty-ffi/src/ffi_serde.rs +294 -0
- package/qtty-js/qtty/qtty-ffi/src/helpers.rs +310 -0
- package/qtty-js/qtty/qtty-ffi/src/lib.rs +229 -0
- package/qtty-js/qtty/qtty-ffi/src/macros.rs +121 -0
- package/qtty-js/qtty/qtty-ffi/src/registry.rs +274 -0
- package/qtty-js/qtty/qtty-ffi/src/types.rs +620 -0
- package/qtty-js/qtty/qtty-ffi/tests/integration_tests.rs +842 -0
- package/qtty-js/qtty/qtty-ffi/units.csv +156 -0
- package/qtty-js/qtty/qtty-ffi/units.csv.md +3 -0
- package/qtty-js/qtty-node/.prettierignore +6 -0
- package/qtty-js/qtty-node/.prettierrc.json +6 -0
- package/qtty-js/qtty-node/README.md +250 -0
- package/qtty-js/qtty-node/c8.config.json +11 -0
- package/qtty-js/qtty-node/eslint.config.js +31 -0
- package/qtty-js/qtty-node/examples/arithmetic.mjs +64 -0
- package/qtty-js/qtty-node/examples/astronomy.mjs +90 -0
- package/qtty-js/qtty-node/examples/quickstart.mjs +36 -0
- package/qtty-js/qtty-node/examples/serialization.mjs +125 -0
- package/qtty-js/qtty-node/examples/unit_factories.mjs +74 -0
- package/qtty-js/qtty-node/index.d.ts +219 -0
- package/qtty-js/qtty-node/index.js +323 -0
- package/qtty-js/qtty-node/lib/DerivedQuantity.js +122 -0
- package/qtty-js/qtty-node/lib/Quantity.js +151 -0
- package/qtty-js/qtty-node/lib/backend.js +25 -0
- package/qtty-js/qtty-node/native.cjs +306 -0
- package/qtty-js/qtty-node/package-lock.json +3223 -0
- package/qtty-js/qtty-node/package.json +70 -0
- package/qtty-js/qtty-node/units.d.ts +299 -0
- package/qtty-js/qtty-node/units.js +210 -0
- package/qtty-js/qtty-web/Cargo.lock +767 -0
- package/qtty-js/qtty-web/Cargo.toml +21 -0
- package/qtty-js/qtty-web/index.d.ts +140 -0
- package/qtty-js/qtty-web/index.js +20 -0
- package/qtty-js/qtty-web/lib/DerivedQuantity.js +58 -0
- package/qtty-js/qtty-web/lib/Quantity.js +75 -0
- package/qtty-js/qtty-web/lib/backend.js +80 -0
- package/qtty-js/qtty-web/package.json +45 -0
- package/qtty-js/qtty-web/src/lib.rs +111 -0
- package/qtty-js/scripts/ci.sh +73 -0
- package/scripts/ci.sh +123 -0
- package/siderust-core/Cargo.lock +787 -0
- package/siderust-core/Cargo.toml +18 -0
- package/siderust-core/DEDUPLICATION.md +124 -0
- package/siderust-core/src/body.rs +120 -0
- package/siderust-core/src/events.rs +184 -0
- package/siderust-core/src/lib.rs +20 -0
- package/siderust-core/src/observer.rs +55 -0
- package/siderust-core/src/position.rs +213 -0
- package/siderust-node/.prettierignore +7 -0
- package/siderust-node/.prettierrc.json +6 -0
- package/siderust-node/Cargo.lock +906 -0
- package/siderust-node/Cargo.toml +29 -0
- package/siderust-node/README.md +109 -0
- package/siderust-node/__test__/index.test.mjs +248 -0
- package/siderust-node/build.rs +5 -0
- package/siderust-node/c8.config.json +3 -0
- package/siderust-node/eslint.config.js +31 -0
- package/siderust-node/examples/01_basic_coordinates.mjs +24 -0
- package/siderust-node/examples/02_coordinate_transformations.mjs +25 -0
- package/siderust-node/examples/03_all_frames_conversions.mjs +26 -0
- package/siderust-node/examples/04_all_center_conversions.mjs +24 -0
- package/siderust-node/examples/05_target_tracking.mjs +22 -0
- package/siderust-node/examples/06_night_events.mjs +18 -0
- package/siderust-node/examples/07_moon_properties.mjs +21 -0
- package/siderust-node/examples/08_solar_system.mjs +19 -0
- package/siderust-node/examples/09_star_observability.mjs +22 -0
- package/siderust-node/examples/10_time_periods.mjs +9 -0
- package/siderust-node/examples/11_serialization.mjs +31 -0
- package/siderust-node/examples/12_runtime_ephemeris.mjs +27 -0
- package/siderust-node/examples/13_coordinate_operations.mjs +20 -0
- package/siderust-node/index.d.ts +623 -0
- package/siderust-node/index.js +79 -0
- package/siderust-node/lib/Observer.js +112 -0
- package/siderust-node/lib/Star.js +118 -0
- package/siderust-node/lib/backend.js +63 -0
- package/siderust-node/lib/wrappers.js +566 -0
- package/siderust-node/main.js +20 -0
- package/siderust-node/native.cjs +360 -0
- package/siderust-node/package-lock.json +3261 -0
- package/siderust-node/package.json +71 -0
- package/siderust-node/src/body.rs +74 -0
- package/siderust-node/src/coordinates.rs +372 -0
- package/siderust-node/src/ephemeris.rs +462 -0
- package/siderust-node/src/events.rs +577 -0
- package/siderust-node/src/lib.rs +43 -0
- package/siderust-node/src/observer.rs +132 -0
- package/siderust-node/src/phase.rs +218 -0
- package/siderust-node/src/position.rs +292 -0
- package/siderust-node/src/star.rs +200 -0
- package/siderust-web/Cargo.lock +855 -0
- package/siderust-web/Cargo.toml +34 -0
- package/siderust-web/README.md +100 -0
- package/siderust-web/__test__/index.test.mjs +118 -0
- package/siderust-web/examples/github-pages/README.md +31 -0
- package/siderust-web/examples/github-pages/index.html +135 -0
- package/siderust-web/index.d.ts +311 -0
- package/siderust-web/index.js +66 -0
- package/siderust-web/lib/Observer.js +103 -0
- package/siderust-web/lib/Star.js +116 -0
- package/siderust-web/lib/backend.js +400 -0
- package/siderust-web/lib/wrappers.js +512 -0
- package/siderust-web/package.json +55 -0
- package/siderust-web/src/body.rs +69 -0
- package/siderust-web/src/coordinates.rs +302 -0
- package/siderust-web/src/ephemeris.rs +456 -0
- package/siderust-web/src/events.rs +520 -0
- package/siderust-web/src/lib.rs +51 -0
- package/siderust-web/src/observer.rs +117 -0
- package/siderust-web/src/phase.rs +190 -0
- package/siderust-web/src/position.rs +291 -0
- package/siderust-web/src/star.rs +178 -0
- package/tempoch-js/.github/workflows/ci.yml +142 -0
- package/tempoch-js/.gitmodules +3 -0
- package/tempoch-js/CHANGELOG.md +25 -0
- package/tempoch-js/LICENSE +661 -0
- package/tempoch-js/README.md +126 -0
- package/tempoch-js/package.json +20 -0
- package/tempoch-js/scripts/ci.sh +73 -0
- package/tempoch-js/tempoch/.github/workflows/ci.yml +113 -0
- package/tempoch-js/tempoch/CHANGELOG.md +82 -0
- package/tempoch-js/tempoch/Cargo.lock +947 -0
- package/tempoch-js/tempoch/Cargo.toml +3 -0
- package/tempoch-js/tempoch/LICENSE +661 -0
- package/tempoch-js/tempoch/README.md +76 -0
- package/tempoch-js/tempoch/tempoch/Cargo.toml +27 -0
- package/tempoch-js/tempoch/tempoch/examples/periods.rs +45 -0
- package/tempoch-js/tempoch/tempoch/examples/quickstart.rs +13 -0
- package/tempoch-js/tempoch/tempoch/src/lib.rs +49 -0
- package/tempoch-js/tempoch/tempoch/tests/integration.rs +57 -0
- package/tempoch-js/tempoch/tempoch-core/Cargo.toml +24 -0
- package/tempoch-js/tempoch/tempoch-core/src/delta_t.rs +345 -0
- package/tempoch-js/tempoch/tempoch-core/src/instant.rs +811 -0
- package/tempoch-js/tempoch/tempoch-core/src/julian_date_ext.rs +142 -0
- package/tempoch-js/tempoch/tempoch-core/src/lib.rs +81 -0
- package/tempoch-js/tempoch/tempoch-core/src/period.rs +1168 -0
- package/tempoch-js/tempoch/tempoch-core/src/scales.rs +779 -0
- package/tempoch-js/tempoch/tempoch-ffi/Cargo.lock +889 -0
- package/tempoch-js/tempoch/tempoch-ffi/Cargo.toml +26 -0
- package/tempoch-js/tempoch/tempoch-ffi/build.rs +24 -0
- package/tempoch-js/tempoch/tempoch-ffi/cbindgen.toml +30 -0
- package/tempoch-js/tempoch/tempoch-ffi/src/error.rs +19 -0
- package/tempoch-js/tempoch/tempoch-ffi/src/lib.rs +82 -0
- package/tempoch-js/tempoch/tempoch-ffi/src/period.rs +101 -0
- package/tempoch-js/tempoch/tempoch-ffi/src/time.rs +711 -0
- package/tempoch-js/tempoch/tempoch-ffi/tests/ffi.rs +265 -0
- package/tempoch-js/tempoch-node/.prettierignore +6 -0
- package/tempoch-js/tempoch-node/.prettierrc.json +6 -0
- package/tempoch-js/tempoch-node/Cargo.lock +496 -0
- package/tempoch-js/tempoch-node/Cargo.toml +29 -0
- package/tempoch-js/tempoch-node/README.md +265 -0
- package/tempoch-js/tempoch-node/__test__/index.test.mjs +598 -0
- package/tempoch-js/tempoch-node/build.rs +5 -0
- package/tempoch-js/tempoch-node/c8.config.json +3 -0
- package/tempoch-js/tempoch-node/eslint.config.js +31 -0
- package/tempoch-js/tempoch-node/examples/periods.mjs +79 -0
- package/tempoch-js/tempoch-node/examples/quickstart.mjs +71 -0
- package/tempoch-js/tempoch-node/examples/timescales.mjs +92 -0
- package/tempoch-js/tempoch-node/index.d.ts +280 -0
- package/tempoch-js/tempoch-node/index.js +32 -0
- package/tempoch-js/tempoch-node/lib/JulianDate.js +176 -0
- package/tempoch-js/tempoch-node/lib/ModifiedJulianDate.js +156 -0
- package/tempoch-js/tempoch-node/lib/Period.js +133 -0
- package/tempoch-js/tempoch-node/lib/backend.js +38 -0
- package/tempoch-js/tempoch-node/lib/qttyCompat.js +92 -0
- package/tempoch-js/tempoch-node/native.cjs +317 -0
- package/tempoch-js/tempoch-node/package-lock.json +3223 -0
- package/tempoch-js/tempoch-node/package.json +56 -0
- package/tempoch-js/tempoch-node/src/lib.rs +573 -0
- package/tempoch-js/tempoch-web/Cargo.toml +23 -0
- package/tempoch-js/tempoch-web/index.d.ts +95 -0
- package/tempoch-js/tempoch-web/index.js +27 -0
- package/tempoch-js/tempoch-web/lib/JulianDate.js +170 -0
- package/tempoch-js/tempoch-web/lib/ModifiedJulianDate.js +145 -0
- package/tempoch-js/tempoch-web/lib/Period.js +121 -0
- package/tempoch-js/tempoch-web/lib/backend.js +118 -0
- package/tempoch-js/tempoch-web/package.json +46 -0
- package/tempoch-js/tempoch-web/src/lib.rs +184 -0
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (C) 2026 Vallés Puig, Ramon
|
|
3
|
+
|
|
4
|
+
//! Altitude and azimuth event queries — the core observation-planning API.
|
|
5
|
+
|
|
6
|
+
use serde::Serialize;
|
|
7
|
+
use wasm_bindgen::prelude::*;
|
|
8
|
+
|
|
9
|
+
use crate::body::{dispatch_body, parse_body, to_js};
|
|
10
|
+
use crate::observer::Observer;
|
|
11
|
+
use crate::star::Star;
|
|
12
|
+
|
|
13
|
+
use qtty::*;
|
|
14
|
+
use siderust::calculus::altitude::{self, SearchOpts};
|
|
15
|
+
use siderust::calculus::azimuth;
|
|
16
|
+
use siderust::AltitudePeriodsProvider;
|
|
17
|
+
use siderust::AzimuthProvider;
|
|
18
|
+
use tempoch::{ModifiedJulianDate, Period, MJD};
|
|
19
|
+
|
|
20
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
21
|
+
// Result types (serde-serialised — becomes plain JS objects)
|
|
22
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
23
|
+
|
|
24
|
+
#[derive(Serialize)]
|
|
25
|
+
#[serde(rename_all = "camelCase")]
|
|
26
|
+
pub struct CrossingEvent {
|
|
27
|
+
pub mjd: f64,
|
|
28
|
+
pub direction: String,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#[derive(Serialize)]
|
|
32
|
+
#[serde(rename_all = "camelCase")]
|
|
33
|
+
pub struct CulminationEvent {
|
|
34
|
+
pub mjd: f64,
|
|
35
|
+
pub altitude_deg: f64,
|
|
36
|
+
pub kind: String,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#[derive(Serialize, serde::Deserialize)]
|
|
40
|
+
#[serde(rename_all = "camelCase")]
|
|
41
|
+
pub struct MjdPeriod {
|
|
42
|
+
pub start_mjd: f64,
|
|
43
|
+
pub end_mjd: f64,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#[derive(Serialize)]
|
|
47
|
+
#[serde(rename_all = "camelCase")]
|
|
48
|
+
pub struct AzimuthCrossingEvent {
|
|
49
|
+
pub mjd: f64,
|
|
50
|
+
pub direction: String,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#[derive(Serialize)]
|
|
54
|
+
#[serde(rename_all = "camelCase")]
|
|
55
|
+
pub struct AzimuthExtremum {
|
|
56
|
+
pub mjd: f64,
|
|
57
|
+
pub azimuth_deg: f64,
|
|
58
|
+
pub kind: String,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
62
|
+
// Helpers
|
|
63
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
64
|
+
|
|
65
|
+
pub(crate) fn make_window(start_mjd: f64, end_mjd: f64) -> Result<Period<MJD>, JsError> {
|
|
66
|
+
if !start_mjd.is_finite() || !end_mjd.is_finite() {
|
|
67
|
+
return Err(JsError::new(
|
|
68
|
+
"Window bounds (startMjd, endMjd) must be finite",
|
|
69
|
+
));
|
|
70
|
+
}
|
|
71
|
+
if start_mjd >= end_mjd {
|
|
72
|
+
return Err(JsError::new(
|
|
73
|
+
"Window start must be before end (startMjd < endMjd)",
|
|
74
|
+
));
|
|
75
|
+
}
|
|
76
|
+
Ok(Period::new(
|
|
77
|
+
ModifiedJulianDate::new(start_mjd),
|
|
78
|
+
ModifiedJulianDate::new(end_mjd),
|
|
79
|
+
))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fn convert_crossings(events: Vec<altitude::CrossingEvent>) -> Vec<CrossingEvent> {
|
|
83
|
+
events
|
|
84
|
+
.into_iter()
|
|
85
|
+
.map(|e| CrossingEvent {
|
|
86
|
+
mjd: e.mjd.value(),
|
|
87
|
+
direction: match e.direction {
|
|
88
|
+
altitude::CrossingDirection::Rising => "rising".to_string(),
|
|
89
|
+
altitude::CrossingDirection::Setting => "setting".to_string(),
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
.collect()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
fn convert_culminations(events: Vec<altitude::CulminationEvent>) -> Vec<CulminationEvent> {
|
|
96
|
+
events
|
|
97
|
+
.into_iter()
|
|
98
|
+
.map(|e| CulminationEvent {
|
|
99
|
+
mjd: e.mjd.value(),
|
|
100
|
+
altitude_deg: e.altitude.value(),
|
|
101
|
+
kind: match e.kind {
|
|
102
|
+
altitude::CulminationKind::Max => "max".to_string(),
|
|
103
|
+
altitude::CulminationKind::Min => "min".to_string(),
|
|
104
|
+
},
|
|
105
|
+
})
|
|
106
|
+
.collect()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
fn convert_periods(periods: Vec<Period<MJD>>) -> Vec<MjdPeriod> {
|
|
110
|
+
periods
|
|
111
|
+
.into_iter()
|
|
112
|
+
.map(|p| MjdPeriod {
|
|
113
|
+
start_mjd: p.start.value(),
|
|
114
|
+
end_mjd: p.end.value(),
|
|
115
|
+
})
|
|
116
|
+
.collect()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fn convert_az_crossings(
|
|
120
|
+
events: Vec<siderust::AzimuthCrossingEvent>,
|
|
121
|
+
) -> Vec<AzimuthCrossingEvent> {
|
|
122
|
+
events
|
|
123
|
+
.into_iter()
|
|
124
|
+
.map(|e| AzimuthCrossingEvent {
|
|
125
|
+
mjd: e.mjd.value(),
|
|
126
|
+
direction: match e.direction {
|
|
127
|
+
siderust::AzimuthCrossingDirection::Rising => "rising".to_string(),
|
|
128
|
+
siderust::AzimuthCrossingDirection::Setting => "setting".to_string(),
|
|
129
|
+
},
|
|
130
|
+
})
|
|
131
|
+
.collect()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fn convert_az_extrema(events: Vec<siderust::AzimuthExtremum>) -> Vec<AzimuthExtremum> {
|
|
135
|
+
events
|
|
136
|
+
.into_iter()
|
|
137
|
+
.map(|e| AzimuthExtremum {
|
|
138
|
+
mjd: e.mjd.value(),
|
|
139
|
+
azimuth_deg: e.azimuth.value(),
|
|
140
|
+
kind: match e.kind {
|
|
141
|
+
siderust::AzimuthExtremumKind::Max => "max".to_string(),
|
|
142
|
+
siderust::AzimuthExtremumKind::Min => "min".to_string(),
|
|
143
|
+
},
|
|
144
|
+
})
|
|
145
|
+
.collect()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
149
|
+
// Body altitude — instantaneous
|
|
150
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
151
|
+
|
|
152
|
+
/// Compute the altitude of a solar-system body at a single instant.
|
|
153
|
+
#[wasm_bindgen(js_name = "bodyAltitudeAt")]
|
|
154
|
+
pub fn body_altitude_at(body: &str, observer: &Observer, mjd: f64) -> Result<f64, JsError> {
|
|
155
|
+
if !mjd.is_finite() {
|
|
156
|
+
return Err(JsError::new("mjd must be finite"));
|
|
157
|
+
}
|
|
158
|
+
let kind = parse_body(body)?;
|
|
159
|
+
let m = ModifiedJulianDate::new(mjd);
|
|
160
|
+
let result: f64 = dispatch_body!(kind, |b| {
|
|
161
|
+
b.altitude_at(&observer.inner, m).to::<Degree>().value()
|
|
162
|
+
});
|
|
163
|
+
Ok(result)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/// Compute the azimuth of a solar-system body at a single instant.
|
|
167
|
+
#[wasm_bindgen(js_name = "bodyAzimuthAt")]
|
|
168
|
+
pub fn body_azimuth_at(body: &str, observer: &Observer, mjd: f64) -> Result<f64, JsError> {
|
|
169
|
+
if !mjd.is_finite() {
|
|
170
|
+
return Err(JsError::new("mjd must be finite"));
|
|
171
|
+
}
|
|
172
|
+
let kind = parse_body(body)?;
|
|
173
|
+
let m = ModifiedJulianDate::new(mjd);
|
|
174
|
+
let result: f64 = dispatch_body!(kind, |b| {
|
|
175
|
+
b.azimuth_at(&observer.inner, m).to::<Degree>().value()
|
|
176
|
+
});
|
|
177
|
+
Ok(result)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
181
|
+
// Body altitude — batch events
|
|
182
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
183
|
+
|
|
184
|
+
/// Find threshold-crossing events (rise/set) for a solar-system body.
|
|
185
|
+
#[wasm_bindgen(js_name = "bodyCrossings")]
|
|
186
|
+
pub fn body_crossings(
|
|
187
|
+
body: &str,
|
|
188
|
+
observer: &Observer,
|
|
189
|
+
start_mjd: f64,
|
|
190
|
+
end_mjd: f64,
|
|
191
|
+
threshold_deg: f64,
|
|
192
|
+
) -> Result<JsValue, JsError> {
|
|
193
|
+
let kind = parse_body(body)?;
|
|
194
|
+
let window = make_window(start_mjd, end_mjd)?;
|
|
195
|
+
let thr = Degrees::new(threshold_deg);
|
|
196
|
+
let opts = SearchOpts::default();
|
|
197
|
+
let result = dispatch_body!(kind, |b| {
|
|
198
|
+
convert_crossings(altitude::crossings(&b, &observer.inner, window, thr, opts))
|
|
199
|
+
});
|
|
200
|
+
to_js(&result)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/// Find culmination events (altitude local extrema) for a solar-system body.
|
|
204
|
+
#[wasm_bindgen(js_name = "bodyCulminations")]
|
|
205
|
+
pub fn body_culminations(
|
|
206
|
+
body: &str,
|
|
207
|
+
observer: &Observer,
|
|
208
|
+
start_mjd: f64,
|
|
209
|
+
end_mjd: f64,
|
|
210
|
+
) -> Result<JsValue, JsError> {
|
|
211
|
+
let kind = parse_body(body)?;
|
|
212
|
+
let window = make_window(start_mjd, end_mjd)?;
|
|
213
|
+
let opts = SearchOpts::default();
|
|
214
|
+
let result = dispatch_body!(kind, |b| {
|
|
215
|
+
convert_culminations(altitude::culminations(&b, &observer.inner, window, opts))
|
|
216
|
+
});
|
|
217
|
+
to_js(&result)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/// Find periods where a body's altitude is above a threshold.
|
|
221
|
+
#[wasm_bindgen(js_name = "bodyAboveThreshold")]
|
|
222
|
+
pub fn body_above_threshold(
|
|
223
|
+
body: &str,
|
|
224
|
+
observer: &Observer,
|
|
225
|
+
start_mjd: f64,
|
|
226
|
+
end_mjd: f64,
|
|
227
|
+
threshold_deg: f64,
|
|
228
|
+
) -> Result<JsValue, JsError> {
|
|
229
|
+
let kind = parse_body(body)?;
|
|
230
|
+
let window = make_window(start_mjd, end_mjd)?;
|
|
231
|
+
let thr = Degrees::new(threshold_deg);
|
|
232
|
+
let opts = SearchOpts::default();
|
|
233
|
+
let result = dispatch_body!(kind, |b| {
|
|
234
|
+
convert_periods(altitude::above_threshold(
|
|
235
|
+
&b,
|
|
236
|
+
&observer.inner,
|
|
237
|
+
window,
|
|
238
|
+
thr,
|
|
239
|
+
opts,
|
|
240
|
+
))
|
|
241
|
+
});
|
|
242
|
+
to_js(&result)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/// Find periods where a body's altitude is below a threshold.
|
|
246
|
+
#[wasm_bindgen(js_name = "bodyBelowThreshold")]
|
|
247
|
+
pub fn body_below_threshold(
|
|
248
|
+
body: &str,
|
|
249
|
+
observer: &Observer,
|
|
250
|
+
start_mjd: f64,
|
|
251
|
+
end_mjd: f64,
|
|
252
|
+
threshold_deg: f64,
|
|
253
|
+
) -> Result<JsValue, JsError> {
|
|
254
|
+
let kind = parse_body(body)?;
|
|
255
|
+
let window = make_window(start_mjd, end_mjd)?;
|
|
256
|
+
let thr = Degrees::new(threshold_deg);
|
|
257
|
+
let opts = SearchOpts::default();
|
|
258
|
+
let result = dispatch_body!(kind, |b| {
|
|
259
|
+
convert_periods(altitude::below_threshold(
|
|
260
|
+
&b,
|
|
261
|
+
&observer.inner,
|
|
262
|
+
window,
|
|
263
|
+
thr,
|
|
264
|
+
opts,
|
|
265
|
+
))
|
|
266
|
+
});
|
|
267
|
+
to_js(&result)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/// Find azimuth-crossing events for a body.
|
|
271
|
+
#[wasm_bindgen(js_name = "bodyAzimuthCrossings")]
|
|
272
|
+
pub fn body_azimuth_crossings(
|
|
273
|
+
body: &str,
|
|
274
|
+
observer: &Observer,
|
|
275
|
+
start_mjd: f64,
|
|
276
|
+
end_mjd: f64,
|
|
277
|
+
bearing_deg: f64,
|
|
278
|
+
) -> Result<JsValue, JsError> {
|
|
279
|
+
let kind = parse_body(body)?;
|
|
280
|
+
let window = make_window(start_mjd, end_mjd)?;
|
|
281
|
+
let bearing = Degrees::new(bearing_deg);
|
|
282
|
+
let opts = SearchOpts::default();
|
|
283
|
+
let result = dispatch_body!(kind, |b| {
|
|
284
|
+
convert_az_crossings(azimuth::azimuth_crossings(
|
|
285
|
+
&b,
|
|
286
|
+
&observer.inner,
|
|
287
|
+
window,
|
|
288
|
+
bearing,
|
|
289
|
+
opts,
|
|
290
|
+
))
|
|
291
|
+
});
|
|
292
|
+
to_js(&result)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/// Find azimuth extrema (max/min bearing) for a body.
|
|
296
|
+
#[wasm_bindgen(js_name = "bodyAzimuthExtrema")]
|
|
297
|
+
pub fn body_azimuth_extrema(
|
|
298
|
+
body: &str,
|
|
299
|
+
observer: &Observer,
|
|
300
|
+
start_mjd: f64,
|
|
301
|
+
end_mjd: f64,
|
|
302
|
+
) -> Result<JsValue, JsError> {
|
|
303
|
+
let kind = parse_body(body)?;
|
|
304
|
+
let window = make_window(start_mjd, end_mjd)?;
|
|
305
|
+
let opts = SearchOpts::default();
|
|
306
|
+
let result = dispatch_body!(kind, |b| {
|
|
307
|
+
convert_az_extrema(azimuth::azimuth_extrema(
|
|
308
|
+
&b,
|
|
309
|
+
&observer.inner,
|
|
310
|
+
window,
|
|
311
|
+
opts,
|
|
312
|
+
))
|
|
313
|
+
});
|
|
314
|
+
to_js(&result)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
318
|
+
// Star altitude — instantaneous
|
|
319
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
320
|
+
|
|
321
|
+
/// Compute the altitude of a catalog or custom star at a single instant.
|
|
322
|
+
#[wasm_bindgen(js_name = "starAltitudeAt")]
|
|
323
|
+
pub fn star_altitude_at(star: &Star, observer: &Observer, mjd: f64) -> Result<f64, JsError> {
|
|
324
|
+
if !mjd.is_finite() {
|
|
325
|
+
return Err(JsError::new("mjd must be finite"));
|
|
326
|
+
}
|
|
327
|
+
let m = ModifiedJulianDate::new(mjd);
|
|
328
|
+
Ok(star
|
|
329
|
+
.inner
|
|
330
|
+
.altitude_at(&observer.inner, m)
|
|
331
|
+
.to::<Degree>()
|
|
332
|
+
.value())
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/// Compute the azimuth of a star at a single instant.
|
|
336
|
+
#[wasm_bindgen(js_name = "starAzimuthAt")]
|
|
337
|
+
pub fn star_azimuth_at(star: &Star, observer: &Observer, mjd: f64) -> Result<f64, JsError> {
|
|
338
|
+
if !mjd.is_finite() {
|
|
339
|
+
return Err(JsError::new("mjd must be finite"));
|
|
340
|
+
}
|
|
341
|
+
let m = ModifiedJulianDate::new(mjd);
|
|
342
|
+
Ok(star
|
|
343
|
+
.inner
|
|
344
|
+
.azimuth_at(&observer.inner, m)
|
|
345
|
+
.to::<Degree>()
|
|
346
|
+
.value())
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
350
|
+
// Star altitude — batch events
|
|
351
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
352
|
+
|
|
353
|
+
/// Find threshold-crossing events for a star.
|
|
354
|
+
#[wasm_bindgen(js_name = "starCrossings")]
|
|
355
|
+
pub fn star_crossings(
|
|
356
|
+
star: &Star,
|
|
357
|
+
observer: &Observer,
|
|
358
|
+
start_mjd: f64,
|
|
359
|
+
end_mjd: f64,
|
|
360
|
+
threshold_deg: f64,
|
|
361
|
+
) -> Result<JsValue, JsError> {
|
|
362
|
+
let window = make_window(start_mjd, end_mjd)?;
|
|
363
|
+
let thr = Degrees::new(threshold_deg);
|
|
364
|
+
let opts = SearchOpts::default();
|
|
365
|
+
to_js(&convert_crossings(altitude::crossings(
|
|
366
|
+
&star.inner,
|
|
367
|
+
&observer.inner,
|
|
368
|
+
window,
|
|
369
|
+
thr,
|
|
370
|
+
opts,
|
|
371
|
+
)))
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/// Find culmination events for a star.
|
|
375
|
+
#[wasm_bindgen(js_name = "starCulminations")]
|
|
376
|
+
pub fn star_culminations(
|
|
377
|
+
star: &Star,
|
|
378
|
+
observer: &Observer,
|
|
379
|
+
start_mjd: f64,
|
|
380
|
+
end_mjd: f64,
|
|
381
|
+
) -> Result<JsValue, JsError> {
|
|
382
|
+
let window = make_window(start_mjd, end_mjd)?;
|
|
383
|
+
let opts = SearchOpts::default();
|
|
384
|
+
to_js(&convert_culminations(altitude::culminations(
|
|
385
|
+
&star.inner,
|
|
386
|
+
&observer.inner,
|
|
387
|
+
window,
|
|
388
|
+
opts,
|
|
389
|
+
)))
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/// Find periods where a star's altitude is above a threshold.
|
|
393
|
+
#[wasm_bindgen(js_name = "starAboveThreshold")]
|
|
394
|
+
pub fn star_above_threshold(
|
|
395
|
+
star: &Star,
|
|
396
|
+
observer: &Observer,
|
|
397
|
+
start_mjd: f64,
|
|
398
|
+
end_mjd: f64,
|
|
399
|
+
threshold_deg: f64,
|
|
400
|
+
) -> Result<JsValue, JsError> {
|
|
401
|
+
let window = make_window(start_mjd, end_mjd)?;
|
|
402
|
+
let thr = Degrees::new(threshold_deg);
|
|
403
|
+
let opts = SearchOpts::default();
|
|
404
|
+
to_js(&convert_periods(altitude::above_threshold(
|
|
405
|
+
&star.inner,
|
|
406
|
+
&observer.inner,
|
|
407
|
+
window,
|
|
408
|
+
thr,
|
|
409
|
+
opts,
|
|
410
|
+
)))
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/// Find periods where a star's altitude is below a threshold.
|
|
414
|
+
#[wasm_bindgen(js_name = "starBelowThreshold")]
|
|
415
|
+
pub fn star_below_threshold(
|
|
416
|
+
star: &Star,
|
|
417
|
+
observer: &Observer,
|
|
418
|
+
start_mjd: f64,
|
|
419
|
+
end_mjd: f64,
|
|
420
|
+
threshold_deg: f64,
|
|
421
|
+
) -> Result<JsValue, JsError> {
|
|
422
|
+
let window = make_window(start_mjd, end_mjd)?;
|
|
423
|
+
let thr = Degrees::new(threshold_deg);
|
|
424
|
+
let opts = SearchOpts::default();
|
|
425
|
+
to_js(&convert_periods(altitude::below_threshold(
|
|
426
|
+
&star.inner,
|
|
427
|
+
&observer.inner,
|
|
428
|
+
window,
|
|
429
|
+
thr,
|
|
430
|
+
opts,
|
|
431
|
+
)))
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
435
|
+
// Star azimuth — batch events
|
|
436
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
437
|
+
|
|
438
|
+
/// Find azimuth-crossing events for a star.
|
|
439
|
+
#[wasm_bindgen(js_name = "starAzimuthCrossings")]
|
|
440
|
+
pub fn star_azimuth_crossings(
|
|
441
|
+
star: &Star,
|
|
442
|
+
observer: &Observer,
|
|
443
|
+
start_mjd: f64,
|
|
444
|
+
end_mjd: f64,
|
|
445
|
+
bearing_deg: f64,
|
|
446
|
+
) -> Result<JsValue, JsError> {
|
|
447
|
+
let window = make_window(start_mjd, end_mjd)?;
|
|
448
|
+
let bearing = Degrees::new(bearing_deg);
|
|
449
|
+
let opts = SearchOpts::default();
|
|
450
|
+
to_js(&convert_az_crossings(azimuth::azimuth_crossings(
|
|
451
|
+
&star.inner,
|
|
452
|
+
&observer.inner,
|
|
453
|
+
window,
|
|
454
|
+
bearing,
|
|
455
|
+
opts,
|
|
456
|
+
)))
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/// Find azimuth extrema (max/min bearing) for a star.
|
|
460
|
+
#[wasm_bindgen(js_name = "starAzimuthExtrema")]
|
|
461
|
+
pub fn star_azimuth_extrema(
|
|
462
|
+
star: &Star,
|
|
463
|
+
observer: &Observer,
|
|
464
|
+
start_mjd: f64,
|
|
465
|
+
end_mjd: f64,
|
|
466
|
+
) -> Result<JsValue, JsError> {
|
|
467
|
+
let window = make_window(start_mjd, end_mjd)?;
|
|
468
|
+
let opts = SearchOpts::default();
|
|
469
|
+
to_js(&convert_az_extrema(azimuth::azimuth_extrema(
|
|
470
|
+
&star.inner,
|
|
471
|
+
&observer.inner,
|
|
472
|
+
window,
|
|
473
|
+
opts,
|
|
474
|
+
)))
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
478
|
+
// Period utilities
|
|
479
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
480
|
+
|
|
481
|
+
/// Intersect two lists of MJD periods, returning only overlapping intervals.
|
|
482
|
+
#[wasm_bindgen(js_name = "intersectPeriods")]
|
|
483
|
+
pub fn intersect_periods_js(
|
|
484
|
+
periods1: JsValue,
|
|
485
|
+
periods2: JsValue,
|
|
486
|
+
) -> Result<JsValue, JsError> {
|
|
487
|
+
let p1: Vec<MjdPeriod> = serde_wasm_bindgen::from_value(periods1)
|
|
488
|
+
.map_err(|e| JsError::new(&format!("periods1: {e}")))?;
|
|
489
|
+
let p2: Vec<MjdPeriod> = serde_wasm_bindgen::from_value(periods2)
|
|
490
|
+
.map_err(|e| JsError::new(&format!("periods2: {e}")))?;
|
|
491
|
+
|
|
492
|
+
let tp1: Vec<Period<MJD>> = p1
|
|
493
|
+
.iter()
|
|
494
|
+
.map(|p| {
|
|
495
|
+
Period::new(
|
|
496
|
+
ModifiedJulianDate::new(p.start_mjd),
|
|
497
|
+
ModifiedJulianDate::new(p.end_mjd),
|
|
498
|
+
)
|
|
499
|
+
})
|
|
500
|
+
.collect();
|
|
501
|
+
let tp2: Vec<Period<MJD>> = p2
|
|
502
|
+
.iter()
|
|
503
|
+
.map(|p| {
|
|
504
|
+
Period::new(
|
|
505
|
+
ModifiedJulianDate::new(p.start_mjd),
|
|
506
|
+
ModifiedJulianDate::new(p.end_mjd),
|
|
507
|
+
)
|
|
508
|
+
})
|
|
509
|
+
.collect();
|
|
510
|
+
|
|
511
|
+
let result = tempoch::intersect_periods(&tp1, &tp2);
|
|
512
|
+
let out: Vec<MjdPeriod> = result
|
|
513
|
+
.into_iter()
|
|
514
|
+
.map(|p| MjdPeriod {
|
|
515
|
+
start_mjd: p.start.value(),
|
|
516
|
+
end_mjd: p.end.value(),
|
|
517
|
+
})
|
|
518
|
+
.collect();
|
|
519
|
+
to_js(&out)
|
|
520
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (C) 2026 Vallés Puig, Ramon
|
|
3
|
+
|
|
4
|
+
//! Browser/WASM bindings for **siderust** — high-precision astronomy.
|
|
5
|
+
//!
|
|
6
|
+
//! This crate uses `wasm-bindgen` to expose siderust's core workflows as a
|
|
7
|
+
//! browser-friendly WebAssembly module. All numerical computation stays in
|
|
8
|
+
//! Rust/WASM; the JS layer only sees high-level domain objects.
|
|
9
|
+
//!
|
|
10
|
+
//! ## Design principles
|
|
11
|
+
//!
|
|
12
|
+
//! * **Batch over chatty** — altitude/azimuth queries return arrays of events
|
|
13
|
+
//! in a single crossing, avoiding per-value JS↔WASM overhead.
|
|
14
|
+
//! * **Domain objects** — `Observer`, `Star`, `CartesianPosition`, etc.
|
|
15
|
+
//! rather than raw numeric tuples.
|
|
16
|
+
//! * **API parity** — mirrors the `@siderust/siderust` (Node) package so
|
|
17
|
+
//! code can be ported between Node and browser with minimal changes.
|
|
18
|
+
|
|
19
|
+
#![deny(clippy::all)]
|
|
20
|
+
|
|
21
|
+
mod body;
|
|
22
|
+
mod coordinates;
|
|
23
|
+
mod ephemeris;
|
|
24
|
+
mod events;
|
|
25
|
+
mod observer;
|
|
26
|
+
mod phase;
|
|
27
|
+
mod position;
|
|
28
|
+
mod star;
|
|
29
|
+
|
|
30
|
+
pub use body::*;
|
|
31
|
+
pub use coordinates::*;
|
|
32
|
+
pub use ephemeris::*;
|
|
33
|
+
pub use events::*;
|
|
34
|
+
pub use observer::*;
|
|
35
|
+
pub use phase::*;
|
|
36
|
+
pub use position::*;
|
|
37
|
+
pub use star::*;
|
|
38
|
+
|
|
39
|
+
use wasm_bindgen::prelude::*;
|
|
40
|
+
|
|
41
|
+
/// Initialise panic hook for readable error messages in the browser console.
|
|
42
|
+
#[wasm_bindgen(start)]
|
|
43
|
+
pub fn init_panic_hook() {
|
|
44
|
+
console_error_panic_hook::set_once();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Return the siderust-web version string.
|
|
48
|
+
#[wasm_bindgen(js_name = "version")]
|
|
49
|
+
pub fn version() -> String {
|
|
50
|
+
env!("CARGO_PKG_VERSION").to_string()
|
|
51
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (C) 2026 Vallés Puig, Ramon
|
|
3
|
+
|
|
4
|
+
//! Observer (geodetic site) for topocentric computations.
|
|
5
|
+
|
|
6
|
+
use wasm_bindgen::prelude::*;
|
|
7
|
+
|
|
8
|
+
use qtty::*;
|
|
9
|
+
use siderust::coordinates::centers::Geodetic;
|
|
10
|
+
use siderust::coordinates::frames::ECEF;
|
|
11
|
+
use siderust::observatories;
|
|
12
|
+
|
|
13
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
14
|
+
// Observer class
|
|
15
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
/// A geodetic observer location on the Earth's surface (WGS84 ellipsoid).
|
|
18
|
+
#[wasm_bindgen]
|
|
19
|
+
pub struct Observer {
|
|
20
|
+
pub(crate) inner: Geodetic<ECEF>,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#[wasm_bindgen]
|
|
24
|
+
impl Observer {
|
|
25
|
+
/// Create an observer at a geodetic position.
|
|
26
|
+
///
|
|
27
|
+
/// @param lonDeg — Longitude in degrees (east positive).
|
|
28
|
+
/// @param latDeg — Latitude in degrees (north positive).
|
|
29
|
+
/// @param heightM — Height above WGS84 ellipsoid in metres.
|
|
30
|
+
#[wasm_bindgen(constructor)]
|
|
31
|
+
pub fn new(lon_deg: f64, lat_deg: f64, height_m: f64) -> Result<Observer, JsError> {
|
|
32
|
+
if !lon_deg.is_finite() || !lat_deg.is_finite() || !height_m.is_finite() {
|
|
33
|
+
return Err(JsError::new(
|
|
34
|
+
"Observer coordinates must be finite (not NaN or ±infinity)",
|
|
35
|
+
));
|
|
36
|
+
}
|
|
37
|
+
Ok(Self {
|
|
38
|
+
inner: Geodetic::<ECEF>::new(
|
|
39
|
+
Degrees::new(lon_deg),
|
|
40
|
+
Degrees::new(lat_deg),
|
|
41
|
+
Meters::new(height_m),
|
|
42
|
+
),
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── preset observatories ─────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
/// Roque de los Muchachos Observatory (La Palma, Spain).
|
|
49
|
+
///
|
|
50
|
+
/// Longitude −17.8925°, Latitude +28.7543°, Altitude 2396 m.
|
|
51
|
+
#[wasm_bindgen(js_name = "roqueDeLasMuchachos")]
|
|
52
|
+
pub fn roque_de_las_muchachos() -> Observer {
|
|
53
|
+
Self {
|
|
54
|
+
inner: observatories::ROQUE_DE_LOS_MUCHACHOS,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Paranal Observatory (ESO, Chile).
|
|
59
|
+
///
|
|
60
|
+
/// Longitude −70.4043°, Latitude −24.6272°, Altitude 2635 m.
|
|
61
|
+
#[wasm_bindgen(js_name = "elParanal")]
|
|
62
|
+
pub fn el_paranal() -> Observer {
|
|
63
|
+
Self {
|
|
64
|
+
inner: observatories::EL_PARANAL,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// Mauna Kea Observatory (Hawaiʻi, USA).
|
|
69
|
+
///
|
|
70
|
+
/// Longitude −155.4681°, Latitude +19.8207°, Altitude 4207 m.
|
|
71
|
+
#[wasm_bindgen(js_name = "maunaKea")]
|
|
72
|
+
pub fn mauna_kea() -> Observer {
|
|
73
|
+
Self {
|
|
74
|
+
inner: observatories::MAUNA_KEA,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// La Silla Observatory (ESO, Chile).
|
|
79
|
+
///
|
|
80
|
+
/// Longitude −70.7346°, Latitude −29.2584°, Altitude 2400 m.
|
|
81
|
+
#[wasm_bindgen(js_name = "laSilla")]
|
|
82
|
+
pub fn la_silla() -> Observer {
|
|
83
|
+
Self {
|
|
84
|
+
inner: observatories::LA_SILLA_OBSERVATORY,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── accessors ────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
/// Longitude in degrees (east positive).
|
|
91
|
+
#[wasm_bindgen(getter, js_name = "lonDeg")]
|
|
92
|
+
pub fn lon_deg(&self) -> f64 {
|
|
93
|
+
self.inner.lon.value()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Latitude in degrees (north positive).
|
|
97
|
+
#[wasm_bindgen(getter, js_name = "latDeg")]
|
|
98
|
+
pub fn lat_deg(&self) -> f64 {
|
|
99
|
+
self.inner.lat.value()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// Height above the WGS84 ellipsoid in metres.
|
|
103
|
+
#[wasm_bindgen(getter, js_name = "heightM")]
|
|
104
|
+
pub fn height_m(&self) -> f64 {
|
|
105
|
+
self.inner.height.value()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// Human-readable string representation.
|
|
109
|
+
pub fn format(&self) -> String {
|
|
110
|
+
format!(
|
|
111
|
+
"Observer(lon={:.4}°, lat={:.4}°, h={:.1} m)",
|
|
112
|
+
self.inner.lon.value(),
|
|
113
|
+
self.inner.lat.value(),
|
|
114
|
+
self.inner.height.value(),
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
}
|