diff --git a/test/source/scwx/util/vectorbuf.test.cpp b/test/source/scwx/util/vectorbuf.test.cpp new file mode 100644 index 00000000..152be978 --- /dev/null +++ b/test/source/scwx/util/vectorbuf.test.cpp @@ -0,0 +1,37 @@ +#include + +#include + +namespace scwx +{ +namespace util +{ + +TEST(vectorbuf, smiles) +{ + std::vector v; + vectorbuf vb(v); + std::istream is(&vb); + + v.reserve(7); + memcpy(v.data(), "smiles", 7); + vb.update_read_pointers(7); + + EXPECT_EQ(is.eof(), false); + EXPECT_EQ(is.fail(), false); + + char data[7]; + is.read(data, 7); + + EXPECT_EQ(std::string(data), std::string("smiles")); + EXPECT_EQ(is.eof(), false); + EXPECT_EQ(is.fail(), false); + + is.read(data, 1); + + EXPECT_EQ(is.eof(), true); + EXPECT_EQ(is.fail(), true); +} + +} // namespace util +} // namespace scwx diff --git a/test/test.cmake b/test/test.cmake index c344548b..a24bd763 100644 --- a/test/test.cmake +++ b/test/test.cmake @@ -8,7 +8,8 @@ find_package(BZip2) find_package(GTest) set(SRC_MAIN source/scwx/wxtest.cpp) -set(SRC_UTIL_TESTS source/scwx/util/rangebuf.test.cpp) +set(SRC_UTIL_TESTS source/scwx/util/rangebuf.test.cpp + source/scwx/util/vectorbuf.test.cpp) set(SRC_WSR88D_TESTS source/scwx/wsr88d/ar2v_file.test.cpp) add_executable(wxtest ${SRC_MAIN} diff --git a/wxdata/include/scwx/util/vectorbuf.hpp b/wxdata/include/scwx/util/vectorbuf.hpp new file mode 100644 index 00000000..6673f97a --- /dev/null +++ b/wxdata/include/scwx/util/vectorbuf.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace scwx +{ +namespace util +{ + +class vectorbuf : public std::streambuf +{ +public: + vectorbuf(std::vector& v); + ~vectorbuf() = default; + + vectorbuf(const vectorbuf&) = delete; + vectorbuf& operator=(const vectorbuf&) = delete; + + void update_read_pointers(size_t size); + +private: + std::vector& v_; +}; + +} // namespace util +} // namespace scwx diff --git a/wxdata/include/scwx/wsr88d/ar2v_file.hpp b/wxdata/include/scwx/wsr88d/ar2v_file.hpp index 105ecd9a..7dc61bab 100644 --- a/wxdata/include/scwx/wsr88d/ar2v_file.hpp +++ b/wxdata/include/scwx/wsr88d/ar2v_file.hpp @@ -25,7 +25,7 @@ public: Ar2vFile& operator=(const Ar2vFile&) = delete; Ar2vFile(Ar2vFile&&) noexcept; - Ar2vFile& operator=(Ar2vFile&&); + Ar2vFile& operator=(Ar2vFile&&) noexcept; bool LoadFile(const std::string& filename); diff --git a/wxdata/include/scwx/wsr88d/rda/clutter_filter_map.hpp b/wxdata/include/scwx/wsr88d/rda/clutter_filter_map.hpp new file mode 100644 index 00000000..930820bf --- /dev/null +++ b/wxdata/include/scwx/wsr88d/rda/clutter_filter_map.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +namespace scwx +{ +namespace wsr88d +{ +namespace rda +{ + +class ClutterFilterMapImpl; + +class ClutterFilterMap : public Message +{ +public: + explicit ClutterFilterMap(); + ~ClutterFilterMap(); + + ClutterFilterMap(const Message&) = delete; + ClutterFilterMap& operator=(const ClutterFilterMap&) = delete; + + ClutterFilterMap(ClutterFilterMap&&) noexcept; + ClutterFilterMap& operator=(ClutterFilterMap&&) noexcept; + + uint16_t map_generation_date() const; + uint16_t map_generation_time() const; + uint16_t number_of_elevation_segments() const; + uint16_t number_of_range_zones(uint16_t e, uint16_t a) const; + uint16_t op_code(uint16_t e, uint16_t a, uint16_t z) const; + uint16_t end_range(uint16_t e, uint16_t a, uint16_t z) const; + + bool Parse(std::istream& is); + + static std::unique_ptr Create(MessageHeader&& header, + std::istream& is); + + static const size_t NUM_AZIMUTH_SEGMENTS = 360u; + +private: + std::unique_ptr p; +}; + +} // namespace rda +} // namespace wsr88d +} // namespace scwx diff --git a/wxdata/include/scwx/wsr88d/rda/message.hpp b/wxdata/include/scwx/wsr88d/rda/message.hpp new file mode 100644 index 00000000..202788f0 --- /dev/null +++ b/wxdata/include/scwx/wsr88d/rda/message.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include + +namespace scwx +{ +namespace wsr88d +{ +namespace rda +{ + +class MessageImpl; + +class Message +{ +protected: + explicit Message(); + + Message(const Message&) = delete; + Message& operator=(const Message&) = delete; + + Message(Message&&) noexcept; + Message& operator=(Message&&) noexcept; + + bool ValidateSize(std::istream& is, size_t bytesRead) const; + +public: + virtual ~Message(); + + const MessageHeader& header() const; + + void set_header(MessageHeader&& header); + + virtual bool Parse(std::istream& is) = 0; + +private: + std::unique_ptr p; +}; + +} // namespace rda +} // namespace wsr88d +} // namespace scwx diff --git a/wxdata/include/scwx/wsr88d/rda/message_factory.hpp b/wxdata/include/scwx/wsr88d/rda/message_factory.hpp new file mode 100644 index 00000000..6726e2c2 --- /dev/null +++ b/wxdata/include/scwx/wsr88d/rda/message_factory.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +namespace scwx +{ +namespace wsr88d +{ +namespace rda +{ + +struct MessageInfo +{ + std::unique_ptr message; + bool headerValid; + bool messageValid; + + MessageInfo() : message(nullptr), headerValid(false), messageValid(false) {} +}; + +class MessageFactory +{ +private: + explicit MessageFactory() = delete; + ~MessageFactory() = delete; + + MessageFactory(const Message&) = delete; + MessageFactory& operator=(const MessageFactory&) = delete; + + MessageFactory(MessageFactory&&) noexcept = delete; + MessageFactory& operator=(MessageFactory&&) noexcept = delete; + +public: + static MessageInfo Create(std::istream& is); +}; + +} // namespace rda +} // namespace wsr88d +} // namespace scwx diff --git a/wxdata/include/scwx/wsr88d/rda/message_header.hpp b/wxdata/include/scwx/wsr88d/rda/message_header.hpp index c1273a7a..8aa5db44 100644 --- a/wxdata/include/scwx/wsr88d/rda/message_header.hpp +++ b/wxdata/include/scwx/wsr88d/rda/message_header.hpp @@ -1,3 +1,5 @@ +#pragma once + #include #include @@ -20,7 +22,7 @@ public: MessageHeader& operator=(const MessageHeader&) = delete; MessageHeader(MessageHeader&&) noexcept; - MessageHeader& operator=(MessageHeader&&); + MessageHeader& operator=(MessageHeader&&) noexcept; uint16_t message_size() const; uint8_t rda_redundant_channel() const; @@ -31,6 +33,8 @@ public: uint16_t number_of_message_segments() const; uint16_t message_segment_number() const; + void set_message_size(uint16_t messageSize); + bool Parse(std::istream& is); static const size_t SIZE = 16u; diff --git a/wxdata/source/scwx/util/vectorbuf.cpp b/wxdata/source/scwx/util/vectorbuf.cpp new file mode 100644 index 00000000..2149e299 --- /dev/null +++ b/wxdata/source/scwx/util/vectorbuf.cpp @@ -0,0 +1,19 @@ +#include + +namespace scwx +{ +namespace util +{ + +vectorbuf::vectorbuf(std::vector& v) : v_(v) +{ + update_read_pointers(0); +} + +void vectorbuf::update_read_pointers(size_t size) +{ + setg(v_.data(), v_.data(), v_.data() + size); +} + +} // namespace util +} // namespace scwx diff --git a/wxdata/source/scwx/wsr88d/ar2v_file.cpp b/wxdata/source/scwx/wsr88d/ar2v_file.cpp index 4c8f5544..8bdcbbbf 100644 --- a/wxdata/source/scwx/wsr88d/ar2v_file.cpp +++ b/wxdata/source/scwx/wsr88d/ar2v_file.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include @@ -54,7 +54,7 @@ Ar2vFile::Ar2vFile() : p(std::make_unique()) {} Ar2vFile::~Ar2vFile() = default; Ar2vFile::Ar2vFile(Ar2vFile&&) noexcept = default; -Ar2vFile& Ar2vFile::operator=(Ar2vFile&&) = default; +Ar2vFile& Ar2vFile::operator=(Ar2vFile&&) noexcept = default; bool Ar2vFile::LoadFile(const std::string& filename) { @@ -112,6 +112,8 @@ bool Ar2vFile::LoadFile(const std::string& filename) void Ar2vFileImpl::LoadLDMRecords(std::ifstream& f) { + BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "Loading LDM Records"; + numRecords_ = 0; while (f.peek() != EOF) @@ -163,6 +165,8 @@ void Ar2vFileImpl::LoadLDMRecords(std::ifstream& f) void Ar2vFileImpl::ParseLDMRecords() { + BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "Parsing LDM Records"; + size_t count = 0; for (auto it = rawRecords_.begin(); it != rawRecords_.end(); it++) @@ -177,18 +181,13 @@ void Ar2vFileImpl::ParseLDMRecords() while (!ss.eof()) { - // TODO: Parse message, not just header - rda::MessageHeader header; - if (!header.Parse(ss)) + rda::MessageInfo msgInfo = rda::MessageFactory::Create(ss); + if (!msgInfo.headerValid) { - // Invalid header + // Invalid message break; } - // Seek to the end of the current message - ss.seekg(header.message_size() * 2 - rda::MessageHeader::SIZE, - std::ios_base::cur); - off_t offset = 0; uint16_t nextSize = 0u; do diff --git a/wxdata/source/scwx/wsr88d/rda/clutter_filter_map.cpp b/wxdata/source/scwx/wsr88d/rda/clutter_filter_map.cpp new file mode 100644 index 00000000..1b3bf8c3 --- /dev/null +++ b/wxdata/source/scwx/wsr88d/rda/clutter_filter_map.cpp @@ -0,0 +1,234 @@ +#include + +#include +#include + +#include + +#ifdef WIN32 +# include +#else +# include +#endif + +namespace scwx +{ +namespace wsr88d +{ +namespace rda +{ + +static const std::string logPrefix_ = + "[scwx::wsr88d::rda::clutter_filter_map] "; + +struct RangeZone +{ + uint16_t opCode; + uint16_t endRange; +}; + +class ClutterFilterMapImpl +{ +public: + explicit ClutterFilterMapImpl() : + mapGenerationDate_(), mapGenerationTime_(), rangeZones_() {}; + ~ClutterFilterMapImpl() = default; + + uint16_t mapGenerationDate_; + uint16_t mapGenerationTime_; + + std::vector>> rangeZones_; +}; + +ClutterFilterMap::ClutterFilterMap() : + Message(), p(std::make_unique()) +{ +} +ClutterFilterMap::~ClutterFilterMap() = default; + +ClutterFilterMap::ClutterFilterMap(ClutterFilterMap&&) noexcept = default; +ClutterFilterMap& +ClutterFilterMap::operator=(ClutterFilterMap&&) noexcept = default; + +uint16_t ClutterFilterMap::map_generation_date() const +{ + return p->mapGenerationDate_; +} + +uint16_t ClutterFilterMap::map_generation_time() const +{ + return p->mapGenerationTime_; +} + +uint16_t ClutterFilterMap::number_of_elevation_segments() const +{ + return static_cast(p->rangeZones_.size()); +} + +uint16_t ClutterFilterMap::number_of_range_zones(uint16_t e, uint16_t a) const +{ + return static_cast(p->rangeZones_[e][a].size()); +} + +uint16_t ClutterFilterMap::op_code(uint16_t e, uint16_t a, uint16_t z) const +{ + return p->rangeZones_[e][a][z].opCode; +} + +uint16_t ClutterFilterMap::end_range(uint16_t e, uint16_t a, uint16_t z) const +{ + return p->rangeZones_[e][a][z].endRange; +} + +bool ClutterFilterMap::Parse(std::istream& is) +{ + BOOST_LOG_TRIVIAL(debug) + << logPrefix_ << "Parsing Clutter Filter Map (Message Type 15)"; + + bool messageValid = true; + size_t bytesRead = 0; + uint16_t numElevationSegments = 0; + + is.read(reinterpret_cast(&p->mapGenerationDate_), 2); + is.read(reinterpret_cast(&p->mapGenerationTime_), 2); + is.read(reinterpret_cast(&numElevationSegments), 2); + bytesRead += 6; + + p->mapGenerationDate_ = htons(p->mapGenerationDate_); + p->mapGenerationTime_ = htons(p->mapGenerationTime_); + numElevationSegments = htons(numElevationSegments); + + if (is.eof()) + { + BOOST_LOG_TRIVIAL(warning) << logPrefix_ << "Reached end of file (1)"; + messageValid = false; + } + else + { + if (p->mapGenerationDate_ < 1) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Invalid date: " << p->mapGenerationDate_; + messageValid = false; + } + if (p->mapGenerationTime_ > 1440) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Invalid time: " << p->mapGenerationTime_; + messageValid = false; + } + if (numElevationSegments < 1 || numElevationSegments > 5) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ + << "Invalid number of elevation segments: " << numElevationSegments; + messageValid = false; + } + } + + if (!messageValid) + { + numElevationSegments = 0; + } + + p->rangeZones_.resize(numElevationSegments); + + for (uint16_t e = 0; e < numElevationSegments && messageValid; e++) + { + p->rangeZones_[e].resize(NUM_AZIMUTH_SEGMENTS); + + for (uint16_t a = 0; a < NUM_AZIMUTH_SEGMENTS && messageValid; a++) + { + uint16_t numRangeZones; + is.read(reinterpret_cast(&numRangeZones), 2); + bytesRead += 2; + + numRangeZones = htons(numRangeZones); + + if (is.eof()) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Reached end of file (2)"; + messageValid = false; + } + else + { + if (numRangeZones < 1 || numRangeZones > 20) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ + << "Invalid number of range zones: " << numRangeZones; + messageValid = false; + } + } + + if (!messageValid) + { + break; + } + + p->rangeZones_[e][a].resize(numRangeZones); + + for (uint16_t z = 0; z < numRangeZones && messageValid; z++) + { + RangeZone& zone = p->rangeZones_[e][a][z]; + + is.read(reinterpret_cast(&zone.opCode), 2); + is.read(reinterpret_cast(&zone.endRange), 2); + bytesRead += 4; + + zone.opCode = htons(zone.opCode); + zone.endRange = htons(zone.endRange); + + if (is.eof()) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Reached end of file (3)"; + messageValid = false; + } + else + { + if (zone.opCode > 2) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Invalid op code: " << zone.opCode; + messageValid = false; + } + if (zone.endRange > 511) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Invalid end range: " << zone.endRange; + messageValid = false; + } + } + } + } + } + + if (!ValidateSize(is, bytesRead)) + { + messageValid = false; + } + + if (!messageValid) + { + p->rangeZones_.resize(0); + p->rangeZones_.shrink_to_fit(); + } + + return messageValid; +} + +std::unique_ptr +ClutterFilterMap::Create(MessageHeader&& header, std::istream& is) +{ + std::unique_ptr message = + std::make_unique(); + message->set_header(std::move(header)); + message->Parse(is); + return message; +} + +} // namespace rda +} // namespace wsr88d +} // namespace scwx diff --git a/wxdata/source/scwx/wsr88d/rda/message.cpp b/wxdata/source/scwx/wsr88d/rda/message.cpp new file mode 100644 index 00000000..3230ea69 --- /dev/null +++ b/wxdata/source/scwx/wsr88d/rda/message.cpp @@ -0,0 +1,72 @@ +#include + +#include + +#include + +namespace scwx +{ +namespace wsr88d +{ +namespace rda +{ + +static const std::string logPrefix_ = "[scwx::wsr88d::rda::message] "; + +class MessageImpl +{ +public: + explicit MessageImpl() : header_() {}; + ~MessageImpl() = default; + + MessageHeader header_; +}; + +Message::Message() : p(std::make_unique()) {} +Message::~Message() = default; + +Message::Message(Message&&) noexcept = default; +Message& Message::operator=(Message&&) noexcept = default; + +bool Message::ValidateSize(std::istream& is, size_t bytesRead) const +{ + bool messageValid = true; + size_t dataSize = header().message_size() * 2 - header().SIZE; + + if (bytesRead != dataSize) + { + is.seekg(static_cast(dataSize) - + static_cast(bytesRead), + std::ios_base::cur); + + if (bytesRead < dataSize) + { + BOOST_LOG_TRIVIAL(trace) + << logPrefix_ << "Message contents smaller than size: " << bytesRead + << " < " << dataSize << " bytes"; + } + if (bytesRead > dataSize) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Message contents larger than size: " << bytesRead + << " > " << dataSize << " bytes"; + messageValid = false; + } + } + + return messageValid; +} + +const MessageHeader& Message::header() const +{ + return p->header_; +} + +void Message::set_header(MessageHeader&& header) +{ + p->header_ = std::move(header); +} + +} // namespace rda +} // namespace wsr88d +} // namespace scwx diff --git a/wxdata/source/scwx/wsr88d/rda/message_factory.cpp b/wxdata/source/scwx/wsr88d/rda/message_factory.cpp new file mode 100644 index 00000000..dae208d4 --- /dev/null +++ b/wxdata/source/scwx/wsr88d/rda/message_factory.cpp @@ -0,0 +1,130 @@ +#include + +#include +#include + +#include +#include +#include + +#include + +namespace scwx +{ +namespace wsr88d +{ +namespace rda +{ + +static const std::string logPrefix_ = "[scwx::wsr88d::rda::message_factory] "; + +typedef std::function(MessageHeader&&, std::istream&)> + CreateMessageFunction; + +static const std::unordered_map create_ { + {15, ClutterFilterMap::Create}}; + +static std::vector messageData_; +static size_t bufferedSize_; +static util::vectorbuf messageBuffer_(messageData_); +static std::istream messageBufferStream_(&messageBuffer_); + +MessageInfo MessageFactory::Create(std::istream& is) +{ + MessageInfo info; + MessageHeader header; + info.headerValid = header.Parse(is); + info.messageValid = info.headerValid; + + if (info.headerValid && create_.find(header.message_type()) == create_.end()) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "Unknown message type: " + << static_cast(header.message_type()); + info.messageValid = false; + } + + if (info.messageValid) + { + uint16_t segment = header.message_segment_number(); + uint16_t totalSegments = header.number_of_message_segments(); + uint8_t messageType = header.message_type(); + size_t dataSize = header.message_size() * 2 - MessageHeader::SIZE; + + std::istream* messageStream = nullptr; + + if (totalSegments == 1) + { + BOOST_LOG_TRIVIAL(trace) << logPrefix_ << "Found Message " + << static_cast(messageType); + messageStream = &is; + } + else + { + BOOST_LOG_TRIVIAL(trace) + << logPrefix_ << "Found Message " + << static_cast(messageType) << " Segment " << segment + << "/" << totalSegments; + + if (segment == 1) + { + // Estimate total message size + messageData_.reserve(dataSize * totalSegments); + bufferedSize_ = 0; + } + + if (messageData_.capacity() < bufferedSize_ + dataSize) + { + BOOST_LOG_TRIVIAL(debug) + << logPrefix_ << "Bad size estimate, increasing size"; + + // Estimate remaining size + uint16_t remainingSegments = + std::max(totalSegments - segment + 1, 100u); + size_t remainingSize = remainingSegments * dataSize; + + messageData_.reserve(bufferedSize_ + remainingSize); + } + + is.read(messageData_.data() + bufferedSize_, dataSize); + bufferedSize_ += dataSize; + + if (is.eof()) + { + BOOST_LOG_TRIVIAL(warning) + << logPrefix_ << "End of file reached trying to buffer message"; + info.messageValid = false; + messageData_.shrink_to_fit(); + bufferedSize_ = 0; + } + else if (segment == totalSegments) + { + messageBuffer_.update_read_pointers(bufferedSize_); + header.set_message_size( + static_cast(bufferedSize_ / 2 + MessageHeader::SIZE)); + + messageStream = &messageBufferStream_; + } + } + + if (messageStream != nullptr) + { + info.message = + create_.at(messageType)(std::move(header), *messageStream); + messageData_.shrink_to_fit(); + bufferedSize_ = 0; + } + } + else if (info.headerValid) + { + // Seek to the end of the current message + is.seekg(header.message_size() * 2 - rda::MessageHeader::SIZE, + std::ios_base::cur); + } + + return info; +} + +} // namespace rda +} // namespace wsr88d +} // namespace scwx diff --git a/wxdata/source/scwx/wsr88d/rda/message_header.cpp b/wxdata/source/scwx/wsr88d/rda/message_header.cpp index 1a3a1c34..45a37d43 100644 --- a/wxdata/source/scwx/wsr88d/rda/message_header.cpp +++ b/wxdata/source/scwx/wsr88d/rda/message_header.cpp @@ -44,14 +44,11 @@ public: uint16_t messageSegmentNumber_; }; -MessageHeader::MessageHeader() : - p(std::make_unique()) -{ -} +MessageHeader::MessageHeader() : p(std::make_unique()) {} MessageHeader::~MessageHeader() = default; MessageHeader::MessageHeader(MessageHeader&&) noexcept = default; -MessageHeader& MessageHeader::operator=(MessageHeader&&) = default; +MessageHeader& MessageHeader::operator=(MessageHeader&&) noexcept = default; uint16_t MessageHeader::message_size() const { @@ -93,6 +90,11 @@ uint16_t MessageHeader::message_segment_number() const return p->messageSegmentNumber_; } +void MessageHeader::set_message_size(uint16_t messageSize) +{ + p->messageSize_ = messageSize; +} + bool MessageHeader::Parse(std::istream& is) { bool headerValid = true; diff --git a/wxdata/wxdata.cmake b/wxdata/wxdata.cmake index f9299613..7c153faa 100644 --- a/wxdata/wxdata.cmake +++ b/wxdata/wxdata.cmake @@ -2,12 +2,20 @@ project(scwx-data) find_package(Boost) -set(HDR_UTIL include/scwx/util/rangebuf.hpp) -set(SRC_UTIL source/scwx/util/rangebuf.cpp) +set(HDR_UTIL include/scwx/util/rangebuf.hpp + include/scwx/util/vectorbuf.hpp) +set(SRC_UTIL source/scwx/util/rangebuf.cpp + source/scwx/util/vectorbuf.cpp) set(HDR_WSR88D include/scwx/wsr88d/ar2v_file.hpp) set(SRC_WSR88D source/scwx/wsr88d/ar2v_file.cpp) -set(HDR_WSR88D_RDA include/scwx/wsr88d/rda/message_header.hpp) -set(SRC_WSR88D_RDA source/scwx/wsr88d/rda/message_header.cpp) +set(HDR_WSR88D_RDA include/scwx/wsr88d/rda/clutter_filter_map.hpp + include/scwx/wsr88d/rda/message.hpp + include/scwx/wsr88d/rda/message_factory.hpp + include/scwx/wsr88d/rda/message_header.hpp) +set(SRC_WSR88D_RDA source/scwx/wsr88d/rda/clutter_filter_map.cpp + source/scwx/wsr88d/rda/message.cpp + source/scwx/wsr88d/rda/message_factory.cpp + source/scwx/wsr88d/rda/message_header.cpp) add_library(wxdata OBJECT ${HDR_UTIL} ${SRC_UTIL}