#include #include #include #include #include #include #include #include #include #include #include #include #include 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 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& tokenList); std::chrono::seconds refresh_ {-1}; // Parsing state boost::units::quantity 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 objectStack_ {}; DrawingStatement currentStatement_ {DrawingStatement::Standard}; // References std::unordered_map iconFiles_ {}; std::unordered_map fonts_ {}; std::vector> drawItems_ {}; }; Placefile::Placefile() : p(std::make_unique()) {} 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::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::Load(std::istream& is) { std::shared_ptr placefile = std::make_shared(); std::string line; while (scwx::util::getline(is, line)) { // Find position of comment (;) bool inQuotes = false; for (std::size_t i = 0; i < line.size(); ++i) { if (!inQuotes && line[i] == ';') { // Remove comment line.erase(i); break; } else if (line[i] == '"') { // Toggle quote state inQuotes = !inQuotes; } } // Remove extra spacing from line boost::trim(line); boost::char_separator delimiter(", "); boost::tokenizer tokens(line, delimiter); std::vector 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& tokenList) { currentStatement_ = DrawingStatement::Standard; if (boost::iequals(tokenList[0], "Threshold:")) { // Threshold: nautical_miles if (tokenList.size() >= 2) { threshold_ = static_cast>( 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 di = std::make_shared(); 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