mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 06:10:04 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			268 lines
		
	
	
	
		
			6.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			268 lines
		
	
	
	
		
			6.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Prevent redefinition of __cpp_lib_format
 | |
| #if defined(_MSC_VER)
 | |
| #   include <yvals_core.h>
 | |
| #endif
 | |
| 
 | |
| // Enable chrono formatters
 | |
| #ifndef __cpp_lib_format
 | |
| #   define __cpp_lib_format 202110L
 | |
| #endif
 | |
| 
 | |
| #include <scwx/awips/coded_time_motion_location.hpp>
 | |
| #include <scwx/util/logger.hpp>
 | |
| 
 | |
| #include <sstream>
 | |
| 
 | |
| #if (__cpp_lib_chrono < 201907L)
 | |
| #   include <date/date.h>
 | |
| #endif
 | |
| 
 | |
| namespace scwx
 | |
| {
 | |
| namespace awips
 | |
| {
 | |
| 
 | |
| static const std::string logPrefix_ = "scwx::awips::coded_time_motion_location";
 | |
| static const auto        logger_    = util::Logger::Create(logPrefix_);
 | |
| 
 | |
| class CodedTimeMotionLocationImpl
 | |
| {
 | |
| public:
 | |
|    explicit CodedTimeMotionLocationImpl() :
 | |
|        time_ {}, direction_ {0}, speed_ {0}, coordinates_ {}
 | |
|    {
 | |
|    }
 | |
| 
 | |
|    ~CodedTimeMotionLocationImpl() {}
 | |
| 
 | |
|    std::chrono::hh_mm_ss<std::chrono::minutes> time_;
 | |
|    uint16_t                                    direction_;
 | |
|    uint8_t                                     speed_;
 | |
|    std::vector<common::Coordinate>             coordinates_;
 | |
| };
 | |
| 
 | |
| CodedTimeMotionLocation::CodedTimeMotionLocation() :
 | |
|     p(std::make_unique<CodedTimeMotionLocationImpl>())
 | |
| {
 | |
| }
 | |
| CodedTimeMotionLocation::~CodedTimeMotionLocation() = default;
 | |
| 
 | |
| CodedTimeMotionLocation::CodedTimeMotionLocation(
 | |
|    CodedTimeMotionLocation&&) noexcept = default;
 | |
| CodedTimeMotionLocation& CodedTimeMotionLocation::operator=(
 | |
|    CodedTimeMotionLocation&&) noexcept = default;
 | |
| 
 | |
| std::chrono::hh_mm_ss<std::chrono::minutes>
 | |
| CodedTimeMotionLocation::time() const
 | |
| {
 | |
|    return p->time_;
 | |
| }
 | |
| 
 | |
| uint16_t CodedTimeMotionLocation::direction() const
 | |
| {
 | |
|    return p->direction_;
 | |
| }
 | |
| 
 | |
| uint8_t CodedTimeMotionLocation::speed() const
 | |
| {
 | |
|    return p->speed_;
 | |
| }
 | |
| 
 | |
| std::vector<common::Coordinate> CodedTimeMotionLocation::coordinates() const
 | |
| {
 | |
|    return p->coordinates_;
 | |
| }
 | |
| 
 | |
| bool CodedTimeMotionLocation::Parse(const StringRange& lines,
 | |
|                                     const std::string& wfo)
 | |
| {
 | |
|    bool dataValid = true;
 | |
| 
 | |
|    std::vector<std::string> tokenList;
 | |
| 
 | |
|    for (std::string line : lines)
 | |
|    {
 | |
|       std::string        token;
 | |
|       std::istringstream tokenStream {line};
 | |
| 
 | |
|       while (tokenStream >> token)
 | |
|       {
 | |
|          tokenList.push_back(token);
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    // First token is "TIME...MOT...LOC"
 | |
|    // At a minimum, one point (latitude/longitude pair) will be included
 | |
|    dataValid = (tokenList.size() >= 6 && tokenList.size() % 2 == 0 &&
 | |
|                 tokenList.at(0) == "TIME...MOT...LOC");
 | |
| 
 | |
|    if (dataValid)
 | |
|    {
 | |
|       const bool wfoIsWest         = (wfo != "PGUM");
 | |
|       double     westLongitude     = (wfoIsWest) ? -1.0 : 1.0;
 | |
|       bool       straddlesDateLine = false;
 | |
| 
 | |
|       // Time: hhmmZ
 | |
|       std::string time = tokenList.at(1);
 | |
|       {
 | |
|          using namespace std::chrono;
 | |
| 
 | |
| #if (__cpp_lib_chrono < 201907L)
 | |
|          using namespace date;
 | |
| #endif
 | |
| 
 | |
|          static const std::string timeFormat {"%H%MZ"};
 | |
| 
 | |
|          std::istringstream in {time};
 | |
|          minutes            tp;
 | |
|          in >> parse(timeFormat, tp);
 | |
| 
 | |
|          if (time.size() == 5 && !in.fail())
 | |
|          {
 | |
|             p->time_ = std::chrono::hh_mm_ss {tp};
 | |
|          }
 | |
|          else
 | |
|          {
 | |
|             logger_->warn("Invalid time: \"{}\"", time);
 | |
|             p->time_  = std::chrono::hh_mm_ss<minutes> {};
 | |
|             dataValid = false;
 | |
|          }
 | |
|       }
 | |
| 
 | |
|       // Direction: dirDEG
 | |
|       std::string direction = tokenList.at(2);
 | |
|       if (direction.size() == 6 && direction.ends_with("DEG"))
 | |
|       {
 | |
|          try
 | |
|          {
 | |
|             p->direction_ =
 | |
|                static_cast<uint16_t>(std::stoul(direction.substr(0, 3)));
 | |
|          }
 | |
|          catch (const std::exception& ex)
 | |
|          {
 | |
|             logger_->warn(
 | |
|                "Invalid direction: \"{}\" ({})", direction, ex.what());
 | |
|             dataValid = false;
 | |
|          }
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|          logger_->warn("Invalid direction: \"{}\"", direction);
 | |
|          dataValid = false;
 | |
|       }
 | |
| 
 | |
|       // Speed: <sp>KT
 | |
|       std::string speed = tokenList.at(3);
 | |
|       if (speed.size() >= 3 && speed.size() <= 5 && speed.ends_with("KT"))
 | |
|       {
 | |
|          try
 | |
|          {
 | |
|             // NWSI 10-1701 specifies a valid speed range of 0-99 knots.
 | |
|             // However, sometimes text products are published with a larger
 | |
|             // value. Instead, allow a value up to 255 knots.
 | |
|             auto parsedSpeed = std::stoul(speed.substr(0, speed.size() - 2));
 | |
|             if (parsedSpeed <= 255u)
 | |
|             {
 | |
|                p->speed_ = static_cast<uint8_t>(parsedSpeed);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                logger_->warn("Invalid speed: \"{}\"", speed);
 | |
|                dataValid = false;
 | |
|             }
 | |
|          }
 | |
|          catch (const std::exception& ex)
 | |
|          {
 | |
|             logger_->warn("Invalid speed: \"{}\" ({})", speed, ex.what());
 | |
|             dataValid = false;
 | |
|          }
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|          logger_->warn("Invalid speed: \"{}\"", speed);
 | |
|          dataValid = false;
 | |
|       }
 | |
| 
 | |
|       // Location
 | |
|       for (auto token = tokenList.cbegin() + 4; token != tokenList.cend();
 | |
|            ++token)
 | |
|       {
 | |
|          double latitude  = 0.0;
 | |
|          double longitude = 0.0;
 | |
| 
 | |
|          try
 | |
|          {
 | |
|             latitude = std::stod(*token) * 0.01;
 | |
|             ++token;
 | |
|             longitude = std::stod(*token) * 0.01;
 | |
|          }
 | |
|          catch (const std::exception& ex)
 | |
|          {
 | |
|             logger_->warn(
 | |
|                "Invalid location token: \"{}\" ({})", *token, ex.what());
 | |
|             dataValid = false;
 | |
|             break;
 | |
|          }
 | |
| 
 | |
|          // If a given product straddles 180 degrees longitude, those points
 | |
|          // west of 180 degrees will be given as if they were west longitude
 | |
|          if (longitude > 180.0)
 | |
|          {
 | |
|             longitude -= 360.0;
 | |
|             straddlesDateLine = true;
 | |
|          }
 | |
| 
 | |
|          longitude *= westLongitude;
 | |
| 
 | |
|          p->coordinates_.push_back({latitude, longitude});
 | |
|       }
 | |
| 
 | |
|       if (!wfoIsWest && straddlesDateLine)
 | |
|       {
 | |
|          for (auto& coordinate : p->coordinates_)
 | |
|          {
 | |
|             coordinate.longitude_ *= -1.0;
 | |
|          }
 | |
|       }
 | |
|    }
 | |
|    else
 | |
|    {
 | |
|       if (tokenList.empty())
 | |
|       {
 | |
|          logger_->warn("TIME...MOT...LOC not found");
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|          logger_->warn("Malformed TIME...MOT...LOC tokens: (0: {}, size: {})",
 | |
|                        tokenList.at(0),
 | |
|                        tokenList.size());
 | |
| 
 | |
|          for (const auto& token : tokenList)
 | |
|          {
 | |
|             logger_->debug("{}", token);
 | |
|          }
 | |
|       }
 | |
| 
 | |
|       dataValid = false;
 | |
|    }
 | |
| 
 | |
|    return dataValid;
 | |
| }
 | |
| 
 | |
| std::optional<CodedTimeMotionLocation>
 | |
| CodedTimeMotionLocation::Create(const StringRange& lines,
 | |
|                                 const std::string& wfo)
 | |
| {
 | |
|    std::optional<CodedTimeMotionLocation> motion =
 | |
|       std::make_optional<CodedTimeMotionLocation>();
 | |
| 
 | |
|    if (!motion->Parse(lines, wfo))
 | |
|    {
 | |
|       motion.reset();
 | |
|    }
 | |
| 
 | |
|    return motion;
 | |
| }
 | |
| 
 | |
| } // namespace awips
 | |
| } // namespace scwx
 | 
