stc_s/
lib.rs

1use std::{
2  fmt::Write,
3  fmt::{self, Display},
4};
5
6use serde::{Deserialize, Serialize};
7
8use nom::{
9  character::complete::multispace0,
10  combinator::{map, opt},
11  error::{FromExternalError, ParseError},
12  sequence::{preceded, tuple},
13  IResult,
14};
15
16pub mod common;
17pub mod redshift;
18pub mod space;
19pub mod spectral;
20pub mod time;
21pub mod visitor;
22
23use self::{
24  redshift::Redshift,
25  space::Space,
26  spectral::Spectral,
27  time::Time,
28  visitor::{RedshiftVisitor, SpaceVisitor, SpectralVisitor, StcVisitResult, TimeVisitor},
29};
30
31/// Trait used everywhere to propagate Nom errors.
32pub trait NomErr<'a>: ParseError<&'a str> + FromExternalError<&'a str, String> {}
33/// Implements the `NomErr` trait to all elements implementing both `ParseError` and `FromExternalError`.
34impl<'a, T> NomErr<'a> for T where T: ParseError<&'a str> + FromExternalError<&'a str, String> {}
35
36/// Represents a STC-S phrase.
37#[derive(Serialize, Deserialize, Default, Debug, PartialEq)]
38pub struct Stc {
39  #[serde(skip_serializing_if = "Option::is_none")]
40  pub time: Option<Time>,
41  #[serde(skip_serializing_if = "Option::is_none")]
42  pub space: Option<Space>,
43  #[serde(skip_serializing_if = "Option::is_none")]
44  pub spectral: Option<Spectral>,
45  #[serde(skip_serializing_if = "Option::is_none")]
46  pub redshift: Option<Redshift>,
47}
48impl Stc {
49  /// Create a new, empty, STC-S phrase.
50  pub fn new() -> Self {
51    Self::default()
52  }
53  /// Set (or overwrite) the time sub-phrase.
54  pub fn set_time<T: Into<Time>>(mut self, elem: T) -> Self {
55    self.set_time_by_ref(elem);
56    self
57  }
58  /// Set (or overwrite) the time sub-phrase, by reference.
59  pub fn set_time_by_ref<T: Into<Time>>(&mut self, elem: T) {
60    self.time.replace(elem.into());
61  }
62
63  /// Set (or overwrite) the space sub-phrase.
64  pub fn set_space<T: Into<Space>>(mut self, space: T) -> Self {
65    self.set_space_by_ref(space);
66    self
67  }
68  /// Set (or overwrite) the time sub-phrase, by reference.
69  pub fn set_space_by_ref<T: Into<Space>>(&mut self, space: T) {
70    self.space.replace(space.into());
71  }
72
73  /// Set (or overwrite) the spectral sub-phrase.
74  pub fn set_spectral<T: Into<Spectral>>(mut self, spectral: T) -> Self {
75    self.set_spectral_by_ref(spectral);
76    self
77  }
78  /// Set (or overwrite) the spectral sub-phrase, by reference.
79  pub fn set_spectral_by_ref<T: Into<Spectral>>(&mut self, spectral: T) {
80    self.spectral.replace(spectral.into());
81  }
82
83  /// Set (or overwrite) the redshift sub-phrase.
84  pub fn set_redshift<T: Into<Redshift>>(mut self, redshift: T) -> Self {
85    self.set_redshift_by_ref(redshift);
86    self
87  }
88  /// Set (or overwrite) the redshift sub-phrase, by reference.
89  pub fn set_redshift_by_ref<T: Into<Redshift>>(&mut self, redshift: T) {
90    self.redshift.replace(redshift.into());
91  }
92
93  pub fn accept<T, S, P, R>(
94    &self,
95    time_visitor: T,
96    space_visitor: S,
97    spectral_visitor: P,
98    redshift_visitor: R,
99  ) -> StcVisitResult<T, S, P, R>
100  where
101    T: TimeVisitor,
102    S: SpaceVisitor,
103    P: SpectralVisitor,
104    R: RedshiftVisitor,
105  {
106    StcVisitResult::new(
107      self.time.as_ref().map(|time| time.accept(time_visitor)),
108      self.space.as_ref().map(|space| space.accept(space_visitor)),
109      self
110        .spectral
111        .as_ref()
112        .map(|spectral| spectral.accept(spectral_visitor)),
113      self
114        .redshift
115        .as_ref()
116        .map(|redshift| redshift.accept(redshift_visitor)),
117    )
118  }
119
120  /// Parse a complete STC-S Phrase.
121  pub fn parse<'a, E: NomErr<'a>>(input: &'a str) -> IResult<&'a str, Self, E> {
122    map(
123      tuple((
124        opt(preceded(multispace0, Time::parse::<E>)),
125        opt(preceded(multispace0, Space::parse::<E>)),
126        opt(preceded(multispace0, Spectral::parse::<E>)),
127        opt(preceded(multispace0, Redshift::parse::<E>)),
128      )),
129      |(time, space, spectral, redshift)| Self {
130        time,
131        space,
132        spectral,
133        redshift,
134      },
135    )(input)
136  }
137}
138impl Display for Stc {
139  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140    let mut first_sub_phrase = true;
141    if let Some(time) = &self.time {
142      Display::fmt(time, f)?;
143      first_sub_phrase = false
144    }
145    if let Some(space) = &self.space {
146      if first_sub_phrase {
147        first_sub_phrase = false;
148      } else {
149        f.write_char('\n')?;
150      }
151      Display::fmt(space, f)?;
152    }
153    if let Some(spectral) = &self.spectral {
154      if first_sub_phrase {
155        first_sub_phrase = false;
156      } else {
157        f.write_char('\n')?;
158      }
159      Display::fmt(spectral, f)?;
160    }
161    if let Some(redshift) = &self.redshift {
162      if !first_sub_phrase {
163        f.write_char('\n')?;
164      }
165      Display::fmt(redshift, f)?;
166    }
167    Ok(())
168  }
169}
170
171#[cfg(test)]
172mod tests {
173  use super::{
174    common::SpaceTimeRefPos,
175    space::{common::Frame, positioninterval::PositionInterval},
176    *,
177  };
178  use nom::{
179    error::{convert_error, VerboseError},
180    Err,
181  };
182
183  #[test]
184  fn test_api_1() {
185    let stc = Stc::new().set_space(
186      PositionInterval::from_frame(Frame::ICRS)
187        .set_refpos(SpaceTimeRefPos::Geocenter)
188        .set_lo_hi_limits(vec![170.0, -20.0, 190.0, 10.0])
189        .set_resolution(vec![0.0001]),
190    );
191    let s = "PositionInterval ICRS GEOCENTER 170 -20 190 10 Resolution 0.0001";
192    assert_eq!(stc.to_string().as_str(), s);
193  }
194
195  #[test]
196  fn test_from_stcs_doc_1() {
197    test(
198      "Circle ICRS TOPOCENTER 147.6 69.9 0.4",
199      r#"{
200  "space": {
201    "Circle": {
202      "frame": "ICRS",
203      "refpos": "TOPOCENTER",
204      "pos": [
205        147.6,
206        69.9
207      ],
208      "radius": 0.4
209    }
210  }
211}"#,
212      false,
213    );
214  }
215
216  #[test]
217  fn test_from_stcs_doc_2() {
218    test(
219      r#"Time TDB BARYCENTER MJD 50814.0 Position ICRS BARYCENTER 147.3 69.3"#,
220      r#"{
221  "time": {
222    "Time": {
223      "timescale": "TDB",
224      "refpos": "BARYCENTER",
225      "time": {
226        "MJD": "50814.0"
227      }
228    }
229  },
230  "space": {
231    "frame": "ICRS",
232    "refpos": "BARYCENTER",
233    "pos": [
234      147.3,
235      69.3
236    ]
237  }
238}"#,
239      false,
240    );
241  }
242  #[test]
243  fn test_from_stcs_doc_3() {
244    test(
245      r#"TimeInterval TT GEOCENTER 2011-01-01 2012-03-30 Resolution 0.2"#,
246      r#"{
247  "time": {
248    "TimeInterval": {
249      "timescale": "TT",
250      "refpos": "GEOCENTER",
251      "start": [
252        {
253          "Iso": "2011-01-01"
254        }
255      ],
256      "stop": [
257        {
258          "Iso": "2012-03-30"
259        }
260      ],
261      "resolution": 0.2
262    }
263  }
264}"#,
265      false,
266    );
267  }
268
269  #[test]
270  fn test_from_stcs_doc_4() {
271    test(
272      r#"TimeInterval TT GEOCENTER 2011-01-01 2012-03-30 Resolution 0.2
273PositionInterval ICRS GEOCENTER 170 -20 190 10 Resolution 0.0001
274SpectralInterval GEOCENTER 4.95 5 unit GHz Size 0.05"#,
275      r#"{
276  "time": {
277    "TimeInterval": {
278      "timescale": "TT",
279      "refpos": "GEOCENTER",
280      "start": [
281        {
282          "Iso": "2011-01-01"
283        }
284      ],
285      "stop": [
286        {
287          "Iso": "2012-03-30"
288        }
289      ],
290      "resolution": 0.2
291    }
292  },
293  "space": {
294    "frame": "ICRS",
295    "refpos": "GEOCENTER",
296    "lo_hi_limits": [
297      170.0,
298      -20.0,
299      190.0,
300      10.0
301    ],
302    "resolution": [
303      0.0001
304    ]
305  },
306  "spectral": {
307    "Interval": {
308      "refpos": "GEOCENTER",
309      "lolimit": [
310        4.95
311      ],
312      "hilimit": [
313        5.0
314      ],
315      "unit": "GHz",
316      "size": 0.05
317    }
318  }
319}"#,
320      false,
321    );
322  }
323
324  #[test]
325  fn test_from_stcs_doc_5() {
326    test(
327      r#"Union ICRS TOPOCENTER
328( Polygon 147.8 69.2 147.4 69.2 147.3 69.4 147.9 69.4
329Polygon 147.9 69.7 147.6 69.7 147.5 69.9 148 69.9 )"#,
330      r#"{
331  "space": {
332    "Union": {
333      "frame": "ICRS",
334      "refpos": "TOPOCENTER",
335      "elems": [
336        {
337          "Polygon": {
338            "pos": [
339              147.8,
340              69.2,
341              147.4,
342              69.2,
343              147.3,
344              69.4,
345              147.9,
346              69.4
347            ]
348          }
349        },
350        {
351          "Polygon": {
352            "pos": [
353              147.9,
354              69.7,
355              147.6,
356              69.7,
357              147.5,
358              69.9,
359              148.0,
360              69.9
361            ]
362          }
363        }
364      ]
365    }
366  }
367}"#,
368      false,
369    );
370  }
371
372  #[test]
373  fn test_from_stcs_doc_6() {
374    test(
375      r#"Union ICRS TOPOCENTER
376( Circle 180 10 20
377Circle 190 20 20
378Intersection
379( Circle 120 -10 20
380Difference
381( Circle 130 -10 20
382Circle 125 -10 2
383)
384Not
385( Circle 118 -8 3 )
386)
387)"#,
388      r#"{
389  "space": {
390    "Union": {
391      "frame": "ICRS",
392      "refpos": "TOPOCENTER",
393      "elems": [
394        {
395          "Circle": {
396            "pos": [
397              180.0,
398              10.0
399            ],
400            "radius": 20.0
401          }
402        },
403        {
404          "Circle": {
405            "pos": [
406              190.0,
407              20.0
408            ],
409            "radius": 20.0
410          }
411        },
412        {
413          "Intersection": {
414            "elems": [
415              {
416                "Circle": {
417                  "pos": [
418                    120.0,
419                    -10.0
420                  ],
421                  "radius": 20.0
422                }
423              },
424              {
425                "Difference": {
426                  "left": {
427                    "Circle": {
428                      "pos": [
429                        130.0,
430                        -10.0
431                      ],
432                      "radius": 20.0
433                    }
434                  },
435                  "right": {
436                    "Circle": {
437                      "pos": [
438                        125.0,
439                        -10.0
440                      ],
441                      "radius": 2.0
442                    }
443                  }
444                }
445              },
446              {
447                "Not": {
448                  "Circle": {
449                    "pos": [
450                      118.0,
451                      -8.0
452                    ],
453                    "radius": 3.0
454                  }
455                }
456              }
457            ]
458          }
459        }
460      ]
461    }
462  }
463}"#,
464      false,
465    );
466  }
467
468  #[test]
469  fn test_from_stcs_doc_7() {
470    test(
471      r#"TimeInterval TT GEOCENTER 1996-01-01T00:00:00Z 1996-01-01T00:30:00Z
472 Time MJD 50814.0 Error 1.2 Resolution 0.8 PixSize 1024
473Circle ICRS GEOCENTER 179 -11.5 0.5 Position 179 -11.5
474 Error 0.000889 Resolution 0.001778 Size 0.000333 0.000278
475 PixSize 0.000083 0.000083
476Spectral BARYCENTER 1420.4 unit MHz Resolution 10
477RedshiftInterval BARYCENTER VELOCITY OPTICAL 200 2300
478 Redshift 300 Resolution 0.7 PixSize 0.3"#,
479      r#"{
480  "time": {
481    "TimeInterval": {
482      "timescale": "TT",
483      "refpos": "GEOCENTER",
484      "start": [
485        {
486          "Iso": "1996-01-01T00:00:00Z"
487        }
488      ],
489      "stop": [
490        {
491          "Iso": "1996-01-01T00:30:00Z"
492        }
493      ],
494      "time": {
495        "MJD": "50814.0"
496      },
497      "error": 1.2,
498      "resolution": 0.8,
499      "pixsize": 1024.0
500    }
501  },
502  "space": {
503    "Circle": {
504      "frame": "ICRS",
505      "refpos": "GEOCENTER",
506      "pos": [
507        179.0,
508        -11.5
509      ],
510      "radius": 0.5,
511      "position": [
512        179.0,
513        -11.5
514      ],
515      "error": [
516        0.000889
517      ],
518      "resolution": [
519        0.001778
520      ],
521      "size": [
522        0.000333,
523        0.000278
524      ],
525      "pixsize": [
526        0.000083,
527        0.000083
528      ]
529    }
530  },
531  "spectral": {
532    "Value": {
533      "refpos": "BARYCENTER",
534      "value": 1420.4,
535      "unit": "MHz",
536      "resolution": 10.0
537    }
538  },
539  "redshift": {
540    "RedshiftInterval": {
541      "refpos": "BARYCENTER",
542      "type": "VELOCITY",
543      "dopplerdef": "OPTICAL",
544      "lolimit": [
545        200.0
546      ],
547      "hilimit": [
548        2300.0
549      ],
550      "redshift": 300.0,
551      "resolution": 0.7,
552      "pixsize": 0.3
553    }
554  }
555}"#,
556      false,
557    );
558  }
559
560  #[test]
561  fn test_from_stcs_doc_8() {
562    // PAS BON => SpectralInterval BARYCENTER Angstrom 4000 7000 Resolution 300 600"#,
563    test(
564      r#"StartTime TT BARYCENTER 1900-01-01
565Circle ICRS BARYCENTER 148.9 69.1 2.0
566 Resolution 0.0001 0.0001 0.0003 0.0003 Size 0.5 0.5 0.67 0.67
567 PixSize 0.00005 0.00005 0.00015 0.00015
568SpectralInterval BARYCENTER 4000 7000 unit Angstrom Resolution 300 600"#,
569      r#"{
570  "time": {
571    "StartTime": {
572      "timescale": "TT",
573      "refpos": "BARYCENTER",
574      "start": {
575        "Iso": "1900-01-01"
576      }
577    }
578  },
579  "space": {
580    "Circle": {
581      "frame": "ICRS",
582      "refpos": "BARYCENTER",
583      "pos": [
584        148.9,
585        69.1
586      ],
587      "radius": 2.0,
588      "resolution": [
589        0.0001,
590        0.0001,
591        0.0003,
592        0.0003
593      ],
594      "size": [
595        0.5,
596        0.5,
597        0.67,
598        0.67
599      ],
600      "pixsize": [
601        0.00005,
602        0.00005,
603        0.00015,
604        0.00015
605      ]
606    }
607  },
608  "spectral": {
609    "Interval": {
610      "refpos": "BARYCENTER",
611      "lolimit": [
612        4000.0
613      ],
614      "hilimit": [
615        7000.0
616      ],
617      "unit": "Angstrom",
618      "resolution": {
619        "lo": 300.0,
620        "hi": 600.0
621      }
622    }
623  }
624}"#,
625      false,
626    );
627  }
628
629  #[test]
630  fn test_from_tap_section6_1() {
631    test(
632      r#"Circle ICRS GEOCENTER 10 20 0.5"#,
633      r#"{
634  "space": {
635    "Circle": {
636      "frame": "ICRS",
637      "refpos": "GEOCENTER",
638      "pos": [
639        10.0,
640        20.0
641      ],
642      "radius": 0.5
643    }
644  }
645}"#,
646      false,
647    );
648  }
649
650  #[test]
651  fn test_from_tap_section6_2() {
652    test(
653      r#"Position GALACTIC 10 20"#,
654      r#"{
655  "space": {
656    "frame": "GALACTIC",
657    "pos": [
658      10.0,
659      20.0
660    ]
661  }
662}"#,
663      false,
664    );
665  }
666
667  #[test]
668  fn test_from_tap_section6_3() {
669    test(
670      r#"Box CARTESIAN2 3 3 2 2"#,
671      r#"{
672  "space": {
673    "Box": {
674      "frame": "UNKNOWNFrame",
675      "flavor": "CART2",
676      "pos": [
677        3.0,
678        3.0
679      ],
680      "bsize": [
681        2.0,
682        2.0
683      ]
684    }
685  }
686}"#,
687      false,
688    );
689  }
690
691  #[test]
692  fn test_from_tap_section6_4() {
693    test(
694      r#"Box 180 0 2 2"#,
695      r#"{
696  "space": {
697    "Box": {
698      "frame": "UNKNOWNFrame",
699      "pos": [
700        180.0,
701        0.0
702      ],
703      "bsize": [
704        2.0,
705        2.0
706      ]
707    }
708  }
709}"#,
710      false,
711    );
712  }
713
714  #[test]
715  fn test_from_tap_section6_5() {
716    test(
717      r#"Union ICRS ( Polygon 1 4 2 4 2 5 1 5 Polygon 3 4 4 4 4 5 3 5 )"#,
718      r#"{
719  "space": {
720    "Union": {
721      "frame": "ICRS",
722      "elems": [
723        {
724          "Polygon": {
725            "pos": [
726              1.0,
727              4.0,
728              2.0,
729              4.0,
730              2.0,
731              5.0,
732              1.0,
733              5.0
734            ]
735          }
736        },
737        {
738          "Polygon": {
739            "pos": [
740              3.0,
741              4.0,
742              4.0,
743              4.0,
744              4.0,
745              5.0,
746              3.0,
747              5.0
748            ]
749          }
750        }
751      ]
752    }
753  }
754}"#,
755      false,
756    );
757  }
758
759  fn test(ascii_str: &str, json_str: &str, print_json: bool) {
760    match Stc::parse::<VerboseError<&str>>(ascii_str) {
761      Ok((rem, space)) => {
762        // Ensures eveything is parsed
763        assert_eq!(rem, "", "Remaining: {}", rem);
764        // Compare re-serializing into STC-S string
765        assert_eq!(
766          space
767            .to_string()
768            .replace('\n', " ")
769            .replace("  ", " ")
770            .replace(".0 ", " ")
771            .replace(" UNKNOWNFrame", ""),
772          ascii_str
773            .replace('\n', " ")
774            .replace("  ", " ")
775            .replace(".0 ", " ")
776            .replace("CARTESIAN", "CART")
777        );
778        // Serialiaze in JSON
779        let json = serde_json::to_string_pretty(&space).unwrap();
780        if print_json {
781          println!("Json:\n{}", json);
782        }
783        assert_eq!(json, json_str);
784        // JSON round-trip to ensure JSON is well parsed
785        let space2 = serde_json::from_str(&json).unwrap();
786        assert_eq!(space, space2);
787        // YAML: Not possible because: "untagged and internally tagged enums do not support enum input"
788        /*if into_yaml {
789          let yaml = serde_yaml::to_string(&space).unwrap();
790          if print_yaml {
791            println!("Yaml:\n{}", yaml);
792          }
793          let space3 = serde_yaml::from_str(&yaml).unwrap();
794          assert_eq!(space, space3);
795        }*/
796      }
797      Err(err) => {
798        println!("Error: {:#?}", err);
799        match err {
800          Err::Incomplete(e) => println!("Error: {:?}", e),
801          Err::Error(e) => println!("Error: {}", convert_error(ascii_str, e)),
802          Err::Failure(e) => println!("Error: {}", convert_error(ascii_str, e)),
803        };
804        assert!(false)
805      }
806    }
807  }
808}