mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 03:40:05 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			391 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			391 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include <scwx/gr/placefile.hpp>
 | |
| #include <scwx/gr/color.hpp>
 | |
| #include <scwx/util/logger.hpp>
 | |
| #include <scwx/util/streams.hpp>
 | |
| 
 | |
| #include <fstream>
 | |
| #include <regex>
 | |
| #include <sstream>
 | |
| #include <unordered_map>
 | |
| 
 | |
| #include <boost/algorithm/string.hpp>
 | |
| #include <boost/tokenizer.hpp>
 | |
| #include <boost/units/base_units/metric/nautical_mile.hpp>
 | |
| #include <boost/units/quantity.hpp>
 | |
| #include <boost/units/systems/si/length.hpp>
 | |
| 
 | |
| namespace scwx
 | |
| {
 | |
| namespace gr
 | |
| {
 | |
| 
 | |
| static const std::string logPrefix_ {"scwx::gr::placefile"};
 | |
| static const auto        logger_ = util::Logger::Create(logPrefix_);
 | |
| 
 | |
| enum class DrawingStatement
 | |
| {
 | |
|    Standard,
 | |
|    Line,
 | |
|    Triangles,
 | |
|    Image,
 | |
|    Polygon
 | |
| };
 | |
| 
 | |
| class Placefile::Impl
 | |
| {
 | |
| public:
 | |
|    explicit Impl() = default;
 | |
|    ~Impl()         = default;
 | |
| 
 | |
|    struct Object
 | |
|    {
 | |
|       double x_ {};
 | |
|       double y_ {};
 | |
|    };
 | |
| 
 | |
|    struct DrawItem
 | |
|    {
 | |
|    };
 | |
| 
 | |
|    struct PlaceDrawItem : DrawItem
 | |
|    {
 | |
|       boost::units::quantity<boost::units::si::length> threshold_ {};
 | |
|       boost::gil::rgba8_pixel_t                        color_ {};
 | |
|       double                                           latitude_ {};
 | |
|       double                                           longitude_ {};
 | |
|       double                                           x_ {};
 | |
|       double                                           y_ {};
 | |
|       std::string                                      text_ {};
 | |
|    };
 | |
| 
 | |
|    void ParseLocation(const std::string& latitudeToken,
 | |
|                       const std::string& longitudeToken,
 | |
|                       double&            latitude,
 | |
|                       double&            longitude,
 | |
|                       double&            x,
 | |
|                       double&            y);
 | |
|    void ProcessLine(const std::string&              line,
 | |
|                     const std::vector<std::string>& tokenList);
 | |
| 
 | |
|    std::chrono::seconds refresh_ {-1};
 | |
| 
 | |
|    // Parsing state
 | |
|    boost::units::quantity<boost::units::si::length> threshold_ {
 | |
|       999.0 * boost::units::metric::nautical_mile_base_unit::unit_type()};
 | |
|    boost::gil::rgba8_pixel_t color_ {255, 255, 255, 255};
 | |
|    ColorMode                 colorMode_ {ColorMode::RGBA};
 | |
|    std::vector<Object>       objectStack_ {};
 | |
|    DrawingStatement          currentStatement_ {DrawingStatement::Standard};
 | |
| 
 | |
|    // References
 | |
|    std::unordered_map<std::size_t, bool> iconFiles_ {};
 | |
|    std::unordered_map<std::size_t, bool> fonts_ {};
 | |
| 
 | |
|    std::vector<std::shared_ptr<DrawItem>> drawItems_ {};
 | |
| };
 | |
| 
 | |
| Placefile::Placefile() : p(std::make_unique<Impl>()) {}
 | |
| Placefile::~Placefile() = default;
 | |
| 
 | |
| Placefile::Placefile(Placefile&&) noexcept            = default;
 | |
| Placefile& Placefile::operator=(Placefile&&) noexcept = default;
 | |
| 
 | |
| bool Placefile::IsValid() const
 | |
| {
 | |
|    return p->drawItems_.size() > 0;
 | |
| }
 | |
| 
 | |
| std::shared_ptr<Placefile> Placefile::Load(const std::string& filename)
 | |
| {
 | |
|    logger_->debug("Loading placefile: {}", filename);
 | |
|    std::ifstream f(filename, std::ios_base::in);
 | |
|    return Load(f);
 | |
| }
 | |
| 
 | |
| std::shared_ptr<Placefile> Placefile::Load(std::istream& is)
 | |
| {
 | |
|    std::shared_ptr<Placefile> placefile = std::make_shared<Placefile>();
 | |
| 
 | |
|    std::string line;
 | |
|    while (scwx::util::getline(is, line))
 | |
|    {
 | |
|       // Find position of comment (;)
 | |
|       std::size_t lineEnd = line.find(';');
 | |
|       if (lineEnd == std::string::npos)
 | |
|       {
 | |
|          // Remove comment
 | |
|          line.erase(lineEnd);
 | |
|       }
 | |
| 
 | |
|       // Remove extra spacing from line
 | |
|       boost::trim(line);
 | |
| 
 | |
|       boost::char_separator<char> delimiter(", ");
 | |
|       boost::tokenizer            tokens(line, delimiter);
 | |
|       std::vector<std::string>    tokenList;
 | |
| 
 | |
|       for (auto& token : tokens)
 | |
|       {
 | |
|          tokenList.push_back(token);
 | |
|       }
 | |
| 
 | |
|       if (tokenList.size() >= 1)
 | |
|       {
 | |
|          try
 | |
|          {
 | |
|             switch (placefile->p->currentStatement_)
 | |
|             {
 | |
|             case DrawingStatement::Standard:
 | |
|                placefile->p->ProcessLine(line, tokenList);
 | |
|                break;
 | |
| 
 | |
|             case DrawingStatement::Line:
 | |
|             case DrawingStatement::Triangles:
 | |
|             case DrawingStatement::Image:
 | |
|             case DrawingStatement::Polygon:
 | |
|                if (boost::iequals(tokenList[0], "End:"))
 | |
|                {
 | |
|                   placefile->p->currentStatement_ = DrawingStatement::Standard;
 | |
|                }
 | |
|                break;
 | |
|             }
 | |
|          }
 | |
|          catch (const std::exception&)
 | |
|          {
 | |
|             logger_->warn("Could not parse line: {}", line);
 | |
|          }
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    return placefile;
 | |
| }
 | |
| 
 | |
| void Placefile::Impl::ProcessLine(const std::string&              line,
 | |
|                                   const std::vector<std::string>& tokenList)
 | |
| {
 | |
|    currentStatement_ = DrawingStatement::Standard;
 | |
| 
 | |
|    if (boost::iequals(tokenList[0], "Threshold:"))
 | |
|    {
 | |
|       // Threshold: nautical_miles
 | |
|       if (tokenList.size() >= 2)
 | |
|       {
 | |
|          threshold_ =
 | |
|             static_cast<boost::units::quantity<boost::units::si::length>>(
 | |
|                std::stod(tokenList[1]) *
 | |
|                boost::units::metric::nautical_mile_base_unit::unit_type());
 | |
|       }
 | |
|    }
 | |
|    else if (boost::iequals(tokenList[0], "HSLuv:"))
 | |
|    {
 | |
|       // HSLuv: value
 | |
|       if (tokenList.size() >= 2)
 | |
|       {
 | |
|          if (boost::iequals(tokenList[1], "true"))
 | |
|          {
 | |
|             colorMode_ = ColorMode::HSLuv;
 | |
|          }
 | |
|          else
 | |
|          {
 | |
|             colorMode_ = ColorMode::RGBA;
 | |
|          }
 | |
|       }
 | |
|    }
 | |
|    else if (boost::iequals(tokenList[0], "Color:"))
 | |
|    {
 | |
|       // Color: red green blue
 | |
|       if (tokenList.size() >= 2)
 | |
|       {
 | |
|          color_ = ParseColor(tokenList, 1, colorMode_);
 | |
|       }
 | |
|    }
 | |
|    else if (boost::iequals(tokenList[0], "Refresh:"))
 | |
|    {
 | |
|       // Refresh: minutes
 | |
|       if (tokenList.size() >= 2)
 | |
|       {
 | |
|          refresh_ = std::chrono::minutes {std::stoi(tokenList[1])};
 | |
|       }
 | |
|    }
 | |
|    else if (boost::iequals(tokenList[0], "RefreshSeconds:"))
 | |
|    {
 | |
|       // RefreshSeconds: seconds
 | |
|       if (tokenList.size() >= 2)
 | |
|       {
 | |
|          refresh_ = std::chrono::seconds {std::stoi(tokenList[1])};
 | |
|       }
 | |
|    }
 | |
|    else if (boost::iequals(tokenList[0], "Place:"))
 | |
|    {
 | |
|       // Place: latitude, longitude, string with spaces
 | |
|       std::regex  re {"Place:\\s*([+\\-0-9\\.]+),\\s*([+\\-0-9\\.]+),\\s*(.+)"};
 | |
|       std::smatch match;
 | |
|       std::regex_match(line, match, re);
 | |
| 
 | |
|       if (match.size() >= 4)
 | |
|       {
 | |
|          std::shared_ptr<PlaceDrawItem> di = std::make_shared<PlaceDrawItem>();
 | |
| 
 | |
|          di->threshold_ = threshold_;
 | |
|          di->color_     = color_;
 | |
| 
 | |
|          ParseLocation(match[1].str(),
 | |
|                        match[2].str(),
 | |
|                        di->latitude_,
 | |
|                        di->longitude_,
 | |
|                        di->x_,
 | |
|                        di->y_);
 | |
| 
 | |
|          di->text_ = match[3].str();
 | |
| 
 | |
|          drawItems_.emplace_back(std::move(di));
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|          logger_->warn("Place statement malformed: {}", line);
 | |
|       }
 | |
|    }
 | |
|    else if (boost::iequals(tokenList[0], "IconFile:"))
 | |
|    {
 | |
|       // IconFile: fileNumber, iconWidth, iconHeight, hotX, hotY, fileName
 | |
| 
 | |
|       // TODO
 | |
|    }
 | |
|    else if (boost::iequals(tokenList[0], "Icon:"))
 | |
|    {
 | |
|       // Icon: lat, lon, angle, fileNumber, iconNumber, hoverText
 | |
| 
 | |
|       // TODO
 | |
|    }
 | |
|    else if (boost::iequals(tokenList[0], "Font:"))
 | |
|    {
 | |
|       // Font: fontNumber, pixels, flags, "face"
 | |
| 
 | |
|       // TODO
 | |
|    }
 | |
|    else if (boost::iequals(tokenList[0], "Text:"))
 | |
|    {
 | |
|       // Text: lat, lon, fontNumber, "string", "hover"
 | |
| 
 | |
|       // TODO
 | |
|    }
 | |
|    else if (boost::iequals(tokenList[0], "Object:"))
 | |
|    {
 | |
|       // Object: lat, lon
 | |
|       //    ...
 | |
|       // End:
 | |
|       std::regex  re {"Object:\\s*([+\\-0-9\\.]+),\\s*([+\\-0-9\\.]+)"};
 | |
|       std::smatch match;
 | |
|       std::regex_match(line, match, re);
 | |
| 
 | |
|       double latitude {};
 | |
|       double longitude {};
 | |
| 
 | |
|       if (match.size() >= 3)
 | |
|       {
 | |
|          latitude  = std::stod(match[1].str());
 | |
|          longitude = std::stod(match[2].str());
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|          logger_->warn("Object statement malformed: {}", line);
 | |
|       }
 | |
| 
 | |
|       objectStack_.emplace_back(Object {latitude, longitude});
 | |
|    }
 | |
|    else if (boost::iequals(tokenList[0], "End:"))
 | |
|    {
 | |
|       // Object End
 | |
|       if (!objectStack_.empty())
 | |
|       {
 | |
|          objectStack_.pop_back();
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|          logger_->warn("End found without Object");
 | |
|       }
 | |
|    }
 | |
|    else if (boost::iequals(tokenList[0], "Line:"))
 | |
|    {
 | |
|       // Line: width, flags [, hover_text]
 | |
|       //    lat, lon
 | |
|       //    ...
 | |
|       // End:
 | |
|       currentStatement_ = DrawingStatement::Line;
 | |
| 
 | |
|       // TODO
 | |
|    }
 | |
|    else if (boost::iequals(tokenList[0], "Triangles:"))
 | |
|    {
 | |
|       // Triangles:
 | |
|       //    lat, lon [, r, g, b [,a]]
 | |
|       //    ...
 | |
|       // End:
 | |
|       currentStatement_ = DrawingStatement::Triangles;
 | |
| 
 | |
|       // TODO
 | |
|    }
 | |
|    else if (boost::iequals(tokenList[0], "Image:"))
 | |
|    {
 | |
|       // Image: image_file
 | |
|       //    lat, lon, Tu [, Tv ]
 | |
|       //    ...
 | |
|       // End:
 | |
|       currentStatement_ = DrawingStatement::Image;
 | |
| 
 | |
|       // TODO
 | |
|    }
 | |
|    else if (boost::iequals(tokenList[0], "Polygon:"))
 | |
|    {
 | |
|       // Polygon:
 | |
|       //    lat1, lon1 [, r, g, b [,a]] ; start of the first contour
 | |
|       //    ...
 | |
|       //    lat1, lon1                  ; repeating the first point closes the
 | |
|       //                                ; contour
 | |
|       //
 | |
|       //    lat2, lon2                  ; next point starts a new contour
 | |
|       //    ...
 | |
|       //    lat2, lon2                  ; and repeating it ends the contour
 | |
|       // End:
 | |
|       currentStatement_ = DrawingStatement::Polygon;
 | |
| 
 | |
|       // TODO
 | |
|    }
 | |
| }
 | |
| 
 | |
| void Placefile::Impl::ParseLocation(const std::string& latitudeToken,
 | |
|                                     const std::string& longitudeToken,
 | |
|                                     double&            latitude,
 | |
|                                     double&            longitude,
 | |
|                                     double&            x,
 | |
|                                     double&            y)
 | |
| {
 | |
|    if (objectStack_.empty())
 | |
|    {
 | |
|       // If an Object statement is not currently open, parse latitude and
 | |
|       // longitude tokens as-is
 | |
|       latitude  = std::stod(latitudeToken);
 | |
|       longitude = std::stod(longitudeToken);
 | |
|    }
 | |
|    else
 | |
|    {
 | |
|       // If an Object statement is open, the latitude and longitude are from the
 | |
|       // outermost Object
 | |
|       latitude  = objectStack_[0].x_;
 | |
|       longitude = objectStack_[0].y_;
 | |
| 
 | |
|       // The latitude and longitude tokens are interpreted as x, y offsets
 | |
|       x = std::stod(latitudeToken);
 | |
|       y = std::stod(longitudeToken);
 | |
| 
 | |
|       // If there are inner Object statements open, treat these as x, y offsets
 | |
|       for (std::size_t i = 1; i < objectStack_.size(); i++)
 | |
|       {
 | |
|          x += objectStack_[i].x_;
 | |
|          y += objectStack_[i].y_;
 | |
|       }
 | |
|    }
 | |
| }
 | |
| 
 | |
| } // namespace gr
 | |
| } // namespace scwx
 | 
