mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 22:10:05 +00:00 
			
		
		
		
	Text product message
This commit is contained in:
		
							parent
							
								
									03fdd99585
								
							
						
					
					
						commit
						55d856a745
					
				
					 5 changed files with 418 additions and 0 deletions
				
			
		
							
								
								
									
										38
									
								
								wxdata/include/scwx/awips/text_product_message.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								wxdata/include/scwx/awips/text_product_message.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <scwx/awips/message.hpp> | ||||||
|  | 
 | ||||||
|  | #include <cstdint> | ||||||
|  | #include <memory> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace awips | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | class TextProductMessageImpl; | ||||||
|  | 
 | ||||||
|  | class TextProductMessage : public Message | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit TextProductMessage(); | ||||||
|  |    ~TextProductMessage(); | ||||||
|  | 
 | ||||||
|  |    TextProductMessage(const TextProductMessage&) = delete; | ||||||
|  |    TextProductMessage& operator=(const TextProductMessage&) = delete; | ||||||
|  | 
 | ||||||
|  |    TextProductMessage(TextProductMessage&&) noexcept; | ||||||
|  |    TextProductMessage& operator=(TextProductMessage&&) noexcept; | ||||||
|  | 
 | ||||||
|  |    size_t data_size() const; | ||||||
|  | 
 | ||||||
|  |    bool Parse(std::istream& is) override; | ||||||
|  | 
 | ||||||
|  |    static std::shared_ptr<TextProductMessage> Create(std::istream& is); | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  |    std::unique_ptr<TextProductMessageImpl> p; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | } // namespace awips
 | ||||||
|  | } // namespace scwx
 | ||||||
|  | @ -8,6 +8,7 @@ namespace Characters | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
| constexpr char DEGREE = static_cast<char>(0xb0); | constexpr char DEGREE = static_cast<char>(0xb0); | ||||||
|  | constexpr char ETX    = static_cast<char>(0x03); | ||||||
| 
 | 
 | ||||||
| } // namespace Characters
 | } // namespace Characters
 | ||||||
| } // namespace common
 | } // namespace common
 | ||||||
|  |  | ||||||
							
								
								
									
										373
									
								
								wxdata/source/scwx/awips/text_product_message.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										373
									
								
								wxdata/source/scwx/awips/text_product_message.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,373 @@ | ||||||
|  | #include <scwx/awips/text_product_message.hpp> | ||||||
|  | #include <scwx/awips/wmo_header.hpp> | ||||||
|  | #include <scwx/common/characters.hpp> | ||||||
|  | #include <scwx/util/streams.hpp> | ||||||
|  | 
 | ||||||
|  | #include <istream> | ||||||
|  | #include <regex> | ||||||
|  | #include <string> | ||||||
|  | 
 | ||||||
|  | #include <boost/log/trivial.hpp> | ||||||
|  | 
 | ||||||
