use pyo3::prelude::*;
use pyo3::types::PyDict;
use pyo3::types::{PyBytes, PyList, PyTuple};
use numpy::{PyArray1, PyReadonlyArray1};
use crate::itrfcoord::ITRFCoord;
use crate::types::Vec3;
use super::Quaternion;
use super::pyutils::*;
#[pyclass(name = "itrfcoord", module = "satkit")]
#[derive(Clone)]
pub struct PyITRFCoord {
pub inner: ITRFCoord,
}
#[pymethods]
impl PyITRFCoord {
#[new]
#[pyo3(signature=(*args, **kwargs))]
fn new(args: &Bound<'_, PyTuple>, mut kwargs: Option<&Bound<'_, PyDict>>) -> PyResult<Self> {
if kwargs.is_some() {
use std::f64::consts::PI;
let mut latitude_deg: Option<f64> = kwargs_or_none(&mut kwargs, "latitude_deg")?;
latitude_deg = match kwargs_or_none::<f64>(&mut kwargs, "latitude_rad")? {
None => latitude_deg,
Some(v) => Some(v * 180.0 / PI),
};
let mut longitude_deg: Option<f64> = kwargs_or_none(&mut kwargs, "longitude_deg")?;
longitude_deg = match kwargs_or_none::<f64>(&mut kwargs, "longitude_rad")? {
None => longitude_deg,
Some(v) => Some(v * 180.0 / PI),
};
let mut altitude: f64 = kwargs_or_default(&mut kwargs, "altitude", 0.0)?;
altitude = match kwargs_or_none(&mut kwargs, "height")? {
None => altitude,
Some(v) => v,
};
if latitude_deg.is_none() || longitude_deg.is_none() {
return Err(pyo3::exceptions::PyTypeError::new_err(
"Must set latitude, longitude",
));
}
Ok(PyITRFCoord {
inner: ITRFCoord::from_geodetic_deg(
latitude_deg.unwrap(),
longitude_deg.unwrap(),
altitude,
),
})
} else {
if args.len() == 3 {
let x = args.get_item(0)?.extract::<f64>()?;
let y = args.get_item(1)?.extract::<f64>()?;
let z = args.get_item(2)?.extract::<f64>()?;
Ok(PyITRFCoord {
inner: ITRFCoord::from_slice(&[x, y, z]).unwrap(),
})
} else if args.len() == 1 {
if args.get_item(0)?.is_instance_of::<PyList>() {
match args.get_item(0)?.extract::<Vec<f64>>() {
Ok(xl) => {
if xl.len() != 3 {
return Err(pyo3::exceptions::PyTypeError::new_err(
"Invalid number of elements",
));
}
Ok(PyITRFCoord {
inner: ITRFCoord::from_slice(&xl).unwrap(),
})
}
Err(e) => Err(e),
}
} else if args.get_item(0)?.is_instance_of::<PyArray1<f64>>() {
let xv = args
.get_item(0)?
.extract::<PyReadonlyArray1<f64>>()
.unwrap();
if xv.len().unwrap() != 3 {
return Err(pyo3::exceptions::PyTypeError::new_err(
"Invalid number of elements",
));
}
Ok(PyITRFCoord {
inner: ITRFCoord::from_slice(xv.as_slice().unwrap()).unwrap(),
})
} else {
return Err(pyo3::exceptions::PyTypeError::new_err(
"First input must be float, 3-element list of floats, or 3-element numpy array of float"
));
}
} else {
return Err(pyo3::exceptions::PyTypeError::new_err(
"First input must be float, 3-element list of floats, or 3-element numpy array of float"
));
}
}
}
#[getter]
fn get_latitude_deg(&self) -> f64 {
self.inner.latitude_deg()
}
#[getter]
fn get_longitude_deg(&self) -> f64 {
self.inner.longitude_deg()
}
#[getter]
fn get_latitude_rad(&self) -> f64 {
self.inner.latitude_rad()
}
#[getter]
fn get_longitude_rad(&self) -> f64 {
self.inner.longitude_rad()
}
#[getter]
fn get_height(&self) -> f64 {
self.inner.hae()
}
#[getter]
fn get_altitude(&self) -> f64 {
self.inner.hae()
}
#[getter]
fn get_geodetic_rad(&self) -> (f64, f64, f64) {
self.inner.to_geodetic_rad()
}
#[getter]
fn get_geodetic_deg(&self) -> (f64, f64, f64) {
self.inner.to_geodetic_deg()
}
#[getter]
fn get_vector(&self) -> PyObject {
pyo3::Python::with_gil(|py| -> PyObject {
numpy::PyArray::from_slice_bound(py, self.inner.itrf.data.as_slice()).to_object(py)
})
}
fn __str__(&self) -> String {
let (lat, lon, hae) = self.inner.to_geodetic_deg();
format!(
"ITRFCoord(lat: {:8.4} deg, lon: {:8.4} deg, hae: {:5.2} km)",
lat,
lon,
hae / 1.0e3
)
}
fn __repr__(&self) -> String {
self.__str__()
}
#[getter]
fn get_qned2itrf(&self) -> Quaternion {
Quaternion {
inner: self.inner.q_ned2itrf(),
}
}
#[getter]
fn get_qenu2itrf(&self) -> Quaternion {
Quaternion {
inner: self.inner.q_enu2itrf(),
}
}
fn to_enu(&self, other: &Self) -> PyObject {
let v: Vec3 = self.inner.q_enu2itrf().conjugate() * (self.inner.itrf - other.inner.itrf);
pyo3::Python::with_gil(|py| -> PyObject {
numpy::PyArray::from_slice_bound(py, v.data.as_slice()).to_object(py)
})
}
fn to_ned(&self, other: &Self) -> PyObject {
let v: Vec3 = self.inner.q_ned2itrf().conjugate() * (self.inner.itrf - other.inner.itrf);
pyo3::Python::with_gil(|py| -> PyObject {
numpy::PyArray::from_slice_bound(py, v.data.as_slice()).to_object(py)
})
}
fn geodesic_distance(&self, other: &Self) -> (f64, f64, f64) {
self.inner.geodesic_distance(&other.inner)
}
fn move_with_heading(&self, distance: f64, heading_rad: f64) -> PyITRFCoord {
PyITRFCoord {
inner: self.inner.move_with_heading(distance, heading_rad),
}
}
fn __getnewargs_ex__<'a>(&self, py: Python<'a>) -> (Bound<'a, PyTuple>, Bound<'a, PyDict>) {
let d = PyDict::new_bound(py);
let tp = PyTuple::new_bound(py, vec![0.0, 0.0, 0.0]);
(tp, d)
}
fn __setstate__(&mut self, py: Python, s: Py<PyBytes>) -> PyResult<()> {
let s = s.as_bytes(py);
if s.len() != 24 {
return Err(pyo3::exceptions::PyTypeError::new_err(
"Invalid serialization length",
));
}
let x = f64::from_le_bytes(s[0..8].try_into()?);
let y = f64::from_le_bytes(s[8..16].try_into()?);
let z = f64::from_le_bytes(s[16..24].try_into()?);
self.inner.itrf = nalgebra::Vector3::<f64>::new(x, y, z);
Ok(())
}
fn __getstate__(&self, py: Python) -> PyResult<PyObject> {
let mut raw = [0 as u8; 24];
raw[0..8].clone_from_slice(f64::to_le_bytes(self.inner.itrf[0]).as_slice());
raw[8..16].clone_from_slice(f64::to_le_bytes(self.inner.itrf[1]).as_slice());
raw[16..24].clone_from_slice(f64::to_le_bytes(self.inner.itrf[2]).as_slice());
Ok(pyo3::types::PyBytes::new_bound(py, &raw).to_object(py))
}
fn __sub__(&self, other: &PyITRFCoord) -> PyObject {
let vout = self.inner - other.inner;
pyo3::Python::with_gil(|py| -> PyObject {
let vnd = PyArray1::<f64>::from_vec_bound(py, vec![vout[0], vout[1], vout[2]]);
vnd.into_py(py)
})
}
}
impl IntoPy<PyObject> for ITRFCoord {
fn into_py(self, py: Python<'_>) -> PyObject {
PyITRFCoord { inner: self }.into_py(py)
}
}
impl<'b> From<&'b PyITRFCoord> for &'b ITRFCoord {
fn from<'a>(s: &'a PyITRFCoord) -> &'a ITRFCoord {
&s.inner
}
}