mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-30 19:00:04 +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