mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-11-04 09:30:05 +00:00 
			
		
		
		
	Adding UGC class for structured UGC
This commit is contained in:
		
							parent
							
								
									e98bca9d5d
								
							
						
					
					
						commit
						d3b3ac6be6
					
				
					 3 changed files with 287 additions and 0 deletions
				
			
		
							
								
								
									
										37
									
								
								wxdata/include/scwx/awips/ugc.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								wxdata/include/scwx/awips/ugc.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,37 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace scwx
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					namespace awips
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UgcImpl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Ugc
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					   explicit Ugc();
 | 
				
			||||||
 | 
					   ~Ugc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Ugc(const Ugc&)            = delete;
 | 
				
			||||||
 | 
					   Ugc& operator=(const Ugc&) = delete;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   Ugc(Ugc&&) noexcept;
 | 
				
			||||||
 | 
					   Ugc& operator=(Ugc&&) noexcept;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   std::vector<std::string> states() const;
 | 
				
			||||||
 | 
					   std::vector<std::string> fips_ids() const;
 | 
				
			||||||
 | 
					   std::string              product_expiration() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   bool Parse(const std::vector<std::string>& ugcString);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					   std::unique_ptr<UgcImpl> p;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace awips
 | 
				
			||||||
 | 
					} // namespace scwx
 | 
				
			||||||
							
								
								
									
										248
									
								
								wxdata/source/scwx/awips/ugc.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								wxdata/source/scwx/awips/ugc.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,248 @@
 | 
				
			||||||
 | 
					#include <scwx/awips/ugc.hpp>
 | 
				
			||||||
 | 
					#include <scwx/util/logger.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <map>
 | 
				
			||||||
 | 
					#include <regex>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <boost/assign.hpp>
 | 
				
			||||||
 | 
					#include <boost/bimap.hpp>
 | 
				
			||||||
 | 
					#include <boost/bimap/unordered_set_of.hpp>
 | 
				
			||||||
 | 
					#include <boost/tokenizer.hpp>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace scwx
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					namespace awips
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const std::string logPrefix_ = "scwx::awips::ugc";
 | 
				
			||||||
 | 
					static const auto        logger_    = util::Logger::Create(logPrefix_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum class UgcFormat
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					   Counties,
 | 
				
			||||||
 | 
					   Zones,
 | 
				
			||||||
 | 
					   Unknown
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef boost::bimap<boost::bimaps::unordered_set_of<UgcFormat>,
 | 
				
			||||||
 | 
					                     boost::bimaps::unordered_set_of<char>>
 | 
				
			||||||
 | 
					   UgcFormatBimap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const UgcFormatBimap ugcFormatMap_ =
 | 
				
			||||||
 | 
					   boost::assign::list_of<UgcFormatBimap::relation> //
 | 
				
			||||||
 | 
					   (UgcFormat::Counties, 'C')                       //
 | 
				
			||||||
 | 
					   (UgcFormat::Zones, 'Z')                          //
 | 
				
			||||||
 | 
					   (UgcFormat::Unknown, '?');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UgcImpl
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					   explicit UgcImpl() :
 | 
				
			||||||
 | 
					       ugcString_ {},
 | 
				
			||||||
 | 
					       format_ {UgcFormat::Unknown},
 | 
				
			||||||
 | 
					       fipsIdMap_ {},
 | 
				
			||||||
 | 
					       productExpiration_ {},
 | 
				
			||||||
 | 
					       valid_ {false}
 | 
				
			||||||
 | 
					   {
 | 
				
			||||||
 | 
					   }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   ~UgcImpl() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   std::vector<std::string> ugcString_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   UgcFormat                                  format_;
 | 
				
			||||||
 | 
					   std::map<std::string, std::list<uint16_t>> fipsIdMap_;
 | 
				
			||||||
 | 
					   std::string                                productExpiration_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   bool valid_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Ugc::Ugc() : p(std::make_unique<UgcImpl>()) {}
 | 
				
			||||||
 | 
					Ugc::~Ugc() = default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Ugc::Ugc(Ugc&&) noexcept            = default;
 | 
				
			||||||
 | 
					Ugc& Ugc::operator=(Ugc&&) noexcept = default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::vector<std::string> Ugc::states() const
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					   std::vector<std::string> states {};
 | 
				
			||||||
 | 
					   states.reserve(p->fipsIdMap_.size());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   for (auto& entry : p->fipsIdMap_)
 | 
				
			||||||
 | 
					   {
 | 
				
			||||||
 | 
					      states.push_back(entry.first);
 | 
				
			||||||
 | 
					   }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   return states;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::vector<std::string> Ugc::fips_ids() const
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					   std::vector<std::string> fipsIds {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   for (auto& fipsIdList : p->fipsIdMap_)
 | 
				
			||||||
 | 
					   {
 | 
				
			||||||
 | 
					      for (auto& id : fipsIdList.second)
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					         fipsIds.push_back(std::format("{}{}{:03}",
 | 
				
			||||||
 | 
					                                       fipsIdList.first,
 | 
				
			||||||
 | 
					                                       ugcFormatMap_.left.at(p->format_),
 | 
				
			||||||
 | 
					                                       id));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					   }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   return fipsIds;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string Ugc::product_expiration() const
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					   return p->productExpiration_;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Ugc::Parse(const std::vector<std::string>& ugcString)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					   bool dataValid = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   // UGC takes the form SSFNNN-NNN>NNN-SSFNNN-DDHHMM- (NWSI 10-1702)
 | 
				
			||||||
 | 
					   static const std::regex reStart {"[A-Z]{2}[CZ]([0-9]{3}|ALL)"};
 | 
				
			||||||
 | 
					   static const std::regex reAnyFipsId {"([0-9]{3}|ALL)"};
 | 
				
			||||||
 | 
					   static const std::regex reSpecificFipsId {"(?!0{3})[0-9]{3}"};
 | 
				
			||||||
 | 
					   static const std::regex reProductExpiration {"[0-9]{6}"};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   std::stringstream ugcStream;
 | 
				
			||||||
 | 
					   for (auto& line : ugcString)
 | 
				
			||||||
 | 
					   {
 | 
				
			||||||
 | 
					      ugcStream << line;
 | 
				
			||||||
 | 
					   }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   // Concatenate UGC lines into a single string
 | 
				
			||||||
 | 
					   std::string ugc {};
 | 
				
			||||||
 | 
					   for (const std::string& line : ugcString)
 | 
				
			||||||
 | 
					   {
 | 
				
			||||||
 | 
					      ugc += line;
 | 
				
			||||||
 | 
					   }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   boost::char_separator<char> sectionDelimiter("-");
 | 
				
			||||||
 | 
					   boost::char_separator<char> rangeDelimiter(">");
 | 
				
			||||||
 | 
					   boost::tokenizer            tokens(ugc, sectionDelimiter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   std::string currentState {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   for (auto& token : tokens)
 | 
				
			||||||
 | 
					   {
 | 
				
			||||||
 | 
					      // Product Expiration is the final token
 | 
				
			||||||
 | 
					      if (std::regex_match(token, reProductExpiration))
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					         p->productExpiration_ = token;
 | 
				
			||||||
 | 
					         dataValid             = true;
 | 
				
			||||||
 | 
					         break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Tokenize string again by ">" (note there will always be at least one
 | 
				
			||||||
 | 
					      // range token)
 | 
				
			||||||
 | 
					      boost::tokenizer rangeTokens(token, rangeDelimiter);
 | 
				
			||||||
 | 
					      const size_t     numRangeTokens =
 | 
				
			||||||
 | 
					         std::distance(rangeTokens.begin(), rangeTokens.end());
 | 
				
			||||||
 | 
					      bool        tokenValid = true;
 | 
				
			||||||
 | 
					      bool        allFipsIds = false;
 | 
				
			||||||
 | 
					      auto        tokenIt    = rangeTokens.begin();
 | 
				
			||||||
 | 
					      std::string firstToken {tokenIt.current_token()};
 | 
				
			||||||
 | 
					      UgcFormat   currentFormat {p->format_};
 | 
				
			||||||
 | 
					      std::string firstFipsId {};
 | 
				
			||||||
 | 
					      std::string secondFipsId {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Look for the start of the UGC string (may be multiple per UGC string
 | 
				
			||||||
 | 
					      // for multiple states, territories, or marine area)
 | 
				
			||||||
 | 
					      if (std::regex_match(firstToken, reStart))
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					         currentState  = firstToken.substr(0, 2);
 | 
				
			||||||
 | 
					         currentFormat = ugcFormatMap_.right.at(firstToken.at(2));
 | 
				
			||||||
 | 
					         firstFipsId   = firstToken.substr(3, 3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					         // The UGC string must contain counties or zones, but not both
 | 
				
			||||||
 | 
					         if (p->format_ != UgcFormat::Unknown && p->format_ != currentFormat)
 | 
				
			||||||
 | 
					         {
 | 
				
			||||||
 | 
					            tokenValid = false;
 | 
				
			||||||
 | 
					         }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      // Look for additional FIPS IDs in the UGC string
 | 
				
			||||||
 | 
					      else if (!currentState.empty() &&
 | 
				
			||||||
 | 
					               std::regex_match(firstToken, reAnyFipsId))
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					         firstFipsId = firstToken;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      // If we see anything else, the UGC token is invalid
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					         tokenValid = false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // All counties or zones are specified by using "000" or "ALL"
 | 
				
			||||||
 | 
					      if (firstFipsId == "000" || firstFipsId == "ALL")
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					         allFipsIds = true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Parse the second token in a range (i.e., NNN>XXX)
 | 
				
			||||||
 | 
					      if (numRangeTokens == 2)
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					         std::string secondToken {(++tokenIt).current_token()};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					         if (std::regex_match(secondToken, reSpecificFipsId))
 | 
				
			||||||
 | 
					         {
 | 
				
			||||||
 | 
					            secondFipsId = secondToken;
 | 
				
			||||||
 | 
					         }
 | 
				
			||||||
 | 
					         else
 | 
				
			||||||
 | 
					         {
 | 
				
			||||||
 | 
					            tokenValid = false;
 | 
				
			||||||
 | 
					         }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Check validity before using parsed data
 | 
				
			||||||
 | 
					      if (!tokenValid || numRangeTokens > 2 ||
 | 
				
			||||||
 | 
					          (allFipsIds && numRangeTokens > 1))
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					         logger_->warn("Invalid token: {}", token);
 | 
				
			||||||
 | 
					         break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      p->format_    = currentFormat;
 | 
				
			||||||
 | 
					      auto& fipsIds = p->fipsIdMap_[currentState];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (allFipsIds)
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					         fipsIds.push_back(0);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					         // Insert the FIPS ID (NNN) from the token
 | 
				
			||||||
 | 
					         fipsIds.push_back(static_cast<uint16_t>(std::stoul(firstFipsId)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					         if (numRangeTokens == 2)
 | 
				
			||||||
 | 
					         {
 | 
				
			||||||
 | 
					            // Insert the remainder of the FIPS IDs in the range given by the
 | 
				
			||||||
 | 
					            // token (NNN>XXX)
 | 
				
			||||||
 | 
					            const uint16_t first = fipsIds.back();
 | 
				
			||||||
 | 
					            const uint16_t last =
 | 
				
			||||||
 | 
					               static_cast<uint16_t>(std::stoul(secondFipsId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (uint16_t i = first + 1; i <= last; i++)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					               fipsIds.push_back(i);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					         }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					   }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   p->valid_ = dataValid;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   if (!dataValid)
 | 
				
			||||||
 | 
					   {
 | 
				
			||||||
 | 
					      p->fipsIdMap_.clear();
 | 
				
			||||||
 | 
					   }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   return dataValid;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace awips
 | 
				
			||||||
 | 
					} // namespace scwx
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,7 @@ set(HDR_AWIPS include/scwx/awips/coded_location.hpp
 | 
				
			||||||
              include/scwx/awips/significance.hpp
 | 
					              include/scwx/awips/significance.hpp
 | 
				
			||||||
              include/scwx/awips/text_product_file.hpp
 | 
					              include/scwx/awips/text_product_file.hpp
 | 
				
			||||||
              include/scwx/awips/text_product_message.hpp
 | 
					              include/scwx/awips/text_product_message.hpp
 | 
				
			||||||
 | 
					              include/scwx/awips/ugc.hpp
 | 
				
			||||||
              include/scwx/awips/wmo_header.hpp)
 | 
					              include/scwx/awips/wmo_header.hpp)
 | 
				
			||||||
set(SRC_AWIPS source/scwx/awips/coded_location.cpp
 | 
					set(SRC_AWIPS source/scwx/awips/coded_location.cpp
 | 
				
			||||||
              source/scwx/awips/coded_time_motion_location.cpp
 | 
					              source/scwx/awips/coded_time_motion_location.cpp
 | 
				
			||||||
| 
						 | 
					@ -23,6 +24,7 @@ set(SRC_AWIPS source/scwx/awips/coded_location.cpp
 | 
				
			||||||
              source/scwx/awips/significance.cpp
 | 
					              source/scwx/awips/significance.cpp
 | 
				
			||||||
              source/scwx/awips/text_product_file.cpp
 | 
					              source/scwx/awips/text_product_file.cpp
 | 
				
			||||||
              source/scwx/awips/text_product_message.cpp
 | 
					              source/scwx/awips/text_product_message.cpp
 | 
				
			||||||
 | 
					              source/scwx/awips/ugc.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