|  | namespace scwx | ||||||
|  | { | ||||||
|  | namespace awips | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | static const std::string logPrefix_ = "[scwx::awips::text_product_message] "; | ||||||
|  | 
 | ||||||
|  | // Issuance date/time takes one of the following forms:
 | ||||||
|  | // * <hhmm>_xM_<tz>_day_mon_<dd>_year
 | ||||||
|  | // * <hhmm>_UTC_day_mon_<dd>_year
 | ||||||
|  | // Segment Header only:
 | ||||||
|  | // * <hhmm>_xM_<tz1>_day_mon_<dd>_year_/<hhmm>_xM_<tz2>_day_mon_<dd>_year/
 | ||||||
|  | // Look for hhmm (xM|UTC) to key the date/time string
 | ||||||
|  | static const std::regex reDateTimeString {"^[0-9]{3,4} ([AP]M|UTC)"}; | ||||||
|  | 
 | ||||||
|  | struct Vtec | ||||||
|  | { | ||||||
|  |    std::string pVtec_; | ||||||
|  |    std::string hVtec_; | ||||||
|  | 
 | ||||||
|  |    Vtec() : pVtec_ {}, hVtec_ {} {} | ||||||
|  | 
 | ||||||
|  |    Vtec(const Vtec&) = delete; | ||||||
|  |    Vtec& operator=(const Vtec&) = delete; | ||||||
|  | 
 | ||||||
|  |    Vtec(Vtec&&) noexcept = default; | ||||||
|  |    Vtec& operator=(Vtec&&) noexcept = default; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct SegmentHeader | ||||||
|  | { | ||||||
|  |    std::string              ugcString_; | ||||||
|  |    std::vector<Vtec>        vtecString_; | ||||||
|  |    std::vector<std::string> ugcNames_; | ||||||
|  |    std::string              issuanceDateTime_; | ||||||
|  | 
 | ||||||
|  |    SegmentHeader() : | ||||||
|  |        ugcString_ {}, vtecString_ {}, ugcNames_ {}, issuanceDateTime_ {} | ||||||
|  |    { | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    SegmentHeader(const SegmentHeader&) = delete; | ||||||
|  |    SegmentHeader& operator=(const SegmentHeader&) = delete; | ||||||
|  | 
 | ||||||
|  |    SegmentHeader(SegmentHeader&&) noexcept = default; | ||||||
|  |    SegmentHeader& operator=(SegmentHeader&&) noexcept = default; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct Segment | ||||||
|  | { | ||||||
|  |    std::optional<SegmentHeader> header_; | ||||||
|  |    std::vector<std::string>     productContent_; | ||||||
|  | 
 | ||||||
|  |    Segment() : header_ {}, productContent_ {} {} | ||||||
|  | 
 | ||||||
|  |    Segment(const Segment&) = delete; | ||||||
|  |    Segment& operator=(const Segment&) = delete; | ||||||
|  | 
 | ||||||
|  |    Segment(Segment&&) noexcept = default; | ||||||
|  |    Segment& operator=(Segment&&) noexcept = default; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static std::vector<std::string>     ParseProductContent(std::istream& is); | ||||||
|  | void                                SkipBlankLines(std::istream& is); | ||||||
|  | bool                                TryParseEndOfProduct(std::istream& is); | ||||||
|  | static std::vector<std::string>     TryParseMndHeader(std::istream& is); | ||||||
|  | static std::optional<SegmentHeader> TryParseSegmentHeader(std::istream& is); | ||||||
|  | static std::optional<Vtec>          TryParseVtecString(std::istream& is); | ||||||
|  | 
 | ||||||
|  | class TextProductMessageImpl | ||||||
|  | { | ||||||
|  | public: | ||||||
|  |    explicit TextProductMessageImpl() : wmoHeader_ {} {} | ||||||
|  |    ~TextProductMessageImpl() = default; | ||||||
|  | 
 | ||||||
|  |    std::shared_ptr<WmoHeader>            wmoHeader_; | ||||||
|  |    std::vector<std::string>              mndHeader_; | ||||||
|  |    std::vector<std::shared_ptr<Segment>> segments_; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | TextProductMessage::TextProductMessage() : | ||||||
|  |     p(std::make_unique<TextProductMessageImpl>()) | ||||||
|  | { | ||||||
|  | } | ||||||
|  | TextProductMessage::~TextProductMessage() = default; | ||||||
|  | 
 | ||||||
|  | TextProductMessage::TextProductMessage(TextProductMessage&&) noexcept = default; | ||||||
|  | TextProductMessage& | ||||||
|  | TextProductMessage::operator=(TextProductMessage&&) noexcept = default; | ||||||
|  | 
 | ||||||
|  | size_t TextProductMessage::data_size() const | ||||||
|  | { | ||||||
|  |    return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool TextProductMessage::Parse(std::istream& is) | ||||||
|  | { | ||||||
|  |    bool dataValid = true; | ||||||
|  | 
 | ||||||
|  |    p->wmoHeader_ = std::make_shared<WmoHeader>(); | ||||||
|  |    dataValid     = p->wmoHeader_->Parse(is); | ||||||
|  | 
 | ||||||
|  |    for (size_t i = 0; dataValid && !is.eof(); i++) | ||||||
|  |    { | ||||||
|  |       if (i != 0 && TryParseEndOfProduct(is)) | ||||||
|  |       { | ||||||
|  |          break; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       std::shared_ptr<Segment> segment = std::make_shared<Segment>(); | ||||||
|  | 
 | ||||||
|  |       if (i == 0) | ||||||
|  |       { | ||||||
|  |          if (is.peek() != '\r') | ||||||
|  |          { | ||||||
|  |             segment->header_ = TryParseSegmentHeader(is); | ||||||
|  |          } | ||||||
|  | 
 | ||||||
|  |          SkipBlankLines(is); | ||||||
|  | 
 | ||||||
|  |          p->mndHeader_ = TryParseMndHeader(is); | ||||||
|  |          SkipBlankLines(is); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (!segment->header_.has_value()) | ||||||
|  |       { | ||||||
|  |          segment->header_ = TryParseSegmentHeader(is); | ||||||
|  |          SkipBlankLines(is); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       segment->productContent_ = ParseProductContent(is); | ||||||
|  |       SkipBlankLines(is); | ||||||
|  | 
 | ||||||
|  |       if (segment->header_.has_value() || !segment->productContent_.empty()) | ||||||
|  |       { | ||||||
|  |          p->segments_.push_back(std::move(segment)); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return dataValid; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::vector<std::string> ParseProductContent(std::istream& is) | ||||||
|  | { | ||||||
|  |    std::vector<std::string> productContent; | ||||||
|  |    std::string              line; | ||||||
|  | 
 | ||||||
|  |    while (!is.eof() && is.peek() != common::Characters::ETX) | ||||||
|  |    { | ||||||
|  |       util::getline(is, line); | ||||||
|  | 
 | ||||||
|  |       productContent.push_back(line); | ||||||
|  | 
 | ||||||
|  |       if (line.starts_with("$$")) | ||||||
|  |       { | ||||||
|  |          // End of Product or Product Segment Code
 | ||||||
|  |          break; | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    while (!productContent.empty() && productContent.back().empty()) | ||||||
|  |    { | ||||||
|  |       productContent.pop_back(); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return productContent; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void SkipBlankLines(std::istream& is) | ||||||
|  | { | ||||||
|  |    std::string line; | ||||||
|  | 
 | ||||||
|  |    while (is.peek() == '\r') | ||||||
|  |    { | ||||||
|  |       util::getline(is, line); | ||||||
|  |    } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool TryParseEndOfProduct(std::istream& is) | ||||||
|  | { | ||||||
|  |    std::string    line; | ||||||
|  |    std::streampos isBegin     = is.tellg(); | ||||||
|  |    bool           endOfStream = false; | ||||||
|  | 
 | ||||||
|  |    if (is.peek() == common::Characters::ETX) | ||||||
|  |    { | ||||||
|  |       is.get(); | ||||||
|  |       endOfStream = true; | ||||||
|  |    } | ||||||
|  |    else if (is.peek() == EOF) | ||||||
|  |    { | ||||||
|  |       endOfStream = true; | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    if (!endOfStream) | ||||||
|  |    { | ||||||
|  |       // Optional Forecast Identifier
 | ||||||
|  |       util::getline(is, line); | ||||||
|  |       SkipBlankLines(is); | ||||||
|  | 
 | ||||||
|  |       if (is.peek() == common::Characters::ETX) | ||||||
|  |       { | ||||||
|  |          is.get(); | ||||||
|  |          endOfStream = true; | ||||||
|  |       } | ||||||
|  |       else if (is.peek() == EOF) | ||||||
|  |       { | ||||||
|  |          endOfStream = true; | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    if (!endOfStream) | ||||||
|  |    { | ||||||
|  |       // End of Product was not found, so reset the istream to the original
 | ||||||
|  |       // state
 | ||||||
|  |       is.seekg(isBegin, std::ios_base::beg); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return endOfStream; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::vector<std::string> TryParseMndHeader(std::istream& is) | ||||||
|  | { | ||||||
|  |    std::vector<std::string> mndHeader; | ||||||
|  |    std::string              line; | ||||||
|  |    std::streampos           isBegin = is.tellg(); | ||||||
|  | 
 | ||||||
|  |    while (!is.eof() && is.peek() != '\r') | ||||||
|  |    { | ||||||
|  |       util::getline(is, line); | ||||||
|  |       mndHeader.push_back(line); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    if (!mndHeader.empty() && | ||||||
|  |        !std::regex_search(mndHeader.back(), reDateTimeString)) | ||||||
|  |    { | ||||||
|  |       // MND Header should end with an Issuance Date/Time Line
 | ||||||
|  |       mndHeader.clear(); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    if (mndHeader.empty()) | ||||||
|  |    { | ||||||
|  |       // MND header was not found, so reset the istream to the original state
 | ||||||
|  |       is.seekg(isBegin, std::ios_base::beg); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return mndHeader; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::optional<SegmentHeader> TryParseSegmentHeader(std::istream& is) | ||||||
|  | { | ||||||
|  |    // UGC takes the form SSFNNN-NNN>NNN-SSFNNN-DDHHMM- (NWSI 10-1702)
 | ||||||
|  |    // Look for SSF(NNN)?[->] to key the UGC string
 | ||||||
|  |    static const std::regex reUgcString {"^[A-Z]{2}[CZ]([0-9]{3})?[->]"}; | ||||||
|  | 
 | ||||||
|  |    std::optional<SegmentHeader> header = std::nullopt; | ||||||
|  |    std::string                  line; | ||||||
|  |    std::streampos               isBegin = is.tellg(); | ||||||
|  | 
 | ||||||
|  |    util::getline(is, line); | ||||||
|  | 
 | ||||||
|  |    if (std::regex_search(line, reUgcString)) | ||||||
|  |    { | ||||||
|  |       header = SegmentHeader(); | ||||||
|  |       header->ugcString_.swap(line); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    if (header.has_value()) | ||||||
|  |    { | ||||||
|  |       std::optional<Vtec> vtec; | ||||||
|  |       while ((vtec = TryParseVtecString(is)) != std::nullopt) | ||||||
|  |       { | ||||||
|  |          header->vtecString_.push_back(std::move(*vtec)); | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       while (!is.eof() && is.peek() != '\r') | ||||||
|  |       { | ||||||
|  |          util::getline(is, line); | ||||||
|  |          if (!std::regex_search(line, reDateTimeString)) | ||||||
|  |          { | ||||||
|  |             header->ugcNames_.push_back(line); | ||||||
|  |          } | ||||||
|  |          else | ||||||
|  |          { | ||||||
|  |             header->issuanceDateTime_.swap(line); | ||||||
|  |             break; | ||||||
|  |          } | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    if (!header.has_value()) | ||||||
|  |    { | ||||||
|  |       // We did not find a valid segment header, so we reset the istream to the
 | ||||||
|  |       // original state
 | ||||||
|  |       is.seekg(isBegin, std::ios_base::beg); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return header; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::optional<Vtec> TryParseVtecString(std::istream& is) | ||||||
|  | { | ||||||
|  |    // P-VTEC takes the form /k.aaa.cccc.pp.s.####.yymmddThhnnZB-yymmddThhnnZE/
 | ||||||
|  |    // (NWSI 10-1703)
 | ||||||
|  |    // Look for /k. to key the P-VTEC string
 | ||||||
|  |    static const std::regex rePVtecString {"^/[OTEX]\\."}; | ||||||
|  | 
 | ||||||
|  |    // H-VTEC takes the form
 | ||||||
|  |    // /nwsli.s.ic.yymmddThhnnZB.yymmddThhnnZC.yymmddThhnnZE.fr/ (NWSI 10-1703)
 | ||||||
|  |    // Look for /nwsli. to key the H-VTEC string
 | ||||||
|  |    static const std::regex reHVtecString {"^/[A-Z0-9]{5}\\."}; | ||||||
|  | 
 | ||||||
|  |    std::optional<Vtec> vtec = std::nullopt; | ||||||
|  |    std::string         line; | ||||||
|  |    std::streampos      isBegin = is.tellg(); | ||||||
|  | 
 | ||||||
|  |    util::getline(is, line); | ||||||
|  | 
 | ||||||
|  |    if (std::regex_search(line, rePVtecString)) | ||||||
|  |    { | ||||||
|  |       vtec = Vtec(); | ||||||
|  |       vtec->pVtec_.swap(line); | ||||||
|  | 
 | ||||||
|  |       isBegin = is.tellg(); | ||||||
|  | 
 | ||||||
|  |       util::getline(is, line); | ||||||
|  | 
 | ||||||
|  |       if (std::regex_search(line, reHVtecString)) | ||||||
|  |       { | ||||||
|  |          vtec->hVtec_.swap(line); | ||||||
|  |       } | ||||||
|  |       else | ||||||
|  |       { | ||||||
|  |          // H-VTEC was not found, so reset the istream to the beginning of the
 | ||||||
|  |          // line
 | ||||||
|  |          is.seekg(isBegin, std::ios_base::beg); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  |    else | ||||||
|  |    { | ||||||
|  |       // P-VTEC was not found, so reset the istream to the original state
 | ||||||
|  |       is.seekg(isBegin, std::ios_base::beg); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return vtec; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | std::shared_ptr<TextProductMessage> TextProductMessage::Create(std::istream& is) | ||||||
|  | { | ||||||
|  |    std::shared_ptr<TextProductMessage> message = | ||||||
|  |       std::make_shared<TextProductMessage>(); | ||||||
|  | 
 | ||||||
|  |    if (!message->Parse(is)) | ||||||
|  |    { | ||||||
|  |       message.reset(); | ||||||
|  |    } | ||||||
|  | 
 | ||||||
|  |    return message; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | } // namespace awips
 | ||||||
|  | } // namespace scwx
 | ||||||
|  | @ -20,6 +20,10 @@ std::istream& getline(std::istream& is, std::string& t) | ||||||
|       case '\n': return is; |       case '\n': return is; | ||||||
| 
 | 
 | ||||||
|       case '\r': |       case '\r': | ||||||
|  |          while (sb->sgetc() == '\r') | ||||||
|  |          { | ||||||
|  |             sb->sbumpc(); | ||||||
|  |          } | ||||||
|          if (sb->sgetc() == '\n') |          if (sb->sgetc() == '\n') | ||||||
|          { |          { | ||||||
|             sb->sbumpc(); |             sb->sbumpc(); | ||||||
|  |  | ||||||
|  | @ -3,8 +3,10 @@ project(scwx-data) | ||||||
| find_package(Boost) | find_package(Boost) | ||||||
| 
 | 
 | ||||||
| set(HDR_AWIPS include/scwx/awips/message.hpp | set(HDR_AWIPS include/scwx/awips/message.hpp | ||||||
|  |               include/scwx/awips/text_product_message.hpp | ||||||
|               include/scwx/awips/wmo_header.hpp) |               include/scwx/awips/wmo_header.hpp) | ||||||
| set(SRC_AWIPS source/scwx/awips/message.cpp | set(SRC_AWIPS source/scwx/awips/message.cpp | ||||||
|  |               source/scwx/awips/text_product_message.cpp | ||||||
|               source/scwx/awips/wmo_header.cpp) |               source/scwx/awips/wmo_header.cpp) | ||||||
| set(HDR_COMMON include/scwx/common/characters.hpp | set(HDR_COMMON include/scwx/common/characters.hpp | ||||||
|                include/scwx/common/color_table.hpp |                include/scwx/common/color_table.hpp | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat