mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-29 22:30:04 +00:00
219 lines
5.8 KiB
C++
219 lines
5.8 KiB
C++
#include <scwx/awips/coded_location.hpp>
|
|
#include <scwx/util/logger.hpp>
|
|
|
|
#include <sstream>
|
|
|
|
namespace scwx
|
|
{
|
|
namespace awips
|
|
{
|
|
|
|
static const std::string logPrefix_ = "scwx::awips::coded_location";
|
|
static const auto logger_ = util::Logger::Create(logPrefix_);
|
|
|
|
class CodedLocationImpl
|
|
{
|
|
public:
|
|
explicit CodedLocationImpl() : coordinates_ {} {}
|
|
|
|
~CodedLocationImpl() {}
|
|
|
|
std::vector<common::Coordinate> coordinates_;
|
|
};
|
|
|
|
CodedLocation::CodedLocation() : p(std::make_unique<CodedLocationImpl>()) {}
|
|
CodedLocation::~CodedLocation() = default;
|
|
|
|
CodedLocation::CodedLocation(CodedLocation&&) noexcept = default;
|
|
CodedLocation& CodedLocation::operator=(CodedLocation&&) noexcept = default;
|
|
|
|
std::vector<common::Coordinate> CodedLocation::coordinates() const
|
|
{
|
|
return p->coordinates_;
|
|
}
|
|
|
|
bool CodedLocation::Parse(const StringRange& lines, const std::string& wfo)
|
|
{
|
|
enum class LocationFormat
|
|
{
|
|
WFO,
|
|
NationalCenter
|
|
};
|
|
|
|
bool dataValid = true;
|
|
LocationFormat format {};
|
|
|
|
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 "LAT...LON"
|
|
// At a minimum, three points (latitude/longitude pairs) will be included
|
|
dataValid = (tokenList.size() >= 4 && tokenList.at(0) == "LAT...LON");
|
|
|
|
if (dataValid)
|
|
{
|
|
format = (tokenList.at(1).size() == 8) ? LocationFormat::NationalCenter :
|
|
LocationFormat::WFO;
|
|
|
|
if (format == LocationFormat::WFO)
|
|
{
|
|
dataValid = (tokenList.size() >= 7 && tokenList.size() % 2 == 1);
|
|
}
|
|
}
|
|
|
|
if (dataValid)
|
|
{
|
|
if (format == LocationFormat::WFO)
|
|
{
|
|
const bool wfoIsWest = (wfo != "PGUM");
|
|
double westLongitude = (wfoIsWest) ? -1.0 : 1.0;
|
|
bool straddlesDateLine = false;
|
|
|
|
for (auto token = tokenList.cbegin() + 1; 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 WFO 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 (dataValid && !wfoIsWest && straddlesDateLine)
|
|
{
|
|
for (auto& coordinate : p->coordinates_)
|
|
{
|
|
coordinate.longitude_ *= -1.0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto token = tokenList.cbegin() + 1; token != tokenList.cend();
|
|
++token)
|
|
{
|
|
if (token->size() != 8)
|
|
{
|
|
logger_->warn("Invalid National Center LAT...LON format: \"{}\"",
|
|
*token);
|
|
|
|
dataValid = false;
|
|
break;
|
|
}
|
|
|
|
double latitude = 0.0;
|
|
double longitude = 0.0;
|
|
|
|
try
|
|
{
|
|
latitude = std::stod(token->substr(0, 4)) * 0.01;
|
|
longitude = std::stod(token->substr(4, 4)) * -0.01;
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
logger_->warn(
|
|
"Invalid National Center location token: \"{}\" ({})",
|
|
*token,
|
|
ex.what());
|
|
dataValid = false;
|
|
break;
|
|
}
|
|
|
|
// Longitudes of greater than 100 degrees will drop the leading 1;
|
|
// i.e., 105.22 W would be coded as 0522. This is ambiguous
|
|
// with 5.22 W, so we assume everything east of 65 W (east of Maine,
|
|
// easternmost point of CONUS) should have 100 degrees added to it.
|
|
// Points in the Atlantic or western Alaska will not be correct, but
|
|
// it is assumed that products will not contain those points coded
|
|
// using this methodology.
|
|
if (longitude > -65.0)
|
|
{
|
|
longitude -= 100.0;
|
|
}
|
|
|
|
p->coordinates_.push_back({latitude, longitude});
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (tokenList.empty())
|
|
{
|
|
logger_->warn("LAT...LON not found");
|
|
}
|
|
else
|
|
{
|
|
logger_->warn("Malformed LAT...LON tokens: (0: {}, size: {})",
|
|
tokenList.at(0),
|
|
tokenList.size());
|
|
|
|
for (const auto& token : tokenList)
|
|
{
|
|
logger_->debug("{}", token);
|
|
}
|
|
}
|
|
|
|
dataValid = false;
|
|
}
|
|
|
|
if (dataValid)
|
|
{
|
|
// If the last point is a repeat of the first point, remove it as
|
|
// redundant
|
|
if (p->coordinates_.front() == p->coordinates_.back())
|
|
{
|
|
p->coordinates_.pop_back();
|
|
}
|
|
}
|
|
|
|
return dataValid;
|
|
}
|
|
|
|
std::optional<CodedLocation> CodedLocation::Create(const StringRange& lines,
|
|
const std::string& wfo)
|
|
{
|
|
std::optional<CodedLocation> location = std::make_optional<CodedLocation>();
|
|
|
|
if (!location->Parse(lines, wfo))
|
|
{
|
|
location.reset();
|
|
}
|
|
|
|
return location;
|
|
}
|
|
|
|
} // namespace awips
|
|
} // namespace scwx
|