Display labels

Two modes of display:
* ICRSd & GALACTIC frame set the formatting of grid labels to decimal
with digit precision being computed from the grid step selected
* ICRS frame set the formatting to sexagesimal in the format: deg min
sec.ddd .

This fixes #172
This commit is contained in:
bmatthieu3
2025-03-19 20:19:18 +01:00
committed by Matthieu Baumann
parent ee2eb6e704
commit ebb9d6d3d6
25 changed files with 289 additions and 354 deletions

View File

@@ -20,6 +20,7 @@
showSettingsControl: true,
showStackLayerControl: true,
samp: true,
showCooGrid: true,
});
aladin.addCatalog(A.catalogFromSimbad('M 82', 0.1, {onClick: 'showTable'}));

View File

@@ -1,8 +1,6 @@
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
use std::fmt;
#[wasm_bindgen(raw_module = "../../js/libs/astro/coo.js")]
extern "C" {
#[wasm_bindgen(js_name = Format)]
@@ -28,28 +26,11 @@ extern "C" {
pub fn toDecimal(num: f64, prec: u8) -> String;
}
#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq)]
use std::cmp::Eq;
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase")]
#[wasm_bindgen]
pub enum AngleSerializeFmt {
DMM,
DD,
DMS,
HMS,
}
impl fmt::Display for AngleSerializeFmt {
// This trait requires `fmt` with this exact signature.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Write strictly the first element into the supplied output
// stream: `f`. Returns `fmt::Result` which indicates whether the
// operation succeeded or failed. Note that `write!` uses syntax which
// is very similar to `println!`.
let str = match self {
Self::DMM => "DMM",
Self::DD => "DD",
Self::DMS => "DMS",
Self::HMS => "HMS",
};
write!(f, "{}", str)
}
}
pub enum Formatter {
Sexagesimal,
Decimal
}

View File

@@ -1,12 +1,9 @@
use wasm_bindgen::prelude::*;
use serde::{Deserialize, Serialize};
use crate::angle_fmt::AngleSerializeFmt;
use crate::angle::Formatter;
use super::color::ColorRGB;
#[wasm_bindgen]
#[derive(Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GridCfg {
@@ -22,7 +19,7 @@ pub struct GridCfg {
#[serde(default = "default_enabled")]
pub enabled: Option<bool>,
#[serde(default = "default_fmt")]
pub fmt: Option<AngleSerializeFmt>,
pub fmt: Option<Formatter>,
}
fn default_labels() -> Option<bool> {
@@ -45,6 +42,6 @@ fn default_thickness() -> Option<f32> {
None
}
fn default_fmt() -> Option<AngleSerializeFmt> {
fn default_fmt() -> Option<Formatter> {
None
}

View File

@@ -13,7 +13,7 @@ pub mod resources;
pub mod cell;
pub mod fov;
pub mod image;
pub mod angle_fmt;
pub mod angle;
pub trait Abort {
type Item;

View File

@@ -20,6 +20,7 @@ use crate::{
tile_fetcher::TileFetcherQueue,
time::DeltaTime,
};
use crate::math::angle::ToAngle;
use al_core::image::format::ChannelType;
use wcs::WCS;
@@ -421,7 +422,7 @@ impl App {
let v = cell.vertices();
let proj2screen = |(lon, lat): &(f64, f64)| -> Option<[f64; 2]> {
// 1. convert to xyzw
let xyzw = crate::math::lonlat::radec_to_xyzw(Angle(*lon), Angle(*lat));
let xyzw = crate::math::lonlat::radec_to_xyzw(lon.to_angle(), lat.to_angle());
// 2. get it back to the camera frame system
let xyzw = crate::coosys::apply_coo_system(
CooSystem::ICRS,
@@ -1325,7 +1326,7 @@ impl App {
Ok(())
}
pub(crate) fn get_max_fov(&self) -> f64 {
pub(crate) fn get_max_fov(&self) -> Angle<f64> {
self.projection.aperture_start()
}

View File

@@ -41,7 +41,7 @@ pub fn build_fov_coverage(
let hpx_idxs_iter = vertices_iter.map(|v| {
let (lon, lat) = crate::math::lonlat::xyzw_to_radec(&v);
::healpix::nested::hash(depth, lon.0, lat.0)
::healpix::nested::hash(depth, lon.to_radians(), lat.to_radians())
});
HEALPixCoverage::from_fixed_hpx_cells(depth, hpx_idxs_iter, Some(vertices.len()))

View File

@@ -88,7 +88,6 @@ const MAX_DPI_LIMIT: f32 = 2.0;
use crate::math;
use crate::time::Time;
use crate::Abort;
use crate::ArcDeg;
impl CameraViewPort {
pub fn new(
gl: &WebGlContext,
@@ -97,7 +96,7 @@ impl CameraViewPort {
) -> CameraViewPort {
let last_user_action = UserAction::Starting;
let aperture = Angle(projection.aperture_start());
let aperture = projection.aperture_start();
let w2m = Matrix4::identity();
let m2w = w2m;
@@ -350,12 +349,12 @@ impl CameraViewPort {
_ => true,
};
let aperture_start: Angle<f64> = ArcDeg(proj.aperture_start()).into();
let aperture_start = proj.aperture_start();
self.clip_zoom_factor = if aperture > aperture_start {
//al_core::log(&format!("a: {:?}, as: {:?}", aperture, aperture_start));
if can_unzoom_more {
aperture.0 / aperture_start.0
aperture.to_radians() / aperture_start.to_radians()
} else {
1.0
}
@@ -363,8 +362,8 @@ impl CameraViewPort {
// Compute the new clip zoom factor
let a = aperture.abs();
let v0 = math::lonlat::radec_to_xyzw(-a / 2.0, Angle(0.0));
let v1 = math::lonlat::radec_to_xyzw(a / 2.0, Angle(0.0));
let v0 = math::lonlat::radec_to_xyzw(-a / 2.0, 0.0.to_angle());
let v1 = math::lonlat::radec_to_xyzw(a / 2.0, 0.0.to_angle());
// Vertex in the WCS of the FOV
if self.width < self.height {

View File

@@ -39,8 +39,8 @@ mod tests {
let gal_lonlat =
super::apply_coo_system(CooSystem::ICRS, CooSystem::GAL, &lonlat.vector()).lonlat();
let gal_lon_deg = gal_lonlat.lon().0 * 360.0 / (2.0 * std::f64::consts::PI);
let gal_lat_deg = gal_lonlat.lat().0 * 360.0 / (2.0 * std::f64::consts::PI);
let gal_lon_deg = gal_lonlat.lon().to_degrees();
let gal_lat_deg = gal_lonlat.lat().to_degrees();
assert_delta!(gal_lon_deg, 96.33723581, 1e-3);
assert_delta!(gal_lat_deg, -60.18845577, 1e-3);
@@ -56,8 +56,8 @@ mod tests {
let lonlat: LonLatT<f64> = LonLatT::new(ArcDeg(0.0).into(), ArcDeg(0.0).into());
let j2000_lonlat =
super::apply_coo_system(CooSystem::GAL, CooSystem::ICRS, &lonlat.vector()).lonlat();
let j2000_lon_deg = j2000_lonlat.lon().0 * 360.0 / (2.0 * std::f64::consts::PI);
let j2000_lat_deg = j2000_lonlat.lat().0 * 360.0 / (2.0 * std::f64::consts::PI);
let j2000_lon_deg = j2000_lonlat.lon().to_degrees();
let j2000_lat_deg = j2000_lonlat.lat().to_degrees();
assert_delta!(j2000_lon_deg, 266.40506655, 1e-3);
assert_delta!(j2000_lat_deg, -28.93616241, 1e-3);
@@ -77,8 +77,8 @@ mod tests {
let gal_lonlat = super::apply_coo_system(CooSystem::ICRS, CooSystem::GAL, &icrs_pos);
let gal_lon_deg = gal_lonlat.lon().0 * 360.0 / (2.0 * std::f64::consts::PI);
let gal_lat_deg = gal_lonlat.lat().0 * 360.0 / (2.0 * std::f64::consts::PI);
let gal_lon_deg = gal_lonlat.lon().to_degrees();
let gal_lat_deg = gal_lonlat.lat().to_degrees();
assert_delta!(gal_lon_deg, 0.0, 1e-3);
assert_delta!(gal_lat_deg, 0.0, 1e-3);

View File

@@ -27,15 +27,14 @@ impl HEALPixCoverage {
let lonlat = vertices_iter
.map(|vertex| {
let LonLatT(lon, lat) = vertex.lonlat();
//let (lon, lat) = math::lonlat::xyzw_to_radec(&vertex);
(lon.0, lat.0)
(lon.to_radians(), lat.to_radians())
})
.collect::<Vec<_>>();
let LonLatT(in_lon, in_lat) = inside.lonlat();
let moc = RangeMOC::from_polygon_with_control_point(
&lonlat[..],
(in_lon.0, in_lat.0),
(in_lon.to_radians(), in_lat.to_radians()),
depth,
CellSelection::All,
);
@@ -84,11 +83,11 @@ impl HEALPixCoverage {
pub fn contains_coo(&self, coo: &Vector4<f64>) -> bool {
let (lon, lat) = math::lonlat::xyzw_to_radec(coo);
self.0.is_in(lon.0, lat.0)
self.0.is_in(lon.to_radians(), lat.to_radians())
}
pub fn contains_lonlat(&self, lonlat: &LonLatT<f64>) -> bool {
self.0.is_in(lonlat.lon().0, lonlat.lat().0)
self.0.is_in(lonlat.lon().to_radians(), lonlat.lat().to_radians())
}
// O(log2(N))

View File

@@ -1,5 +1,6 @@
use crate::healpix::cell::HEALPixCell;
use crate::math::{angle::Angle, lonlat::LonLatT};
use crate::math::lonlat::LonLatT;
use crate::math::angle::ToAngle;
/// A simple wrapper around sore core methods
/// of cdshealpix
///
@@ -17,15 +18,15 @@ pub fn vertices_lonlat<S: BaseFloat>(cell: &HEALPixCell) -> [LonLatT<S>; 4] {
let lon = S::from(*lon).unwrap_abort();
let lat = S::from(*lat).unwrap_abort();
(lon, lat)
(lon.to_angle(), lat.to_angle())
})
.unzip();
[
LonLatT::new(Angle(lon[0]), Angle(lat[0])),
LonLatT::new(Angle(lon[1]), Angle(lat[1])),
LonLatT::new(Angle(lon[2]), Angle(lat[2])),
LonLatT::new(Angle(lon[3]), Angle(lat[3])),
LonLatT::new(lon[0], lat[0]),
LonLatT::new(lon[1], lat[1]),
LonLatT::new(lon[2], lat[2]),
LonLatT::new(lon[3], lat[3]),
]
}
use crate::Abort;
@@ -40,13 +41,13 @@ pub fn grid_lonlat<S: BaseFloat>(cell: &HEALPixCell, n_segments_by_side: u16) ->
let lon = S::from(*lon).unwrap_abort();
let lat = S::from(*lat).unwrap_abort();
LonLatT::new(Angle(lon), Angle(lat))
LonLatT::new(lon.to_angle(), lat.to_angle())
})
.collect()
}
pub fn hash_with_dxdy(depth: u8, lonlat: &LonLatT<f64>) -> (u64, f64, f64) {
healpix::nested::hash_with_dxdy(depth, lonlat.lon().0, lonlat.lat().0)
healpix::nested::hash_with_dxdy(depth, lonlat.lon().to_radians(), lonlat.lat().to_radians())
}
pub const MEAN_HPX_CELL_RES: &[f64; 30] = &[

View File

@@ -562,7 +562,7 @@ impl WebClient {
/// the sinus would be 180 degrees.
#[wasm_bindgen(js_name = getMaxFieldOfView)]
pub fn get_max_fov(&mut self) -> f64 {
self.app.get_max_fov()
self.app.get_max_fov().to_degrees()
}
/// Get the clip zoom factor of the view

View File

@@ -1,4 +1,5 @@
use cgmath::BaseFloat;
use crate::Abort;
// ArcDeg wrapper structure
#[derive(Clone, Copy)]
pub struct ArcDeg<T: BaseFloat>(pub T);
@@ -6,23 +7,6 @@ pub struct ArcDeg<T: BaseFloat>(pub T);
//pub const TWICE_PI: f64 = 6.28318530718;
pub const PI: f64 = std::f64::consts::PI;
impl<T> ArcDeg<T>
where
T: BaseFloat,
{
fn get_frac_minutes(&self) -> ArcMin<T> {
let deg = *self;
let frac = deg.fract();
let minutes_per_degree = T::from(60_f32).unwrap_abort();
ArcMin(frac * minutes_per_degree)
}
fn truncate(&mut self) {
*self = Self((*self).trunc());
}
}
use cgmath::{Deg, Rad};
use serde::Deserialize;
// Convert a Rad<T> to an ArcDeg<T>
@@ -71,18 +55,6 @@ where
#[derive(Clone, Copy)]
pub struct ArcHour<T: BaseFloat>(pub T);
impl<T> ArcHour<T>
where
T: BaseFloat,
{
fn get_frac_minutes(&self) -> ArcMin<T> {
let hour = *self;
let frac = hour.fract();
let minutes_per_hour = T::from(60_f64).unwrap_abort();
ArcMin(minutes_per_hour * frac)
}
}
impl<T> From<Rad<T>> for ArcHour<T>
where
@@ -122,19 +94,6 @@ where
#[derive(Clone, Copy)]
pub struct ArcMin<T: BaseFloat>(pub T);
impl<T> ArcMin<T>
where
T: BaseFloat,
{
fn get_frac_seconds(&self) -> ArcSec<T> {
let min: ArcMin<T> = *self;
let frac = min.fract();
let seconds_per_min = T::from(60_f64).unwrap_abort();
ArcSec(seconds_per_min * frac)
}
}
// Convert a Rad<T> to an ArcMin<T>
impl<T> From<Rad<T>> for ArcMin<T>
where
@@ -241,6 +200,8 @@ where
}
}
use al_api::angle::Format;
/*
pub enum SerializeFmt {
DMS,
HMS,
@@ -269,7 +230,7 @@ impl SerializeFmt {
Self::DD => DD::to_string(angle),
}
}
}
}*/
/*pub trait SerializeToString {
fn to_string(&self) -> String;
@@ -284,6 +245,7 @@ where
}
}*/
/*
pub struct DMS;
pub struct HMS;
pub struct DMM;
@@ -315,7 +277,7 @@ impl FormatType for DMM {
result
}
}
use crate::Abort;
impl FormatType for DMS {
fn to_string<S: BaseFloat + ToString>(angle: Angle<S>) -> String {
let angle = Rad(angle.0);
@@ -361,94 +323,106 @@ impl FormatType for HMS {
result
}
}
}*/
#[derive(Clone, Copy, Debug, Eq, Hash, Deserialize)]
#[serde(rename_all = "camelCase")]
#[repr(C)]
pub struct Angle<S: BaseFloat>(pub S);
pub struct Angle<S: BaseFloat> {
pub rad: S,
fmt: AngleFormatter,
}
impl<S> Angle<S>
where
S: BaseFloat,
{
pub fn new<T: Into<Rad<S>>>(angle: T) -> Angle<S> {
let radians: Rad<S> = angle.into();
Angle(radians.0)
Angle { rad: radians.0, fmt: AngleFormatter::default() }
}
pub fn cos(&self) -> S {
self.0.cos()
self.rad.cos()
}
pub fn sin(&self) -> S {
self.0.sin()
self.rad.sin()
}
pub fn tan(&self) -> S {
self.0.tan()
self.rad.tan()
}
pub fn asin(self) -> S {
self.0.asin()
self.rad.asin()
}
pub fn acos(self) -> S {
self.0.acos()
self.rad.acos()
}
pub fn atan(self) -> S {
self.0.atan()
self.rad.atan()
}
pub fn atan2(self, other: Self) -> S {
self.0.atan2(other.0)
self.rad.atan2(other.rad)
}
pub fn floor(self) -> Self {
Angle(self.0.floor())
self.rad.floor().to_angle()
}
pub fn ceil(self) -> Self {
Angle(self.0.ceil())
self.rad.ceil().to_angle()
}
pub fn round(self) -> Self {
Angle(self.0.round())
self.rad.round().to_angle()
}
pub fn trunc(self) -> Self {
Angle(self.0.trunc())
self.rad.trunc().to_angle()
}
pub fn fract(self) -> S {
self.0.fract()
self.rad.fract()
}
pub fn abs(self) -> Self {
Angle(self.0.abs())
self.rad.abs().to_angle()
}
pub fn max(self, other: Self) -> Self {
Angle(self.0.max(other.0))
self.rad.max(other.rad).to_angle()
}
pub fn min(self, other: Self) -> Self {
Angle(self.0.min(other.0))
self.rad.min(other.rad).to_angle()
}
pub fn min_value() -> Self {
Angle(S::min_value())
S::min_value().to_angle()
}
pub fn max_value() -> Self {
Angle(S::max_value())
S::max_value().to_angle()
}
pub fn to_radians(&self) -> S {
self.0
self.rad
}
pub fn to_degrees(&self) -> S {
self.0.to_degrees()
self.rad.to_degrees()
}
pub fn to_hours(&self) -> S {
self.to_degrees() / S::from(15.0).unwrap()
}
pub fn set_format(&mut self, fmt: AngleFormatter) {
self.fmt = fmt;
}
}
@@ -464,7 +438,7 @@ where
S: BaseFloat,
{
fn to_angle(self) -> Angle<S> {
Angle(self)
Angle { rad: self, fmt: Default::default() }
}
}
@@ -474,7 +448,7 @@ where
S: BaseFloat,
{
fn from(rad: Rad<S>) -> Self {
Angle(rad.0)
rad.0.to_angle()
}
}
impl<S> From<Angle<S>> for Rad<S>
@@ -482,7 +456,7 @@ where
S: BaseFloat,
{
fn from(angle: Angle<S>) -> Self {
Rad(angle.0)
Rad(angle.rad)
}
}
@@ -501,7 +475,7 @@ where
{
fn eq(&self, other: &T) -> bool {
let angle: Angle<S> = (*other).into();
angle.0 == self.0
angle.rad == self.rad
}
}
@@ -513,7 +487,7 @@ where
{
fn partial_cmp(&self, other: &T) -> Option<Ordering> {
let angle: Angle<S> = (*other).into();
self.0.partial_cmp(&angle.0)
self.rad.partial_cmp(&angle.rad)
}
}
@@ -524,7 +498,7 @@ where
{
fn from(deg: ArcDeg<S>) -> Self {
let rad: Rad<S> = deg.into();
Angle(rad.0)
rad.0.to_angle()
}
}
impl<S> From<Angle<S>> for ArcDeg<S>
@@ -545,7 +519,7 @@ where
{
fn from(min: ArcMin<S>) -> Self {
let rad: Rad<S> = min.into();
Angle(rad.0)
rad.0.to_angle()
}
}
// Convert from ArcSec<S>
@@ -555,28 +529,11 @@ where
{
fn from(sec: ArcSec<S>) -> Self {
let rad: Rad<S> = sec.into();
Angle(rad.0)
rad.0.to_angle()
}
}
/*
impl<S> PartialEq<S> for Angle<S>
where
S: BaseFloat + !AngleUnit<S>,
{
fn eq(&self, other: &S) -> bool {
self.0 == *other
}
}
*/
use std::cmp::Ordering;
/*impl<S> PartialOrd<S> for Angle<S>
where
S: BaseFloat,
{
fn partial_cmp(&self, other: &S) -> Option<Ordering> {
self.0.partial_cmp(other)
}
}*/
use std::ops::Div;
impl<S> Div for Angle<S>
@@ -586,8 +543,8 @@ where
type Output = Self;
fn div(self, rhs: Self) -> Self::Output {
let angle = self.0 / rhs.0;
Angle(angle)
let rad = self.rad / rhs.rad;
rad.to_angle()
}
}
impl<S> Div<S> for Angle<S>
@@ -597,8 +554,8 @@ where
type Output = Self;
fn div(self, rhs: S) -> Self::Output {
let angle = self.0 / rhs;
Angle(angle)
let rad = self.rad / rhs;
rad.to_angle()
}
}
@@ -610,8 +567,8 @@ where
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
let angle = self.0 * rhs.0;
Angle(angle)
let angle = self.rad * rhs.rad;
angle.to_angle()
}
}
impl<S> Mul<S> for Angle<S>
@@ -621,8 +578,8 @@ where
type Output = Self;
fn mul(self, rhs: S) -> Self::Output {
let angle = self.0 * rhs;
Angle(angle)
let angle = self.rad * rhs;
angle.to_angle()
}
}
@@ -634,8 +591,8 @@ where
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
let angle = self.0 - other.0;
Angle(angle)
let angle = self.rad - other.rad;
angle.to_angle()
}
}
impl<S> Sub<S> for Angle<S>
@@ -645,8 +602,8 @@ where
type Output = Self;
fn sub(self, other: S) -> Self::Output {
let angle = self.0 - other;
Angle(angle)
let angle = self.rad - other;
angle.to_angle()
}
}
@@ -658,8 +615,8 @@ where
type Output = Self;
fn add(self, other: Self) -> Self::Output {
let angle = self.0 + other.0;
Angle(angle)
let angle = self.rad + other.rad;
angle.to_angle()
}
}
impl<S> Add<S> for Angle<S>
@@ -669,8 +626,8 @@ where
type Output = Self;
fn add(self, other: S) -> Self::Output {
let angle = self.0 + other;
Angle(angle)
let angle = self.rad + other;
angle.to_angle()
}
}
@@ -718,8 +675,56 @@ where
type Output = Self;
fn rem(self, other: Self) -> Self::Output {
let angle = self.0 % other.0;
Angle(angle)
let angle = self.rad % other.rad;
angle.to_angle()
}
}
#[derive(PartialEq, Eq, Clone, Copy, Debug, Hash, Deserialize)]
pub enum AngleFormatter {
Sexagesimal {
/// Number of digit of precision for the unit value
/// (interpreted as hours or degrees depending of the hours boolean field)
prec: u8,
/// Whether a '+' is added
plus: bool,
/// HMS or DMS
hours: bool,
},
Decimal {
/// Number of digit of precision
prec: u8,
}
}
impl Default for AngleFormatter {
fn default() -> Self {
AngleFormatter::Decimal { prec: 8 }
}
}
use std::fmt::Display;
impl Display for Angle<f64> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.fmt {
AngleFormatter::Sexagesimal { prec, plus, hours } => {
let unit = if hours {
self.to_hours()
} else {
self.to_degrees()
};
// Round at a specific number of digit of precision
let pw = 10.0_f64.powi(prec as i32);
let unit = (unit * pw).round() / pw;
// Format the unit value to sexagesimal.
// The precision 8 corresponds to the formatting: deg/hour min sec.ddd
write!(f, "{}", Format::toSexagesimal(unit, 8, plus))
},
AngleFormatter::Decimal { prec } => {
write!(f, "{:.1$}°", self.to_degrees(), prec as usize)
}
}
}
}
@@ -730,18 +735,18 @@ where
{
type Output = Self;
fn neg(self) -> Self::Output {
Angle(-self.0)
(-self.rad).to_angle()
}
}
use al_core::{shader::UniformType, WebGlContext};
use web_sys::WebGlUniformLocation;
impl UniformType for Angle<f32> {
fn uniform(gl: &WebGlContext, location: Option<&WebGlUniformLocation>, value: &Self) {
gl.uniform1f(location, value.0);
gl.uniform1f(location, value.rad);
}
}
impl UniformType for Angle<f64> {
fn uniform(gl: &WebGlContext, location: Option<&WebGlUniformLocation>, value: &Self) {
gl.uniform1f(location, value.0 as f32);
gl.uniform1f(location, value.rad as f32);
}
}

View File

@@ -26,8 +26,8 @@ where
/// * ``lon`` - Longitude
/// * ``lat`` - Latitude
pub fn new(mut lon: Angle<S>, lat: Angle<S>) -> LonLatT<S> {
if lon.0 < S::zero() {
lon.0 = lon.0 + S::from(TWICE_PI).unwrap_abort();
if lon.to_radians() < S::zero() {
lon = lon + S::from(TWICE_PI).unwrap_abort();
}
LonLatT(lon, lat)
@@ -156,33 +156,31 @@ where
#[inline]
pub fn ang_between_lonlat<S: BaseFloat>(lonlat1: LonLatT<S>, lonlat2: LonLatT<S>) -> Angle<S> {
let abs_diff_lon = (lonlat1.lon() - lonlat2.lon()).abs();
Angle(
(lonlat1.lat().sin() * lonlat2.lat().sin()
+ lonlat1.lat().cos() * lonlat2.lat().cos() * abs_diff_lon.cos())
.acos(),
)
(lonlat1.lat().sin() * lonlat2.lat().sin()
+ lonlat1.lat().cos() * lonlat2.lat().cos() * abs_diff_lon.cos())
.acos().to_angle()
}
#[inline]
pub fn xyz_to_radec<S: BaseFloat>(v: &Vector3<S>) -> (Angle<S>, Angle<S>) {
let lon = Angle(v.x.atan2(v.z));
let lat = Angle(v.y.atan2((v.x * v.x + v.z * v.z).sqrt()));
let lon = (v.x.atan2(v.z)).to_angle();
let lat = (v.y.atan2((v.x * v.x + v.z * v.z).sqrt())).to_angle();
(lon, lat)
}
#[inline]
pub fn xyzw_to_radec<S: BaseFloat>(v: &Vector4<S>) -> (Angle<S>, Angle<S>) {
let lon = Angle(v.x.atan2(v.z));
let lat = Angle(v.y.atan2((v.x * v.x + v.z * v.z).sqrt()));
let lon = (v.x.atan2(v.z)).to_angle();
let lat = (v.y.atan2((v.x * v.x + v.z * v.z).sqrt())).to_angle();
(lon, lat)
}
#[inline]
pub fn radec_to_xyz<S: BaseFloat>(theta: Angle<S>, delta: Angle<S>) -> Vector3<S> {
let (ds, dc) = delta.0.sin_cos();
let (ts, tc) = theta.0.sin_cos();
let (ds, dc) = delta.to_radians().sin_cos();
let (ts, tc) = theta.to_radians().sin_cos();
Vector3::<S>::new(dc * ts, ds, dc * tc)
}

View File

@@ -19,8 +19,10 @@ use cgmath::Vector2;
pub mod coo_space;
pub mod domain;
use domain::{basic, full::FullScreen};
use crate::math::angle::ToAngle;
use domain::{basic, full::FullScreen};
use crate::math::angle::Angle;
/* S <-> NDC space conversion methods */
pub fn screen_to_ndc_space(
pos_screen_space: &XYScreen<f64>,
@@ -304,7 +306,7 @@ impl ProjectionType {
}
}*/
pub fn bounds_size_ratio(&self) -> f64 {
pub const fn bounds_size_ratio(&self) -> f64 {
match self {
// Zenithal projections
/* TAN, Gnomonic projection */
@@ -355,17 +357,17 @@ impl ProjectionType {
}
}
pub fn aperture_start(&self) -> f64 {
pub fn aperture_start(&self) -> Angle<f64> {
match self {
// Zenithal projections
/* TAN, Gnomonic projection */
ProjectionType::Tan(_) => 150.0,
ProjectionType::Tan(_) => 150.0_f64.to_radians().to_angle(),
/* STG, Stereographic projection */
ProjectionType::Stg(_) => 360.0,
ProjectionType::Stg(_) => 360.0_f64.to_radians().to_angle(),
/* SIN, Orthographic */
ProjectionType::Sin(_) => 180.0,
ProjectionType::Sin(_) => 180.0_f64.to_radians().to_angle(),
/* ZEA, Equal-area */
ProjectionType::Zea(_) => 360.0,
ProjectionType::Zea(_) => 360.0_f64.to_radians().to_angle(),
/* FEYE, Fish-eyes */
//ProjectionType::Feye(_) => 190.0,
/* AIR, */
@@ -379,9 +381,9 @@ impl ProjectionType {
// Pseudo-cylindrical projections
/* AIT, Aitoff */
ProjectionType::Ait(_) => 360.0,
ProjectionType::Ait(_) => 360.0_f64.to_radians().to_angle(),
// MOL, Mollweide */
ProjectionType::Mol(_) => 360.0,
ProjectionType::Mol(_) => 360.0_f64.to_radians().to_angle(),
// PAR, */
//ProjectionType::Par(_) => 360.0,
// SFL, */
@@ -389,7 +391,7 @@ impl ProjectionType {
// Cylindrical projections
// MER, Mercator */
ProjectionType::Mer(_) => 360.0,
ProjectionType::Mer(_) => 360.0_f64.to_radians().to_angle(),
// CAR, */
//ProjectionType::Car(_) => 360.0,
// CEA, */

View File

@@ -2,6 +2,7 @@ use crate::math;
use cgmath::{BaseFloat, InnerSpace};
use cgmath::{Euler, Quaternion};
use cgmath::{Vector3, Vector4};
use crate::math::angle::ToAngle;
#[derive(Clone, Copy, Debug)]
// Internal structure of a rotation, a quaternion
@@ -159,7 +160,7 @@ where
let a = m.x.z.atan2(m.z.z);
let b = (-m.z.y).atan2((S::one() - m.z.y * m.z.y).sqrt());
let c = m.x.y.atan2(m.y.y);
(Angle(a), Angle(b), Angle(c))
(a.to_angle(), b.to_angle(), c.to_angle())
}
}

View File

@@ -1,15 +1,16 @@
use crate::math::angle::Angle;
use cgmath::{BaseFloat, InnerSpace, Vector2, Vector3};
use crate::math::angle::ToAngle;
#[inline]
pub fn angle2<S: BaseFloat>(ab: &Vector2<S>, bc: &Vector2<S>) -> Angle<S> {
Angle((ab.dot(*bc)).acos())
((ab.dot(*bc)).acos()).to_angle()
}
#[inline]
pub fn angle3<S: BaseFloat>(x: &Vector3<S>, y: &cgmath::Vector3<S>) -> Angle<S> {
let theta = x.cross(*y).magnitude().atan2(x.dot(*y));
Angle(theta)
theta.to_angle()
}
#[inline]

View File

@@ -6,12 +6,13 @@ use crate::ProjectionType;
use cgmath::InnerSpace;
use cgmath::Vector3;
use crate::math::angle::SerializeFmt;
use crate::math::lonlat::LonLat;
use crate::math::projection::coo_space::XYScreen;
use crate::math::TWICE_PI;
use crate::math::angle::ToAngle;
use crate::math::angle::AngleFormatter;
use al_api::angle::Formatter;
use cgmath::Vector2;
use core::ops::Range;
@@ -22,7 +23,6 @@ pub enum LabelOptions {
Centered,
OnSide,
}
#[derive(Debug)]
pub struct Label {
// The position
@@ -39,7 +39,8 @@ impl Label {
options: LabelOptions,
camera: &CameraViewPort,
projection: &ProjectionType,
_fmt: &SerializeFmt,
fmt: Formatter,
grid_decimal_prec: u8
) -> Option<Self> {
let fov = camera.get_field_of_view();
let d = if fov.contains_north_pole() {
@@ -76,8 +77,18 @@ impl Label {
lon += TWICE_PI;
}
//let content = fmt.to_string(lon.to_angle());
let content = al_api::angle_fmt::Format::toSexagesimal(lon.to_degrees() / 15.0, 8, false);
let mut angle = lon.to_angle();
let fmt = match fmt {
Formatter::Decimal => {
AngleFormatter::Decimal { prec: grid_decimal_prec }
},
Formatter::Sexagesimal => {
// Sexagesimal formatting for longitudes is HMS
AngleFormatter::Sexagesimal { prec: grid_decimal_prec, plus: false, hours: true }
}
};
angle.set_format(fmt);
let content = angle.to_string();
let position = if !fov.is_allsky() {
d1 + OFF_TANGENT * dt - OFF_BI_TANGENT * db
@@ -101,6 +112,8 @@ impl Label {
options: LabelOptions,
camera: &CameraViewPort,
projection: &ProjectionType,
fmt: Formatter,
grid_decimal_prec: u8
) -> Option<Self> {
let lonlat = match options {
LabelOptions::Centered => {
@@ -130,8 +143,18 @@ impl Label {
let dt = (d2 - d1).normalize();
let db = Vector2::new(dt.y.abs(), dt.x.abs());
//let content = SerializeFmt::DMS.to_string(lonlat.lat());
let content = al_api::angle_fmt::Format::toSexagesimal(lonlat.lat().to_degrees(), 7, false);
let mut angle = lat.to_angle();
let fmt = match fmt {
Formatter::Decimal => {
AngleFormatter::Decimal { prec: grid_decimal_prec }
},
Formatter::Sexagesimal => {
// Sexagesimal formatting for latitudes is DMS with an optional '+' character
AngleFormatter::Sexagesimal { prec: grid_decimal_prec, plus: true, hours: false }
}
};
angle.set_format(fmt);
let content = angle.to_string();
let fov = camera.get_field_of_view();
let position = if !fov.is_allsky() && !fov.contains_pole() {

View File

@@ -1,3 +1,5 @@
use al_api::angle::Formatter;
use super::label::{Label, LabelOptions};
use crate::math::lonlat::LonLat;
use crate::math::sph_geom::region::Intersection;
@@ -7,14 +9,14 @@ use core::ops::Range;
use crate::math::MINUS_HALF_PI;
use crate::ProjectionType;
use super::angle::SerializeFmt;
use crate::math::HALF_PI;
pub fn get_intersecting_meridian(
lon: f64,
camera: &CameraViewPort,
projection: &ProjectionType,
fmt: &SerializeFmt,
fmt: Formatter,
grid_decimal_prec: u8
) -> Option<Meridian> {
let fov = camera.get_field_of_view();
if fov.contains_both_poles() {
@@ -25,6 +27,7 @@ pub fn get_intersecting_meridian(
camera,
projection,
fmt,
grid_decimal_prec
);
Some(meridian)
} else {
@@ -39,6 +42,7 @@ pub fn get_intersecting_meridian(
camera,
projection,
fmt,
grid_decimal_prec
);
Some(meridian)
}
@@ -56,7 +60,7 @@ pub fn get_intersecting_meridian(
lat1..MINUS_HALF_PI
};
Meridian::new(lon, &lat, LabelOptions::OnSide, camera, projection, fmt)
Meridian::new(lon, &lat, LabelOptions::OnSide, camera, projection, fmt, grid_decimal_prec)
}
2 => {
// full intersection
@@ -73,56 +77,18 @@ pub fn get_intersecting_meridian(
camera,
projection,
fmt,
grid_decimal_prec
)
}
_ => {
/*let mut vertices = vertices.into_vec();
// One segment over two will be in the field of view
vertices.push(Vector4::new(0.0, 1.0, 0.0, 1.0));
vertices.push(Vector4::new(0.0, -1.0, 0.0, 1.0));
vertices.sort_by(|i1, i2| {
i1.y.total_cmp(&i2.y)
});
let v1 = &vertices[0];
let v2 = &vertices[1];
// meridian are part of great circles so the mean between v1 & v2 also lies on it
let vm = (v1 + v2).truncate().normalize();
let vertices = if !fov.contains_south_pole() {
&vertices[1..]
} else {
&vertices
};
let line_vertices = vertices.iter().zip(vertices.iter().skip(1))
.step_by(2)
.map(|(i1, i2)| {
line::great_circle_arc::project(
lon,
i1.lat().to_radians(),
lon,
i2.lat().to_radians(),
camera,
projection
)
})
.flatten()
.collect::<Vec<_>>();
let label = Label::from_meridian(&v1.lonlat(), camera, projection, fmt);
*/
Meridian::new(
lon,
&(-HALF_PI..HALF_PI),
LabelOptions::OnSide,
camera,
projection,
fmt,
)
}
_ => Meridian::new(
lon,
&(-HALF_PI..HALF_PI),
LabelOptions::OnSide,
camera,
projection,
fmt,
grid_decimal_prec
)
};
Some(meridian)
@@ -139,7 +105,6 @@ pub struct Meridian {
indices: Vec<Range<usize>>,
label: Option<Label>,
}
impl Meridian {
pub fn new(
lon: f64,
@@ -147,9 +112,10 @@ impl Meridian {
label_options: LabelOptions,
camera: &CameraViewPort,
projection: &ProjectionType,
fmt: &SerializeFmt,
fmt: Formatter,
grid_decimal_prec: u8
) -> Self {
let label = Label::from_meridian(lon, lat, label_options, camera, projection, fmt);
let label = Label::from_meridian(lon, lat, label_options, camera, projection, fmt, grid_decimal_prec);
// Draw the full parallel
let vertices = crate::renderable::line::great_circle_arc::project(
@@ -185,52 +151,6 @@ impl Meridian {
indices.push(start_idx..vertices.len());
/*let mut prev_v = [vertices[0].x as f32, vertices[0].y as f32];
let vertices: Vec<_> = std::iter::once(prev_v)
.chain(
vertices.into_iter().skip(1)
.filter_map(|v| {
let cur_v = [v.x as f32, v.y as f32];
if cur_v == prev_v {
None
} else {
prev_v = cur_v;
Some(cur_v)
}
})
)
.collect();
// Create subsets of vertices referring to different lines
let indices = if vertices.len() >= 3 {
let mut indices = vec![];
let mut v0 = 0;
let mut v1 = 1;
let mut v2 = 2;
let mut s = 0;
let n_segment = vertices.len() - 1;
for i in 0..n_segment {
if Triangle::new(&vertices[v0], &vertices[v1], &vertices[v2]).is_valid(camera) {
indices.push(s..(i+1));
s = i;
}
v0 = v1;
v1 = v2;
v2 = (v2 + 1) % vertices.len();
}
//indices.push(start_line_i..vertices.len());
//vec![0..vertices.len()]
vec![0..2]
} else {
vec![0..vertices.len()]
};*/
Self {
vertices,
indices,

View File

@@ -8,7 +8,6 @@ use al_core::VecData;
use parallel::Parallel;
use crate::camera::CameraViewPort;
use crate::math::angle;
use crate::math::HALF_PI;
use crate::ProjectionType;
use al_api::color::ColorRGBA;
@@ -28,7 +27,7 @@ pub struct ProjetedGrid {
// Render Text Manager
text_renderer: TextRenderManager,
fmt: angle::SerializeFmt,
fmt: Formatter,
//line_style: line::Style,
meridians: Vec<Meridian>,
@@ -41,9 +40,8 @@ use crate::renderable::text::TextRenderManager;
use crate::renderable::Renderer;
use wasm_bindgen::JsValue;
use web_sys::HtmlElement;
use al_api::angle::Formatter;
use self::meridian::Meridian;
impl ProjetedGrid {
pub fn new(gl: WebGlContext, aladin_div: &HtmlElement) -> Result<ProjetedGrid, JsValue> {
let text_renderer = TextRenderManager::new(aladin_div)?;
@@ -58,7 +56,7 @@ impl ProjetedGrid {
let enabled = false;
let label_scale = 1.0;
//let line_style = line::Style::None;
let fmt = angle::SerializeFmt::DMS;
let fmt = Formatter::Decimal;
let thickness = 2.0;
let meridians = Vec::new();
let parallels = Vec::new();
@@ -150,7 +148,7 @@ impl ProjetedGrid {
}
if let Some(fmt) = fmt {
self.fmt = fmt.into();
self.fmt = fmt;
}
if let Some(label_size) = label_size {
@@ -210,6 +208,8 @@ impl ProjetedGrid {
(bbox.get_lon_size() as f64) * step_line_px / (camera.get_width() as f64);
let step_lon = select_fixed_step(step_lon_precised);
let decimal_lon_prec = step_lon.to_degrees().log10().abs().ceil() as u8;
// Add meridians
let start_lon = bbox.lon_min() - (bbox.lon_min() % step_lon);
let mut stop_lon = bbox.lon_max();
@@ -221,7 +221,7 @@ impl ProjetedGrid {
let mut lon = start_lon;
while lon < stop_lon {
if let Some(p) =
meridian::get_intersecting_meridian(lon, camera, projection, &self.fmt)
meridian::get_intersecting_meridian(lon, camera, projection, self.fmt, decimal_lon_prec)
{
meridians.push(p);
}
@@ -235,6 +235,8 @@ impl ProjetedGrid {
(bbox.get_lat_size() as f64) * step_line_px / (camera.get_height() as f64);
let step_lat = select_fixed_step(step_lat_precised);
let decimal_lat_prec = step_lat.to_degrees().log10().abs().ceil() as u8;
let mut start_lat = bbox.lat_min() - (bbox.lat_min() % step_lat);
if start_lat == -HALF_PI {
start_lat += step_lat;
@@ -244,7 +246,7 @@ impl ProjetedGrid {
let mut parallels = vec![];
while lat < stop_lat {
if let Some(p) = parallel::get_intersecting_parallel(lat, camera, projection) {
if let Some(p) = parallel::get_intersecting_parallel(lat, camera, projection, self.fmt, decimal_lat_prec) {
parallels.push(p);
}
lat += step_lat;

View File

@@ -1,3 +1,5 @@
use al_api::angle::Formatter;
use super::label::Label;
use crate::math::projection::ProjectionType;
use crate::math::sph_geom::region::Intersection;
@@ -6,15 +8,17 @@ use crate::CameraViewPort;
use crate::math::lonlat::LonLat;
use crate::math::{PI, TWICE_PI};
use crate::renderable::line;
use core::ops::Range;
pub fn get_intersecting_parallel(
lat: f64,
camera: &CameraViewPort,
projection: &ProjectionType,
fmt: Formatter,
grid_decimal_prec: u8
) -> Option<Parallel> {
let fov = camera.get_field_of_view();
if fov.get_bounding_box().get_lon_size() > PI {
@@ -28,6 +32,8 @@ pub fn get_intersecting_parallel(
camera,
LabelOptions::Centered,
projection,
fmt,
grid_decimal_prec
))
} else {
// Longitude fov < PI
@@ -43,6 +49,8 @@ pub fn get_intersecting_parallel(
camera,
LabelOptions::Centered,
projection,
fmt,
grid_decimal_prec
))
}
Intersection::Intersect { vertices } => {
@@ -65,6 +73,8 @@ pub fn get_intersecting_parallel(
camera,
LabelOptions::OnSide,
projection,
fmt,
grid_decimal_prec
))
}
Intersection::Empty => None,
@@ -89,8 +99,10 @@ impl Parallel {
camera: &CameraViewPort,
label_options: LabelOptions,
projection: &ProjectionType,
fmt: Formatter,
grid_decimal_prec: u8
) -> Self {
let label = Label::from_parallel(lat, lon, label_options, camera, projection);
let label = Label::from_parallel(lat, lon, label_options, camera, projection, fmt, grid_decimal_prec);
// Draw the full parallel
let vertices = if lon.end - lon.start > PI {
@@ -109,24 +121,6 @@ impl Parallel {
line::parallel_arc::project(lat, lon.start, lon.end, camera, projection)
};
/*let mut prev_v = [vertices[0].x as f32, vertices[0].y as f32];
let vertices: Vec<_> = std::iter::once(prev_v)
.chain(
vertices.into_iter().skip(1)
.filter_map(|v| {
let cur_v = [v.x as f32, v.y as f32];
if cur_v == prev_v {
None
} else {
prev_v = cur_v;
Some(cur_v)
}
})
)
.collect();
let indices = vec![0..vertices.len()];
*/
let mut start_idx = 0;
let mut indices = if vertices.len() >= 3 {

View File

@@ -19,7 +19,6 @@ use al_core::VecData;
use al_core::VertexArrayObject;
use al_core::WebGlContext;
use crate::math::angle::Angle;
use crate::ProjectionType;
use crate::camera::CameraViewPort;
@@ -31,6 +30,8 @@ use crate::downloader::request::allsky::Allsky;
use crate::healpix::{cell::HEALPixCell, coverage::HEALPixCoverage};
use crate::renderable::utils::index_patch::DefaultPatchIndexIter;
use crate::time::Time;
use crate::math::angle::ToAngle;
use super::config::HiPSConfig;
use std::collections::HashSet;
@@ -565,7 +566,7 @@ impl HiPS2D {
self.uv_end.extend(uv_end);
self.time_tile_received.push(start_time);
let xyz = crate::math::lonlat::radec_to_xyz(Angle(lon), Angle(lat));
let xyz = crate::math::lonlat::radec_to_xyz(lon.to_angle(), lat.to_angle());
pos.push([xyz.x as f32, xyz.y as f32, xyz.z as f32]);
//pos.push([lon as f32, lat as f32]);
}

View File

@@ -1,19 +1,17 @@
use crate::camera::CameraViewPort;
use crate::math::angle::Angle;
use crate::math::projection::ProjectionType;
use crate::math::vector::dist2;
use crate::HEALPixCell;
use crate::math::angle::ToAngle;
const M: f64 = 280.0 * 280.0;
const N: f64 = 150.0 * 150.0;
const RAP: f64 = 0.7;
fn is_too_large(cell: &HEALPixCell, camera: &CameraViewPort, projection: &ProjectionType) -> bool {
let vertices = cell
.vertices()
.iter()
.filter_map(|(lon, lat)| {
let vertex = crate::math::lonlat::radec_to_xyzw(Angle(*lon), Angle(*lat));
let vertex = crate::math::lonlat::radec_to_xyzw(lon.to_angle(), lat.to_angle());
projection.icrs_celestial_to_screen_space(&vertex, camera)
})
.collect::<Vec<_>>();

View File

@@ -1416,6 +1416,12 @@ export let Aladin = (function () {
this.view.showCatalog(show);
};
/**
* Show/Hide the reticle centered on the view
*
* @memberof Aladin
* @param {boolean} show - True to enable, false to hide
*/
Aladin.prototype.showReticle = function (show) {
this.reticle.update({ show });
};
@@ -1433,6 +1439,12 @@ export let Aladin = (function () {
});
};
/**
* Add a overlay of shapes/footprints to the view
*
* @memberof Aladin
* @param {GraphicOverlay} overlay - The shapes/footprints overlay to add
*/
Aladin.prototype.addOverlay = function (overlay) {
this.view.addOverlay(overlay);
@@ -1441,10 +1453,15 @@ export let Aladin = (function () {
});
};
/**
* Add a Multi-Order Coverage map (MOC) to the view
*
* @memberof Aladin
* @param {MOC} moc - The MOC object to add
*/
Aladin.prototype.addMOC = function (moc) {
this.view.addMOC(moc);
// see MOC.setView for sending it to outside the UI
};
Aladin.prototype.removeUIByName = function(name) {
@@ -3078,12 +3095,5 @@ aladin.displayFITS(
});
};
/*
Aladin.prototype.setReduceDeformations = function (reduce) {
this.reduceDeformations = reduce;
this.view.requestRedraw();
}
*/
return Aladin;
})();

View File

@@ -208,6 +208,7 @@ export let AladinUtils = {
* // returns "36 arcsec"
* Numbers.degreesToString(0.01);
*/
// FIXME: Same as in the libs/astro/angle.js
degreesToString: function(numberDegrees) {
let setPrecision = 3
let degrees = numberDegrees | 0;

View File

@@ -1966,11 +1966,11 @@ export let View = (function () {
}
// Set the grid label format
if (this.cooFrame.label == "ICRSd") {
this.setGridOptions({fmt: "HMS"});
if (this.cooFrame.label == "ICRS") {
this.setGridOptions({fmt: "sexagesimal"});
}
else {
this.setGridOptions({fmt: "DMS"});
this.setGridOptions({fmt: "decimal"});
}
// Get the new view center position (given in icrs)