use std::{
array::TryFromSliceError,
cmp::Ordering,
convert::{TryFrom, TryInto},
error::Error,
fs::File,
io::{BufWriter, Write},
iter::{Empty, Map},
marker::PhantomData,
ops::Range,
path::Path,
slice::{from_raw_parts, ChunksExact},
str,
time::SystemTime,
};
use chrono::{DateTime, Utc};
use log::debug;
use memmap2::{Mmap, MmapOptions};
use num_traits::{FromBytes, ToBytes};
use super::super::{
super::{
fits::{
error::FitsError,
read::{
check_keyword_and_parse_uint_val, check_keyword_and_str_val, check_keyword_and_val,
get_str_val_no_quote, next_36_chunks_of_80_bytes, parse_uint_val,
},
},
skymap::{SkyMap, SkyMapValue},
},
LhsRhsBoth, Mom, ZUniqHashT,
};
use crate::nested::map::mom::WritableMom;
pub trait Z4FITS:
ZUniqHashT + ToBytes + FromBytes<Bytes: for<'a> TryFrom<&'a [u8], Error = TryFromSliceError>>
{
}
impl<T> Z4FITS for T
where
T: ZUniqHashT + ToBytes + FromBytes,
<T as FromBytes>::Bytes: for<'a> TryFrom<&'a [u8], Error = TryFromSliceError>,
{
}
pub trait V4FITS:
SkyMapValue
+ ToBytes
+ FromBytes<Bytes: for<'a> TryFrom<&'a [u8], Error = TryFromSliceError>>
+ 'static
{
}
impl<T> V4FITS for T
where
T: SkyMapValue + ToBytes + FromBytes + 'static,
<T as FromBytes>::Bytes: for<'a> TryFrom<&'a [u8], Error = TryFromSliceError>,
{
}
pub enum FITSMom {
U32U32(FitsMMappedCIndex<u32, u32>),
U32F32(FitsMMappedCIndex<u32, f32>),
U32F64(FitsMMappedCIndex<u32, f64>),
U64U32(FitsMMappedCIndex<u64, u32>),
U64F32(FitsMMappedCIndex<u64, f32>),
U64F64(FitsMMappedCIndex<u64, f64>),
}
impl FITSMom {
#[cfg(not(target_arch = "wasm32"))]
pub fn from_fits_file<P: AsRef<Path>>(path: P) -> Result<Self, FitsError> {
let mut file = File::open(path)?;
let mut raw_header = [b' '; 2880];
let mut raw_cards_it = next_36_chunks_of_80_bytes(&mut file, &mut raw_header)?;
let (n_bytes_per_val, n_rows) =
check_keyword_and_val(raw_cards_it.next().unwrap(), b"SIMPLE ", b"T")
.and_then(|()| check_keyword_and_val(raw_cards_it.next().unwrap(), b"BITPIX ", b"8"))
.and_then(|()| check_keyword_and_val(raw_cards_it.next().unwrap(), b"NAXIS ", b"2"))
.and_then(|()| {
check_keyword_and_parse_uint_val::<u64>(raw_cards_it.next().unwrap(), b"NAXIS1 ")
})
.and_then(|n_bytes| {
check_keyword_and_parse_uint_val::<u64>(raw_cards_it.next().unwrap(), b"NAXIS2 ")
.map(move |n_rows| (n_bytes, n_rows))
})?;
let mut end_found = false;
let mut prodtype_found = false;
let mut dtendian_found = false;
let mut hpxoder: Option<u8> = None;
let mut datatype_z: Option<String> = None;
let mut datatype_v: Option<String> = None;
let mut value_name: Option<String> = None;
let mut date: Option<SystemTime> = None;
for kw_record in &mut raw_cards_it {
match &kw_record[0..8] {
b"EXTEND " => check_keyword_and_val(kw_record, b"EXTEND ", b"F"),
b"PRODTYPE" => {
check_keyword_and_str_val(kw_record, b"PRODTYPE", b"HEALPIX MULTI ORDER MAP")
.map(|()| prodtype_found = true)
}
b"HPXORDER" => parse_uint_val::<u8>(kw_record).map(|v| hpxoder = Some(v)),
b"ZUNIQ_DT" => get_str_val_no_quote(kw_record)
.map(|v| datatype_z = Some(String::from_utf8_lossy(v).to_string())),
b"VALUE_DT" => get_str_val_no_quote(kw_record)
.map(|v| datatype_v = Some(String::from_utf8_lossy(v).to_string())),
b"VAL_NAME" => get_str_val_no_quote(kw_record)
.map(|v| value_name = Some(String::from_utf8_lossy(v).to_string())),
b"DTENDIAN" => check_keyword_and_str_val(kw_record, b"DTENDIAN", b"LITTLE")
.map(|()| dtendian_found = true),
b"DATE " => get_str_val_no_quote(kw_record).map(|v| {
date = unsafe { str::from_utf8_unchecked(v) }
.parse::<DateTime<Utc>>()
.ok()
.map(|dt| dt.into())
}),
b"CREATOR " => continue,
b"END " => {
end_found = true;
break;
}
_ => {
debug!("Ignored FITS card: {}", unsafe {
str::from_utf8_unchecked(kw_record)
});
continue;
}
}?;
}
if !end_found {
return Err(FitsError::new_custom(String::from(
"'END' keyword not found in the first 36 primary header cards.",
)));
}
if !(prodtype_found & dtendian_found) {
return Err(FitsError::new_custom(String::from(
"One of the HEALPIX MULTI ORDER MAP mandatory cards is missing in the FITS header!",
)));
}
let value_name = value_name.ok_or_else(|| {
FitsError::new_custom(String::from(
"'VAL_NAME' keyword not found in the primary header cards.",
))
})?;
let depth = hpxoder.ok_or_else(|| {
FitsError::new_custom(String::from(
"'HPXORDER' keyword not found in the primary header cards.",
))
})?;
let n_bytes_data = n_bytes_per_val * n_rows;
let mmap = unsafe {
MmapOptions::new()
.offset(2880)
.len(n_bytes_data as usize)
.map(&file)
.map_err(FitsError::Io)?
};
match (datatype_z.as_deref(), datatype_v.as_deref()) {
(Some(<u32 as ZUniqHashT>::FITS_DATATYPE), Some(<u32 as SkyMapValue>::FITS_DATATYPE)) => {
Ok(FITSMom::U32U32(FitsMMappedCIndex::new(
date, value_name, depth, n_rows, mmap,
)))
}
(Some(<u32 as ZUniqHashT>::FITS_DATATYPE), Some(<f32 as SkyMapValue>::FITS_DATATYPE)) => {
Ok(FITSMom::U32F32(FitsMMappedCIndex::new(
date, value_name, depth, n_rows, mmap,
)))
}
(Some(<u32 as ZUniqHashT>::FITS_DATATYPE), Some(<f64 as SkyMapValue>::FITS_DATATYPE)) => {
Ok(FITSMom::U32F64(FitsMMappedCIndex::new(
date, value_name, depth, n_rows, mmap,
)))
}
(Some(<u64 as ZUniqHashT>::FITS_DATATYPE), Some(<u32 as SkyMapValue>::FITS_DATATYPE)) => {
Ok(FITSMom::U64U32(FitsMMappedCIndex::new(
date, value_name, depth, n_rows, mmap,
)))
}
(Some(<u64 as ZUniqHashT>::FITS_DATATYPE), Some(<f32 as SkyMapValue>::FITS_DATATYPE)) => {
Ok(FITSMom::U64F32(FitsMMappedCIndex::new(
date, value_name, depth, n_rows, mmap,
)))
}
(Some(<u64 as ZUniqHashT>::FITS_DATATYPE), Some(<f64 as SkyMapValue>::FITS_DATATYPE)) => {
Ok(FITSMom::U64F64(FitsMMappedCIndex::new(
date, value_name, depth, n_rows, mmap,
)))
}
(Some(zs), Some(vs)) => Err(FitsError::UnexpectedValue {
keyword: String::from("ZUNIQ_DT or VALUE_DT"),
expected: String::from("One of: u32, u64, f32, f64"),
actual: format!("{} and {}", zs, vs),
}),
_ => Err(FitsError::new_custom(String::from(
"FITS card ZUNIQ_DT or VALUE_DT is missing",
))),
}
}
pub fn to_fits_bintable<W: Write>(&self, writer: W) -> Result<(), FitsError> {
match &self {
FITSMom::U32U32(e) => e.get_mom().to_fits_bintable(writer, &e.value_name),
FITSMom::U32F32(e) => e.get_mom().to_fits_bintable(writer, &e.value_name),
FITSMom::U32F64(e) => e.get_mom().to_fits_bintable(writer, &e.value_name),
FITSMom::U64U32(e) => e.get_mom().to_fits_bintable(writer, &e.value_name),
FITSMom::U64F32(e) => e.get_mom().to_fits_bintable(writer, &e.value_name),
FITSMom::U64F64(e) => e.get_mom().to_fits_bintable(writer, &e.value_name),
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn to_fits_bintable_file<P: AsRef<Path>>(&self, path: P) -> Result<(), FitsError> {
File::create(path)
.map_err(FitsError::Io)
.and_then(|file| self.to_fits_bintable(BufWriter::new(file)))
}
}
#[derive(Debug)]
pub struct FitsMMappedCIndex<Z: Z4FITS, V: V4FITS> {
fits_creation_date: Option<SystemTime>,
value_name: String,
depth: u8,
n_rows: u64,
mmap: Mmap,
_phantom_z: PhantomData<Z>,
_phantom_v: PhantomData<V>,
}
impl<Z: Z4FITS, V: V4FITS> FitsMMappedCIndex<Z, V> {
fn new(
fits_creation_date: Option<SystemTime>,
value_name: String,
depth: u8,
n_rows: u64,
mmap: Mmap,
) -> Self {
assert_eq!(
n_rows * (size_of::<Z>() + size_of::<V>()) as u64,
mmap.len() as u64
);
Self {
fits_creation_date,
value_name,
depth,
n_rows,
mmap,
_phantom_z: PhantomData,
_phantom_v: PhantomData,
}
}
pub fn get_value_name(&self) -> &str {
self.value_name.as_str()
}
pub fn get_fits_creation_date(&self) -> Option<&SystemTime> {
self.fits_creation_date.as_ref()
}
pub fn get_mom(&self) -> MomSliceImpl<'_, Z, V> {
MomSliceImpl::new(
self.depth,
&self.mmap[0..self.n_rows as usize * (size_of::<Z>() + size_of::<V>())],
)
}
}
#[derive(Debug)]
pub struct MomSliceImpl<'b, Z, V>
where
Z: Z4FITS,
V: V4FITS,
{
depth: u8,
bytes: &'b [u8],
_z_type: PhantomData<Z>,
_v_type: PhantomData<V>,
}
impl<'b, Z, V> MomSliceImpl<'b, Z, V>
where
Z: Z4FITS,
V: V4FITS,
{
pub fn new(depth: u8, bytes: &'b [u8]) -> Self {
Self {
depth,
bytes,
_z_type: PhantomData,
_v_type: PhantomData,
}
}
pub(crate) fn get_z(&self, index: usize) -> Z {
let ptr = self.bytes.as_ptr();
Z::from_le_bytes(
&unsafe {
from_raw_parts(
ptr.add(index * <Self as Mom>::ZV_SIZE),
<Self as Mom>::Z_SIZE,
)
}
.try_into()
.unwrap(),
)
}
pub(crate) fn get_entry(&self, index: usize) -> (Z, V) {
let ptr = self.bytes.as_ptr();
let sub_slice = unsafe {
from_raw_parts(
ptr.add(index * <Self as Mom>::ZV_SIZE),
<Self as Mom>::ZV_SIZE,
)
};
let (z, v) = sub_slice.split_at(<Self as Mom>::Z_SIZE);
(
Z::from_le_bytes(&z.try_into().unwrap()),
V::from_le_bytes(&v.try_into().unwrap()),
)
}
fn get_entries(&self, range: Range<usize>) -> <Self as Mom<'b>>::OverlappedEntriesCopy {
let range = range.start * <Self as Mom>::ZV_SIZE..range.end * <Self as Mom>::ZV_SIZE;
self.bytes[range]
.chunks_exact(<Self as Mom>::ZV_SIZE)
.map(|bytes| {
(
Z::from_le_bytes(&bytes[0..<Self as Mom>::Z_SIZE].try_into().unwrap()),
V::from_le_bytes(&bytes[<Self as Mom>::Z_SIZE..].try_into().unwrap()),
)
})
}
fn binary_search(&self, zuniq_at_depth_max: Z) -> Result<usize, usize> {
let mut size = self.len();
if size == 0 {
return Err(0);
}
let mut base = 0usize;
while size > 1 {
let half = size >> 1;
let mid = base + half;
let cmp = self.get_z(mid).cmp(&zuniq_at_depth_max);
base = if cmp == Ordering::Greater { base } else { mid };
size -= half;
}
let cmp = self.get_z(base).cmp(&zuniq_at_depth_max);
if cmp == Ordering::Equal {
Ok(base)
} else {
let result = base + (cmp == Ordering::Less) as usize;
Err(result)
}
}
}
impl<'a, Z, V> Mom<'a> for MomSliceImpl<'a, Z, V>
where
Z: Z4FITS,
V: V4FITS,
{
type ZUniqHType = Z;
type ValueType = V;
type OverlappedEntries = Empty<(Z, &'a V)>;
type OverlappedEntriesCopy = Map<ChunksExact<'a, u8>, fn(&'a [u8]) -> (Z, V)>;
type ZuniqIt = Map<ChunksExact<'a, u8>, fn(&'a [u8]) -> Z>;
type ValuesIt = Empty<&'a V>;
type ValuesCopyIt = Map<ChunksExact<'a, u8>, fn(&'a [u8]) -> V>;
type EntriesIt = Empty<(Z, &'a V)>;
type EntriesCopyIt = Map<ChunksExact<'a, u8>, fn(&'a [u8]) -> (Z, V)>;
type OwnedEntriesIt = Self::EntriesCopyIt;
fn depth_max(&self) -> u8 {
self.depth
}
fn len(&self) -> usize {
self.bytes.len() / <Self as Mom>::ZV_SIZE
}
fn get_cell_containing_unsafe(
&'a self,
_hash_at_depth_max: Self::ZUniqHType,
) -> Option<(Self::ZUniqHType, &'a Self::ValueType)> {
unimplemented!("Unable to get references while directly reading from bytes. Look at the get_copy_of_cell_containing_unsafe method or the MomVecImpl implementation.")
}
fn get_copy_of_cell_containing_unsafe(
&'a self,
zuniq_at_depth_max: Self::ZUniqHType,
) -> Option<(Self::ZUniqHType, Self::ValueType)> {
match self.binary_search(zuniq_at_depth_max) {
Ok(i) => Some(self.get_entry(i)),
Err(i) => {
if i > 0 {
let e = self.get_entry(i - 1);
if Z::are_overlapping(zuniq_at_depth_max, e.0) {
return Some(e);
}
}
if i < self.len() {
let e = self.get_entry(i);
if Z::are_overlapping(zuniq_at_depth_max, e.0) {
return Some(e);
}
}
None
}
}
}
fn get_overlapped_cells(&'a self, _zuniq: Self::ZUniqHType) -> Self::OverlappedEntries {
unimplemented!("Unable to get references while directly reading from bytes. Look at the get_copy_of_overlapped_cells method or the MomVecImpl implementation.")
}
fn get_copy_of_overlapped_cells(
&'a self,
zuniq: Self::ZUniqHType,
) -> Self::OverlappedEntriesCopy {
let mut range = match self.binary_search(zuniq) {
Ok(i) => i..i + 1,
Err(i) => i..i,
};
while range.start - 1 > 0 && Z::are_overlapping(zuniq, self.get_z(range.start - 1)) {
range.start -= 1;
}
while range.end < self.len() && Z::are_overlapping(zuniq, self.get_z(range.end)) {
range.end += 1;
}
self.get_entries(range)
}
fn zuniqs(&'a self) -> Self::ZuniqIt {
self
.bytes
.chunks_exact(<Self as Mom>::ZV_SIZE)
.map(|bytes| Z::from_le_bytes(&bytes[0..<Self as Mom>::Z_SIZE].try_into().unwrap()))
}
fn values(&'a self) -> Self::ValuesIt {
unimplemented!("Unable to get references while directly reading from bytes. Look at the owned_entries method or the MomVecImpl implementation.")
}
fn values_copy(&'a self) -> Self::ValuesCopyIt {
self
.bytes
.chunks_exact(<Self as Mom>::ZV_SIZE)
.map(|bytes| V::from_le_bytes(&bytes[<Self as Mom>::Z_SIZE..].try_into().unwrap()))
}
fn entries(&'a self) -> Self::EntriesIt {
unimplemented!("Unable to get references while directly reading from bytes. Look at the owned_entries method or the MomVecImpl implementation.")
}
fn entries_copy(&'a self) -> Self::EntriesCopyIt {
self
.bytes
.chunks_exact(<Self as Mom>::ZV_SIZE)
.map(|bytes| {
(
Z::from_le_bytes(&bytes[0..<Self as Mom>::Z_SIZE].try_into().unwrap()),
V::from_le_bytes(&bytes[<Self as Mom>::Z_SIZE..].try_into().unwrap()),
)
})
}
fn owned_entries(self) -> Self::OwnedEntriesIt {
self
.bytes
.chunks_exact(<Self as Mom>::ZV_SIZE)
.map(|bytes| {
(
Z::from_le_bytes(&bytes[0..<Self as Mom>::Z_SIZE].try_into().unwrap()),
V::from_le_bytes(&bytes[<Self as Mom>::Z_SIZE..].try_into().unwrap()),
)
})
}
fn from_hpx_sorted_entries<I, M>(_depth: u8, _sorted_entries_it: I, _merger: M) -> Self
where
I: Iterator<Item = (Self::ZUniqHType, Self::ValueType)>,
M: Fn(
u8,
Self::ZUniqHType,
[Self::ValueType; 4],
) -> Result<Self::ValueType, [Self::ValueType; 4]>,
{
unimplemented!(
"Unable to create this object from the merge operation. Look at MomVecImpl instead."
)
}
fn from_hpx_sorted_entries_fallible<E, I, M>(
_depth: u8,
_sorted_entries_it: I,
_merger: M,
) -> Result<Self, E>
where
E: Error,
I: Iterator<Item = Result<(Self::ZUniqHType, Self::ValueType), E>>,
M: Fn(
u8,
Self::ZUniqHType,
[Self::ValueType; 4],
) -> Result<Self::ValueType, [Self::ValueType; 4]>,
{
unimplemented!(
"Unable to create this object from the merge operation. Look at MomVecImpl instead."
)
}
fn from_skymap_ref<'s, S, M>(_skymap: &'s S, _merger: M) -> Self
where
S: SkyMap<'s, HashType = Self::ZUniqHType, ValueType = Self::ValueType>,
M: Fn(u8, Self::ZUniqHType, [&Self::ValueType; 4]) -> Option<Self::ValueType>,
Self::ValueType: 's,
{
unimplemented!("Unable to create this object from a skymap ref. Look at MomVecImpl instead.")
}
fn from_skymap<'s, S, M>(skymap: S, merger: M) -> Self
where
S: SkyMap<'s, HashType = Self::ZUniqHType, ValueType = Self::ValueType>,
M: Fn(
u8,
Self::ZUniqHType,
[Self::ValueType; 4],
) -> Result<Self::ValueType, [Self::ValueType; 4]>,
Self::ValueType: 's,
{
Self::from_hpx_sorted_entries(skymap.depth(), skymap.owned_entries(), merger)
}
fn merge<'s, L, R, S, O, M>(_lhs: L, _rhs: R, _split: S, _op: O, _merge: M) -> Self
where
L: Mom<'s, ZUniqHType = Self::ZUniqHType, ValueType = Self::ValueType>,
R: Mom<'s, ZUniqHType = Self::ZUniqHType, ValueType = Self::ValueType>,
S: Fn(u8, Self::ZUniqHType, Self::ValueType) -> [Self::ValueType; 4],
O: Fn(LhsRhsBoth<Self::ValueType>) -> Option<Self::ValueType>,
M: Fn(
u8,
Self::ZUniqHType,
[Self::ValueType; 4],
) -> Result<Self::ValueType, [Self::ValueType; 4]>,
{
unimplemented!(
"Unable to create this object from the merge operation. Look at MomVecImpl instead."
)
}
}