mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 00:40:06 +00:00 
			
		
		
		
	Initial placefile parsing
This commit is contained in:
		
							parent
							
								
									7c863793ea
								
							
						
					
					
						commit
						88475f5b0e
					
				
					 6 changed files with 569 additions and 0 deletions
				
			
		
							
								
								
									
										86
									
								
								wxdata/source/scwx/gr/color.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								wxdata/source/scwx/gr/color.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,86 @@ | |||
| #include <scwx/gr/color.hpp> | ||||
| 
 | ||||
| #include <limits> | ||||
| 
 | ||||
| #include <hsluv.h> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
| namespace gr | ||||
| { | ||||
| 
 | ||||
| template<typename T> | ||||
| T RoundChannel(double value); | ||||
| template<typename T> | ||||
| T StringToDecimal(const std::string& str); | ||||
| 
 | ||||
| boost::gil::rgba8_pixel_t ParseColor(const std::vector<std::string>& tokenList, | ||||
|                                      std::size_t                     startIndex, | ||||
|                                      ColorMode                       colorMode, | ||||
|                                      bool                            hasAlpha) | ||||
| { | ||||
| 
 | ||||
|    std::uint8_t r {}; | ||||
|    std::uint8_t g {}; | ||||
|    std::uint8_t b {}; | ||||
|    std::uint8_t a = 255; | ||||
| 
 | ||||
|    if (colorMode == ColorMode::RGBA) | ||||
|    { | ||||
|       if (tokenList.size() >= startIndex + 3) | ||||
|       { | ||||
|          r = StringToDecimal<std::uint8_t>(tokenList[startIndex + 0]); | ||||
|          g = StringToDecimal<std::uint8_t>(tokenList[startIndex + 1]); | ||||
|          b = StringToDecimal<std::uint8_t>(tokenList[startIndex + 2]); | ||||
|       } | ||||
| 
 | ||||
|       if (hasAlpha && tokenList.size() >= startIndex + 4) | ||||
|       { | ||||
|          a = StringToDecimal<std::uint8_t>(tokenList[startIndex + 3]); | ||||
|       } | ||||
|    } | ||||
|    else // if (colorMode == ColorMode::HSLuv)
 | ||||
|    { | ||||
|       double h {}; | ||||
|       double s {}; | ||||
|       double l {}; | ||||
| 
 | ||||
|       if (tokenList.size() >= startIndex + 3) | ||||
|       { | ||||
|          h = std::stod(tokenList[startIndex + 0]); | ||||
|          s = std::stod(tokenList[startIndex + 1]); | ||||
|          l = std::stod(tokenList[startIndex + 2]); | ||||
|       } | ||||
| 
 | ||||
|       double dr; | ||||
|       double dg; | ||||
|       double db; | ||||
| 
 | ||||
|       hsluv2rgb(h, s, l, &dr, &dg, &db); | ||||
| 
 | ||||
|       r = RoundChannel<std::uint8_t>(dr * 255.0); | ||||
|       g = RoundChannel<std::uint8_t>(dg * 255.0); | ||||
|       b = RoundChannel<std::uint8_t>(db * 255.0); | ||||
|    } | ||||
| 
 | ||||
|    return boost::gil::rgba8_pixel_t {r, g, b, a}; | ||||
| } | ||||
| 
 | ||||
| template<typename T> | ||||
| T RoundChannel(double value) | ||||
| { | ||||
|    return static_cast<T>(std::clamp<int>(std::lround(value), | ||||
|                                          std::numeric_limits<T>::min(), | ||||
|                                          std::numeric_limits<T>::max())); | ||||
| } | ||||
| 
 | ||||
| template<typename T> | ||||
| T StringToDecimal(const std::string& str) | ||||
| { | ||||
|    return static_cast<T>(std::clamp<int>(std::stoi(str), | ||||
|                                          std::numeric_limits<T>::min(), | ||||
|                                          std::numeric_limits<T>::max())); | ||||
| } | ||||
| 
 | ||||
| } // namespace gr
 | ||||
| } // namespace scwx
 | ||||
							
								
								
									
										391
									
								
								wxdata/source/scwx/gr/placefile.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										391
									
								
								wxdata/source/scwx/gr/placefile.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,391 @@ | |||
| #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
 | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat