use std::{fmt, slice::ChunksMut, str};
use log::warn;
use super::{
error::FitsError,
read::{get_keyword, get_str_val_no_quote, parse_uint_val},
write::write_keyword_record,
};
pub trait FitsCard: Sized {
const KEYWORD: &'static [u8; 8];
fn keyword_str() -> &'static str {
unsafe { str::from_utf8_unchecked(Self::KEYWORD) }
}
fn keyword_string() -> String {
unsafe { String::from_utf8_unchecked(Self::KEYWORD.to_vec()) }
}
fn parse_value(keyword_record: &[u8]) -> Result<Self, FitsError> {
Self::specific_parse_value(keyword_record)
}
fn specific_parse_value(keyword_record: &[u8]) -> Result<Self, FitsError>;
fn write_keyword_record(&self, keyword_record: &mut [u8]) -> Result<(), FitsError> {
write_keyword_record(keyword_record, Self::KEYWORD, &self.to_fits_value());
Ok(())
}
fn to_fits_value(&self) -> String;
fn predefine_val_err(parsed_value: &[u8], expected_values: &[&[u8]]) -> FitsError {
FitsError::UnexpectedValue {
keyword: Self::keyword_string(),
expected: format!(
"{:?}",
expected_values
.iter()
.map(|v| unsafe { String::from_utf8_unchecked(v.to_vec()) })
.collect::<Vec<String>>()
),
actual: String::from_utf8_lossy(parsed_value).to_string(),
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Ordering {
Nested,
Ring,
}
impl FitsCard for Ordering {
const KEYWORD: &'static [u8; 8] = b"ORDERING";
fn specific_parse_value(keyword_record: &[u8]) -> Result<Self, FitsError> {
match get_str_val_no_quote(keyword_record)? {
b"NESTED" => Ok(Ordering::Nested),
b"RING" => Ok(Ordering::Ring),
parsed_val => Err(Self::predefine_val_err(parsed_val, &[b"NESTED", b"RING"])),
}
}
fn to_fits_value(&self) -> String {
String::from(match self {
Ordering::Nested => "'NESTED'",
Ordering::Ring => "'RING'",
})
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum CoordSys {
Cel,
Gal,
}
impl FitsCard for CoordSys {
const KEYWORD: &'static [u8; 8] = b"COORDSYS";
fn specific_parse_value(keyword_record: &[u8]) -> Result<Self, FitsError> {
match get_str_val_no_quote(keyword_record)? {
b"C" => Ok(CoordSys::Cel),
b"G" => Ok(CoordSys::Gal),
b"CEL" => {
warn!("COORDSYS value should be 'C', not 'CEL'");
Ok(CoordSys::Cel)
}
b"GAL" => {
warn!("COORDSYS value should be 'G', not 'CAL'");
Ok(CoordSys::Gal)
}
parsed_val => Err(Self::predefine_val_err(parsed_val, &[b"C", b"G"])),
}
}
fn to_fits_value(&self) -> String {
String::from(match self {
CoordSys::Cel => "'C'",
CoordSys::Gal => "'G'",
})
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum PixType {
Healpix,
}
impl FitsCard for PixType {
const KEYWORD: &'static [u8; 8] = b"PIXTYPE ";
fn specific_parse_value(keyword_record: &[u8]) -> Result<Self, FitsError> {
match get_str_val_no_quote(keyword_record)? {
b"HEALPIX" => Ok(PixType::Healpix),
parsed_val => Err(Self::predefine_val_err(parsed_val, &[b"TCB"])),
}
}
fn to_fits_value(&self) -> String {
String::from("'HEALPIX'")
}
}
#[derive(Debug)]
pub struct Order(u8);
impl Order {
pub fn get(&self) -> u8 {
self.0
}
}
impl FitsCard for Order {
const KEYWORD: &'static [u8; 8] = b"ORDER ";
fn specific_parse_value(keyword_record: &[u8]) -> Result<Self, FitsError> {
parse_uint_val::<u8>(keyword_record).map(Self)
}
fn to_fits_value(&self) -> String {
format!("{}", &self.0)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum TForm {
B(Option<u32>), I(Option<u32>), J(Option<u32>), K(Option<u32>), E(Option<u32>), D(Option<u32>), }
impl TForm {
pub fn type_n_bytes(&self) -> usize {
match self {
TForm::B(_) => size_of::<u8>(),
TForm::I(_) => size_of::<u16>(),
TForm::J(_) => size_of::<u32>(),
TForm::K(_) => size_of::<u64>(),
TForm::E(_) => size_of::<f32>(),
TForm::D(_) => size_of::<f64>(),
}
}
pub fn n_bytes(&self) -> usize {
match self {
TForm::B(opt_n) => opt_n.unwrap_or(1) as usize * size_of::<u8>(),
TForm::I(opt_n) => opt_n.unwrap_or(1) as usize * size_of::<u16>(),
TForm::J(opt_n) => opt_n.unwrap_or(1) as usize * size_of::<u32>(),
TForm::K(opt_n) => opt_n.unwrap_or(1) as usize * size_of::<u64>(),
TForm::E(opt_n) => opt_n.unwrap_or(1) as usize * size_of::<f32>(),
TForm::D(opt_n) => opt_n.unwrap_or(1) as usize * size_of::<f64>(),
}
}
pub fn n_pack(&self) -> u32 {
match self {
TForm::B(opt_n) => opt_n.unwrap_or(1),
TForm::I(opt_n) => opt_n.unwrap_or(1),
TForm::J(opt_n) => opt_n.unwrap_or(1),
TForm::K(opt_n) => opt_n.unwrap_or(1),
TForm::E(opt_n) => opt_n.unwrap_or(1),
TForm::D(opt_n) => opt_n.unwrap_or(1),
}
}
fn specific_parse_value(keyword_record: &[u8]) -> Result<Self, FitsError> {
let expected_values = [b"nB", b"nI", b"nJ", b"nK", b"nE", b"nD"];
let val = get_str_val_no_quote(keyword_record)?;
if val.len() == 1 {
match val {
b"B" => Ok(TForm::B(None)),
b"I" => Ok(TForm::I(None)),
b"J" => Ok(TForm::J(None)),
b"K" => Ok(TForm::K(None)),
b"E" => Ok(TForm::E(None)),
b"D" => Ok(TForm::D(None)),
parsed_value => Err(FitsError::UnexpectedValue {
keyword: unsafe { String::from_utf8_unchecked(get_keyword(keyword_record).to_vec()) },
expected: format!(
"{:?}",
expected_values
.iter()
.map(|v| unsafe { String::from_utf8_unchecked(v.to_vec()) })
.collect::<Vec<String>>()
),
actual: String::from_utf8_lossy(parsed_value).to_string(),
}),
}
} else {
let (n, k) = val.split_at(val.len() - 1);
let n_str = unsafe { str::from_utf8_unchecked(n) };
let n = n_str
.parse::<u32>()
.map_err(|err| FitsError::WrongUintValue {
context: n_str.to_string(),
err,
})?;
match k {
b"B" => Ok(TForm::B(Some(n))),
b"I" => Ok(TForm::I(Some(n))),
b"J" => Ok(TForm::J(Some(n))),
b"K" => Ok(TForm::K(Some(n))),
b"E" => Ok(TForm::E(Some(n))),
b"D" => Ok(TForm::D(Some(n))),
parsed_value => Err(FitsError::UnexpectedValue {
keyword: unsafe { String::from_utf8_unchecked(get_keyword(keyword_record).to_vec()) },
expected: format!(
"{:?}",
expected_values
.iter()
.map(|v| unsafe { String::from_utf8_unchecked(v.to_vec()) })
.collect::<Vec<String>>()
),
actual: String::from_utf8_lossy(parsed_value).to_string(),
}),
}
}
}
pub fn to_fits_value(&self) -> String {
match self {
TForm::B(opt_n) => format!(
"'{}B'",
opt_n.map(|n| n.to_string()).unwrap_or(String::from(""))
),
TForm::I(opt_n) => format!(
"'{}I'",
opt_n.map(|n| n.to_string()).unwrap_or(String::from(""))
),
TForm::J(opt_n) => format!(
"'{}J'",
opt_n.map(|n| n.to_string()).unwrap_or(String::from(""))
),
TForm::K(opt_n) => format!(
"'{}K'",
opt_n.map(|n| n.to_string()).unwrap_or(String::from(""))
),
TForm::E(opt_n) => format!(
"'{}E'",
opt_n.map(|n| n.to_string()).unwrap_or(String::from(""))
),
TForm::D(opt_n) => format!(
"'{}D'",
opt_n.map(|n| n.to_string()).unwrap_or(String::from(""))
),
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct TForm1(TForm);
impl TForm1 {
pub fn to_tform(self) -> TForm {
self.0
}
}
impl fmt::Display for TForm1 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} = {}", Self::keyword_str(), self.to_fits_value())
}
}
impl FitsCard for TForm1 {
const KEYWORD: &'static [u8; 8] = b"TFORM1 ";
fn specific_parse_value(keyword_record: &[u8]) -> Result<Self, FitsError> {
TForm::specific_parse_value(keyword_record).map(Self)
}
fn to_fits_value(&self) -> String {
self.0.to_fits_value()
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct TForm2(TForm);
impl TForm2 {
pub fn to_tform(self) -> TForm {
self.0
}
}
impl fmt::Display for TForm2 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} = {}", Self::keyword_str(), self.to_fits_value())
}
}
impl FitsCard for TForm2 {
const KEYWORD: &'static [u8; 8] = b"TFORM2 ";
fn specific_parse_value(keyword_record: &[u8]) -> Result<Self, FitsError> {
TForm::specific_parse_value(keyword_record).map(Self)
}
fn to_fits_value(&self) -> String {
self.0.to_fits_value()
}
}
#[derive(Debug)]
pub struct TType1(String);
impl TType1 {
pub fn get(&self) -> &str {
self.0.as_str()
}
}
impl FitsCard for TType1 {
const KEYWORD: &'static [u8; 8] = b"TTYPE1 ";
fn specific_parse_value(keyword_record: &[u8]) -> Result<Self, FitsError> {
get_str_val_no_quote(keyword_record)
.map(|s| String::from_utf8_lossy(s).to_string())
.map(Self)
}
fn to_fits_value(&self) -> String {
format!("'{}'", &self.0)
}
}
#[derive(Debug)]
pub struct Nside(u32);
impl Nside {
pub fn get(&self) -> u32 {
self.0
}
}
impl FitsCard for Nside {
const KEYWORD: &'static [u8; 8] = b"NSIDE ";
fn specific_parse_value(keyword_record: &[u8]) -> Result<Self, FitsError> {
parse_uint_val::<u32>(keyword_record).map(Self)
}
fn to_fits_value(&self) -> String {
format!("{}", &self.0)
}
}
#[derive(Debug)]
pub struct FirstPix(u64);
impl FirstPix {
pub fn get(&self) -> u64 {
self.0
}
}
impl Default for FirstPix {
fn default() -> Self {
Self(0)
}
}
impl FitsCard for FirstPix {
const KEYWORD: &'static [u8; 8] = b"FIRSTPIX";
fn specific_parse_value(keyword_record: &[u8]) -> Result<Self, FitsError> {
parse_uint_val::<u64>(keyword_record).map(Self)
}
fn to_fits_value(&self) -> String {
format!("{}", &self.0)
}
}
#[derive(Debug)]
pub struct LastPix(u64);
impl LastPix {
pub fn get(&self) -> u64 {
self.0
}
}
impl FitsCard for LastPix {
const KEYWORD: &'static [u8; 8] = b"LASTPIX ";
fn specific_parse_value(keyword_record: &[u8]) -> Result<Self, FitsError> {
parse_uint_val::<u64>(keyword_record).map(Self)
}
fn to_fits_value(&self) -> String {
format!("{}", &self.0)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum IndexSchema {
Implicit,
Explicit,
Sparse,
}
impl FitsCard for IndexSchema {
const KEYWORD: &'static [u8; 8] = b"INDXSCHM";
fn specific_parse_value(keyword_record: &[u8]) -> Result<Self, FitsError> {
match get_str_val_no_quote(keyword_record)? {
b"IMPLICIT" => Ok(IndexSchema::Implicit),
b"EXPLICIT" => Ok(IndexSchema::Explicit),
b"SPARSE" => Ok(IndexSchema::Sparse),
parsed_val => Err(Self::predefine_val_err(
parsed_val,
&[b"IMPLICIT", b"EXPLICIT", b"SPARSE"],
)),
}
}
fn to_fits_value(&self) -> String {
String::from(match self {
IndexSchema::Implicit => "'IMPLICIT'",
IndexSchema::Explicit => "'EXPLICIT'",
IndexSchema::Sparse => "'SPARSE'",
})
}
}
pub trait SkymapCard: FitsCard {
const INDEX: u8;
}
impl SkymapCard for Ordering {
const INDEX: u8 = 0;
}
impl SkymapCard for CoordSys {
const INDEX: u8 = 1;
}
impl SkymapCard for Order {
const INDEX: u8 = 2;
}
impl SkymapCard for PixType {
const INDEX: u8 = 3;
}
impl SkymapCard for TForm1 {
const INDEX: u8 = 4;
}
impl SkymapCard for TForm2 {
const INDEX: u8 = 5;
}
impl SkymapCard for TType1 {
const INDEX: u8 = 6;
}
impl SkymapCard for Nside {
const INDEX: u8 = 7;
}
impl SkymapCard for FirstPix {
const INDEX: u8 = 8;
}
impl SkymapCard for LastPix {
const INDEX: u8 = 9;
}
impl SkymapCard for IndexSchema {
const INDEX: u8 = 10;
}
#[derive(Debug)]
pub(super) struct SkymapKeywordsMap {
entries: [Option<SkymapKeywords>; 11],
}
impl SkymapKeywordsMap {
pub(super) fn new() -> SkymapKeywordsMap {
Self {
entries: [
None, None, None, None, None, None, None, None, None, None, None,
],
}
}
pub(super) fn insert(&mut self, entry: SkymapKeywords) -> Option<SkymapKeywords> {
self.entries[entry.index()].replace(entry)
}
pub(super) fn get<T: SkymapCard>(&self) -> Option<&SkymapKeywords> {
self.entries[T::INDEX as usize].as_ref()
}
#[allow(dead_code)]
pub(super) fn write_all(&self, keyword_records: &mut ChunksMut<u8>) -> Result<(), FitsError> {
for kw in self.entries.iter().filter_map(|v| v.as_ref()) {
kw.write_keyword_record(keyword_records.next().unwrap())?;
}
Ok(())
}
pub(super) fn check_pixtype(&self) -> Result<(), FitsError> {
match self.get::<PixType>() {
Some(SkymapKeywords::PixType(PixType::Healpix)) => Ok(()),
None => Err(FitsError::MissingKeyword {
keyword: PixType::keyword_string(),
}),
_ => unreachable!(), }
}
pub(super) fn check_coordsys(
&self,
expected: CoordSys,
accept_not_found: bool,
) -> Result<(), FitsError> {
match self.get::<CoordSys>() {
Some(SkymapKeywords::CoordSys(actual)) => {
if *actual == expected {
Ok(())
} else {
Err(FitsError::UnexpectedValue {
keyword: CoordSys::keyword_string(),
expected: CoordSys::Cel.to_fits_value(),
actual: actual.to_fits_value(),
})
}
}
None => {
if accept_not_found {
warn!(
"Missing keyword '{}'; Value '{}' is assumed!",
CoordSys::keyword_str(),
expected.to_fits_value()
);
Ok(())
} else {
Err(FitsError::MissingKeyword {
keyword: CoordSys::keyword_string(),
})
}
}
_ => unreachable!(), }
}
pub(super) fn check_ordering(
&self,
expected: Ordering,
accept_not_found: bool,
) -> Result<(), FitsError> {
match self.get::<Ordering>() {
Some(SkymapKeywords::Ordering(actual)) => {
if *actual == expected {
Ok(())
} else {
Err(FitsError::UnexpectedValue {
keyword: Ordering::keyword_string(),
expected: expected.to_fits_value(),
actual: actual.to_fits_value(),
})
}
}
_ => {
if accept_not_found {
warn!(
"Missing keyword '{}'; Value '{}' is assumed!",
Ordering::keyword_str(),
expected.to_fits_value()
);
Ok(())
} else {
Err(FitsError::MissingKeyword {
keyword: Ordering::keyword_string(),
})
}
}
}
}
pub(super) fn check_firstpix(
&self,
expected: u64,
accept_not_found: bool,
) -> Result<(), FitsError> {
match self.get::<FirstPix>() {
Some(SkymapKeywords::FirstPix(FirstPix(actual))) => {
if *actual == expected {
Ok(())
} else {
Err(FitsError::UnexpectedValue {
keyword: FirstPix::keyword_string(),
expected: expected.to_string(),
actual: actual.to_string(),
})
}
}
None => {
if accept_not_found {
warn!(
"Missing keyword '{}'; Value '{}' is assumed!",
FirstPix::keyword_str(),
expected
);
Ok(())
} else {
Err(FitsError::MissingKeyword {
keyword: FirstPix::keyword_string(),
})
}
}
_ => unreachable!(),
}
}
pub(super) fn check_lastpix(
&self,
expected: u64,
accept_not_found: bool,
) -> Result<(), FitsError> {
match self.get::<LastPix>() {
Some(SkymapKeywords::LastPix(LastPix(actual))) => {
if *actual == expected {
Ok(())
} else {
Err(FitsError::UnexpectedValue {
keyword: LastPix::keyword_string(),
expected: expected.to_string(),
actual: actual.to_string(),
})
}
}
None => {
if accept_not_found {
warn!(
"Missing keyword '{}'; Value '{}' is assumed!",
LastPix::keyword_str(),
&expected
);
Ok(())
} else {
Err(FitsError::MissingKeyword {
keyword: LastPix::keyword_string(),
})
}
}
_ => unreachable!(),
}
}
}
#[derive(Debug)]
pub enum SkymapKeywords {
Ordering(Ordering),
CoordSys(CoordSys),
Order(Order),
PixType(PixType),
TForm1(TForm1),
TForm2(TForm2),
TType1(TType1),
Nside(Nside),
FirstPix(FirstPix),
LastPix(LastPix),
IndexSchema(IndexSchema),
}
impl SkymapKeywords {
pub(super) fn is_skymap_kw(keyword_record: &[u8]) -> Result<Option<Self>, FitsError> {
match get_keyword(keyword_record) {
b"ORDERING" => Ordering::parse_value(keyword_record)
.map(SkymapKeywords::Ordering)
.map(Some),
b"COORDSYS" => CoordSys::parse_value(keyword_record)
.map(SkymapKeywords::CoordSys)
.map(Some),
b"ORDER " => Order::parse_value(keyword_record)
.map(SkymapKeywords::Order)
.map(Some),
b"PIXTYPE " => PixType::parse_value(keyword_record)
.map(SkymapKeywords::PixType)
.map(Some),
b"TFORM1 " => TForm1::parse_value(keyword_record)
.map(SkymapKeywords::TForm1)
.map(Some),
b"TFORM2 " => TForm2::parse_value(keyword_record)
.map(SkymapKeywords::TForm2)
.map(Some),
b"TTYPE1 " => TType1::parse_value(keyword_record)
.map(SkymapKeywords::TType1)
.map(Some),
b"NSIDE " => Nside::parse_value(keyword_record)
.map(SkymapKeywords::Nside)
.map(Some),
b"FIRSTPIX" => FirstPix::parse_value(keyword_record)
.map(SkymapKeywords::FirstPix)
.map(Some),
b"LASTPIX " => LastPix::parse_value(keyword_record)
.map(SkymapKeywords::LastPix)
.map(Some),
b"INDXSCHM" => IndexSchema::parse_value(keyword_record)
.map(SkymapKeywords::IndexSchema)
.map(Some),
_ => Ok(None),
}
}
fn index(&self) -> usize {
(match self {
SkymapKeywords::Ordering(_) => Ordering::INDEX,
SkymapKeywords::CoordSys(_) => CoordSys::INDEX,
SkymapKeywords::Order(_) => Order::INDEX,
SkymapKeywords::PixType(_) => PixType::INDEX,
SkymapKeywords::TForm1(_) => TForm1::INDEX,
SkymapKeywords::TForm2(_) => TForm2::INDEX,
SkymapKeywords::TType1(_) => TType1::INDEX,
SkymapKeywords::Nside(_) => Nside::INDEX,
SkymapKeywords::FirstPix(_) => FirstPix::INDEX,
SkymapKeywords::LastPix(_) => LastPix::INDEX,
SkymapKeywords::IndexSchema(_) => IndexSchema::INDEX,
}) as usize
}
pub(super) fn keyword(&self) -> &'static [u8; 8] {
match self {
SkymapKeywords::Ordering(_) => Ordering::KEYWORD,
SkymapKeywords::CoordSys(_) => CoordSys::KEYWORD,
SkymapKeywords::Order(_) => Order::KEYWORD,
SkymapKeywords::PixType(_) => PixType::KEYWORD,
SkymapKeywords::TForm1(_) => TForm1::KEYWORD,
SkymapKeywords::TForm2(_) => TForm2::KEYWORD,
SkymapKeywords::TType1(_) => TType1::KEYWORD,
SkymapKeywords::Nside(_) => Nside::KEYWORD,
SkymapKeywords::FirstPix(_) => FirstPix::KEYWORD,
SkymapKeywords::LastPix(_) => LastPix::KEYWORD,
SkymapKeywords::IndexSchema(_) => IndexSchema::KEYWORD,
}
}
pub(super) fn keyword_str(&self) -> &str {
unsafe { str::from_utf8_unchecked(self.keyword()) }.trim_end()
}
fn write_keyword_record(&self, keyword_record: &mut [u8]) -> Result<(), FitsError> {
match self {
SkymapKeywords::Ordering(kw) => kw.write_keyword_record(keyword_record),
SkymapKeywords::CoordSys(kw) => kw.write_keyword_record(keyword_record),
SkymapKeywords::Order(kw) => kw.write_keyword_record(keyword_record),
SkymapKeywords::PixType(kw) => kw.write_keyword_record(keyword_record),
SkymapKeywords::TForm1(kw) => kw.write_keyword_record(keyword_record),
SkymapKeywords::TForm2(kw) => kw.write_keyword_record(keyword_record),
SkymapKeywords::TType1(kw) => kw.write_keyword_record(keyword_record),
SkymapKeywords::Nside(kw) => kw.write_keyword_record(keyword_record),
SkymapKeywords::FirstPix(kw) => kw.write_keyword_record(keyword_record),
SkymapKeywords::LastPix(kw) => kw.write_keyword_record(keyword_record),
SkymapKeywords::IndexSchema(kw) => kw.write_keyword_record(keyword_record),
}
}
}