#include #include namespace scwx { namespace wsr88d { namespace rda { static const std::string logPrefix_ = "scwx::wsr88d::rda::volume_coverage_pattern_data"; static const auto logger_ = util::Logger::Create(logPrefix_); struct Sector; static void ReadSector(std::istream& is, Sector& s); static void SwapSector(Sector& s); struct Sector { uint16_t edgeAngle_; uint16_t dopplerPrfNumber_; uint16_t dopplerPrfPulseCountRadial_; Sector() : edgeAngle_ {0}, dopplerPrfNumber_ {0}, dopplerPrfPulseCountRadial_ {0} { } }; struct ElevationCut { uint16_t elevationAngle_; uint8_t channelConfiguration_; uint8_t waveformType_; uint8_t superResolutionControl_; uint8_t surveillancePrfNumber_; uint16_t surveillancePrfPulseCountRadial_; int16_t azimuthRate_; uint16_t reflectivityThreshold_; uint16_t velocityThreshold_; uint16_t spectrumWidthThreshold_; uint16_t differentialReflectivityThreshold_; uint16_t differentialPhaseThreshold_; uint16_t correlationCoefficientThreshold_; std::array sector_; uint16_t supplementalData_; uint16_t ebcAngle_; ElevationCut() : elevationAngle_ {0}, channelConfiguration_ {0}, waveformType_ {0}, superResolutionControl_ {0}, surveillancePrfNumber_ {0}, surveillancePrfPulseCountRadial_ {0}, azimuthRate_ {0}, reflectivityThreshold_ {0}, velocityThreshold_ {0}, spectrumWidthThreshold_ {0}, differentialReflectivityThreshold_ {0}, differentialPhaseThreshold_ {0}, correlationCoefficientThreshold_ {0}, sector_(), supplementalData_ {0}, ebcAngle_ {0} { } }; class VolumeCoveragePatternDataImpl { public: explicit VolumeCoveragePatternDataImpl() : patternType_ {0}, patternNumber_ {0}, version_ {0}, clutterMapGroupNumber_ {0}, dopplerVelocityResolution_ {0}, pulseWidth_ {0}, vcpSequencing_ {0}, vcpSupplementalData_ {0}, elevationCuts_() {}; ~VolumeCoveragePatternDataImpl() = default; uint16_t patternType_; uint16_t patternNumber_; uint8_t version_; uint8_t clutterMapGroupNumber_; uint8_t dopplerVelocityResolution_; uint8_t pulseWidth_; uint16_t vcpSequencing_; uint16_t vcpSupplementalData_; std::vector elevationCuts_; }; VolumeCoveragePatternData::VolumeCoveragePatternData() : Level2Message(), p(std::make_unique()) { } VolumeCoveragePatternData::~VolumeCoveragePatternData() = default; VolumeCoveragePatternData::VolumeCoveragePatternData( VolumeCoveragePatternData&&) noexcept = default; VolumeCoveragePatternData& VolumeCoveragePatternData::operator=( VolumeCoveragePatternData&&) noexcept = default; uint16_t VolumeCoveragePatternData::pattern_type() const { return p->patternType_; } uint16_t VolumeCoveragePatternData::pattern_number() const { return p->patternNumber_; } uint16_t VolumeCoveragePatternData::number_of_elevation_cuts() const { return static_cast(p->elevationCuts_.size()); } uint8_t VolumeCoveragePatternData::version() const { return p->version_; } uint8_t VolumeCoveragePatternData::clutter_map_group_number() const { return p->clutterMapGroupNumber_; } float VolumeCoveragePatternData::doppler_velocity_resolution() const { float resolution = 0.0f; switch (p->dopplerVelocityResolution_) { case 2: resolution = 0.5f; break; case 4: resolution = 1.0f; break; } return resolution; } uint8_t VolumeCoveragePatternData::pulse_width() const { return p->pulseWidth_; } uint16_t VolumeCoveragePatternData::vcp_sequencing() const { return p->vcpSequencing_; } uint16_t VolumeCoveragePatternData::number_of_elevations() const { return p->vcpSequencing_ & 0x001f; } uint16_t VolumeCoveragePatternData::maximum_sails_cuts() const { return (p->vcpSequencing_ & 0x0060) >> 5; } bool VolumeCoveragePatternData::sequence_active() const { return p->vcpSequencing_ & 0x2000; } bool VolumeCoveragePatternData::truncated_vcp() const { return p->vcpSequencing_ & 0x4000; } uint16_t VolumeCoveragePatternData::vcp_supplemental_data() const { return p->vcpSupplementalData_; } bool VolumeCoveragePatternData::sails_vcp() const { return p->vcpSupplementalData_ & 0x0001; } uint16_t VolumeCoveragePatternData::number_of_sails_cuts() const { return (p->vcpSupplementalData_ & 0x000E) >> 1; } bool VolumeCoveragePatternData::mrle_vcp() const { return p->vcpSupplementalData_ & 0x0010; } uint16_t VolumeCoveragePatternData::number_of_mrle_cuts() const { return (p->vcpSupplementalData_ & 0x00E0) >> 5; } bool VolumeCoveragePatternData::mpda_vcp() const { return p->vcpSupplementalData_ & 0x0800; } bool VolumeCoveragePatternData::base_tilt_vcp() const { return p->vcpSupplementalData_ & 0x1000; } uint16_t VolumeCoveragePatternData::number_of_base_tilts() const { return (p->vcpSupplementalData_ & 0xE000) >> 13; } double VolumeCoveragePatternData::elevation_angle(uint16_t e) const { double elevationAngleConverted = p->elevationCuts_[e].elevationAngle_ * ANGLE_DATA_SCALE; // Any elevation above 90 degrees should be interpreted as a // negative angle // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) if (elevationAngleConverted > 90) { elevationAngleConverted -= 360; } // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) return elevationAngleConverted; } uint16_t VolumeCoveragePatternData::elevation_angle_raw(uint16_t e) const { return p->elevationCuts_[e].elevationAngle_; } uint8_t VolumeCoveragePatternData::channel_configuration(uint16_t e) const { return p->elevationCuts_[e].channelConfiguration_; } WaveformType VolumeCoveragePatternData::waveform_type(uint16_t e) const { switch (p->elevationCuts_[e].waveformType_) { case 1: return WaveformType::ContiguousSurveillance; case 2: return WaveformType::ContiguousDopplerWithAmbiguityResolution; case 3: return WaveformType::ContiguousDopplerWithoutAmbiguityResolution; case 4: return WaveformType::Batch; case 5: return WaveformType::StaggeredPulsePair; default: return WaveformType::Unknown; } } uint8_t VolumeCoveragePatternData::waveform_type_raw(uint16_t e) const { return p->elevationCuts_[e].waveformType_; } uint8_t VolumeCoveragePatternData::super_resolution_control(uint16_t e) const { return p->elevationCuts_[e].superResolutionControl_; } bool VolumeCoveragePatternData::half_degree_azimuth(uint16_t e) const { return p->elevationCuts_[e].superResolutionControl_ & 0x01; } bool VolumeCoveragePatternData::quarter_km_reflectivity(uint16_t e) const { return p->elevationCuts_[e].superResolutionControl_ & 0x02; } bool VolumeCoveragePatternData::doppler_to_300km(uint16_t e) const { return p->elevationCuts_[e].superResolutionControl_ & 0x04; } bool VolumeCoveragePatternData::dual_polarization_to_300km(uint16_t e) const { return p->elevationCuts_[e].superResolutionControl_ & 0x08; } uint8_t VolumeCoveragePatternData::surveillance_prf_number(uint16_t e) const { return p->elevationCuts_[e].surveillancePrfNumber_; } uint16_t VolumeCoveragePatternData::surveillance_prf_pulse_count_radial(uint16_t e) const { return p->elevationCuts_[e].surveillancePrfPulseCountRadial_; } double VolumeCoveragePatternData::azimuth_rate(uint16_t e) const { return p->elevationCuts_[e].azimuthRate_ * AZ_EL_RATE_DATA_SCALE; } float VolumeCoveragePatternData::reflectivity_threshold(uint16_t e) const { return p->elevationCuts_[e].reflectivityThreshold_ * 0.125f; } float VolumeCoveragePatternData::velocity_threshold(uint16_t e) const { return p->elevationCuts_[e].velocityThreshold_ * 0.125f; } float VolumeCoveragePatternData::spectrum_width_threshold(uint16_t e) const { return p->elevationCuts_[e].spectrumWidthThreshold_ * 0.125f; } float VolumeCoveragePatternData::differential_reflectivity_threshold( uint16_t e) const { return p->elevationCuts_[e].differentialReflectivityThreshold_ * 0.125f; } float VolumeCoveragePatternData::differential_phase_threshold(uint16_t e) const { return p->elevationCuts_[e].differentialPhaseThreshold_ * 0.125f; } float VolumeCoveragePatternData::correlation_coefficient_threshold( uint16_t e) const { return p->elevationCuts_[e].correlationCoefficientThreshold_ * 0.125f; } uint16_t VolumeCoveragePatternData::supplemental_data(uint16_t e) const { return p->elevationCuts_[e].supplementalData_; } bool VolumeCoveragePatternData::sails_cut(uint16_t e) const { return p->elevationCuts_[e].supplementalData_ & 0x0001; } uint16_t VolumeCoveragePatternData::sails_sequence_number(uint16_t e) const { return (p->elevationCuts_[e].supplementalData_ & 0x000E) >> 1; } bool VolumeCoveragePatternData::mrle_cut(uint16_t e) const { return p->elevationCuts_[e].supplementalData_ & 0x0010; } uint16_t VolumeCoveragePatternData::mrle_sequence_number(uint16_t e) const { return (p->elevationCuts_[e].supplementalData_ & 0x00E0) >> 5; } bool VolumeCoveragePatternData::mpda_cut(uint16_t e) const { return p->elevationCuts_[e].supplementalData_ & 0x0200; } bool VolumeCoveragePatternData::base_tilt_cut(uint16_t e) const { return p->elevationCuts_[e].supplementalData_ & 0x0400; } double VolumeCoveragePatternData::ebc_angle(uint16_t e) const { return p->elevationCuts_[e].ebcAngle_ * ANGLE_DATA_SCALE; } double VolumeCoveragePatternData::edge_angle(uint16_t e, uint16_t s) const { return p->elevationCuts_[e].sector_[s].edgeAngle_ * ANGLE_DATA_SCALE; } uint16_t VolumeCoveragePatternData::doppler_prf_number(uint16_t e, uint16_t s) const { return p->elevationCuts_[e].sector_[s].dopplerPrfNumber_; } uint16_t VolumeCoveragePatternData::doppler_prf_pulse_count_radial(uint16_t e, uint16_t s) const { return p->elevationCuts_[e].sector_[s].dopplerPrfPulseCountRadial_; } bool VolumeCoveragePatternData::Parse(std::istream& is) { logger_->trace("Parsing Volume Coverage Pattern Data (Message Type 5)"); bool messageValid = true; size_t bytesRead = 0; uint16_t messageSize = 0; uint16_t numberOfElevationCuts = 0; is.read(reinterpret_cast(&messageSize), 2); // 1 is.read(reinterpret_cast(&p->patternType_), 2); // 2 is.read(reinterpret_cast(&p->patternNumber_), 2); // 3 is.read(reinterpret_cast(&numberOfElevationCuts), 2); // 4 is.read(reinterpret_cast(&p->version_), 1); // 5 is.read(reinterpret_cast(&p->clutterMapGroupNumber_), 1); // 5 is.read(reinterpret_cast(&p->dopplerVelocityResolution_), 1); // 6 is.read(reinterpret_cast(&p->pulseWidth_), 1); // 6 is.seekg(4, std::ios_base::cur); // 7-8 is.read(reinterpret_cast(&p->vcpSequencing_), 2); // 9 is.read(reinterpret_cast(&p->vcpSupplementalData_), 2); // 10 is.seekg(2, std::ios_base::cur); // 11 bytesRead += 22; messageSize = ntohs(messageSize); p->patternType_ = ntohs(p->patternType_); p->patternNumber_ = ntohs(p->patternNumber_); numberOfElevationCuts = ntohs(numberOfElevationCuts); p->vcpSequencing_ = ntohs(p->vcpSequencing_); p->vcpSupplementalData_ = ntohs(p->vcpSupplementalData_); if (messageSize == 0) { logger_->trace("Ignoring empty message"); messageValid = false; } else { if (messageSize < 34 || messageSize > 747) { logger_->warn("Invalid message size: {}", messageSize); messageValid = false; } if (numberOfElevationCuts < 1 || numberOfElevationCuts > 32) { logger_->warn("Invalid number of elevation cuts: {}", numberOfElevationCuts); messageValid = false; } } if (!messageValid) { numberOfElevationCuts = 0; } p->elevationCuts_.resize(numberOfElevationCuts); for (uint16_t e = 0; e < numberOfElevationCuts; ++e) { ElevationCut& c = p->elevationCuts_[e]; is.read(reinterpret_cast(&c.elevationAngle_), 2); // E1 is.read(reinterpret_cast(&c.channelConfiguration_), 1); // E2 is.read(reinterpret_cast(&c.waveformType_), 1); // E2 is.read(reinterpret_cast(&c.superResolutionControl_), 1); // E3 is.read(reinterpret_cast(&c.surveillancePrfNumber_), 1); // E3 is.read(reinterpret_cast(&c.surveillancePrfPulseCountRadial_), 2); // E4 is.read(reinterpret_cast(&c.azimuthRate_), 2); // E5 is.read(reinterpret_cast(&c.reflectivityThreshold_), 2); // E6 is.read(reinterpret_cast(&c.velocityThreshold_), 2); // E7 is.read(reinterpret_cast(&c.spectrumWidthThreshold_), 2); // E8 is.read(reinterpret_cast(&c.differentialReflectivityThreshold_), 2); // E9 is.read(reinterpret_cast(&c.differentialPhaseThreshold_), 2); // E10 is.read(reinterpret_cast(&c.correlationCoefficientThreshold_), 2); // E11 ReadSector(is, c.sector_[0]); // E12-E14 is.read(reinterpret_cast(&c.supplementalData_), 2); // E15 ReadSector(is, c.sector_[1]); // E16-E18 is.read(reinterpret_cast(&c.ebcAngle_), 2); // E19 ReadSector(is, c.sector_[2]); // E20-E22 is.seekg(2, std::ios_base::cur); // E23 bytesRead += 46; c.elevationAngle_ = ntohs(c.elevationAngle_); c.surveillancePrfPulseCountRadial_ = ntohs(c.surveillancePrfPulseCountRadial_); c.azimuthRate_ = ntohs(c.azimuthRate_); c.reflectivityThreshold_ = ntohs(c.reflectivityThreshold_); c.velocityThreshold_ = ntohs(c.velocityThreshold_); c.spectrumWidthThreshold_ = ntohs(c.spectrumWidthThreshold_); c.differentialReflectivityThreshold_ = ntohs(c.differentialReflectivityThreshold_); c.differentialPhaseThreshold_ = ntohs(c.differentialPhaseThreshold_); c.correlationCoefficientThreshold_ = ntohs(c.correlationCoefficientThreshold_); c.supplementalData_ = ntohs(c.supplementalData_); c.ebcAngle_ = ntohs(c.ebcAngle_); for (size_t s = 0; s < c.sector_.size(); s++) { SwapSector(c.sector_[s]); } } if (messageValid && bytesRead != messageSize * 2) { logger_->warn("Bytes read ({}) not equal to message size ({})", bytesRead, messageSize * 2); } if (!ValidateMessage(is, bytesRead)) { messageValid = false; } if (!messageValid) { p->elevationCuts_.resize(0); p->elevationCuts_.shrink_to_fit(); } return messageValid; } std::shared_ptr VolumeCoveragePatternData::Create(Level2MessageHeader&& header, std::istream& is) { std::shared_ptr message = std::make_shared(); message->set_header(std::move(header)); if (!message->Parse(is)) { message.reset(); } return message; } static void ReadSector(std::istream& is, Sector& s) { is.read(reinterpret_cast(&s.edgeAngle_), 2); // S1 is.read(reinterpret_cast(&s.dopplerPrfNumber_), 2); // S2 is.read(reinterpret_cast(&s.dopplerPrfPulseCountRadial_), 2); // S3 } static void SwapSector(Sector& s) { s.edgeAngle_ = ntohs(s.edgeAngle_); s.dopplerPrfNumber_ = ntohs(s.dopplerPrfNumber_); s.dopplerPrfPulseCountRadial_ = ntohs(s.dopplerPrfPulseCountRadial_); } } // namespace rda } // namespace wsr88d } // namespace scwx