/** * @file GPS solver with RAIM (Receiver Autonomous Integrity Monitoring) */ /* * Copyright (c) 2021, M.Naruoka (fenrir) * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * - Neither the name of the naruoka.org nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #ifndef __GPS_SOLVER_RAIM_H__ #define __GPS_SOLVER_RAIM_H__ #include "GPS_Solver_Base.h" #include <algorithm> #include <deque> template <class FloatT, class PVT_BaseT = typename GPS_Solver_Base<FloatT>::user_pvt_t> struct GPS_PVT_RAIM_LSR : public PVT_BaseT { struct detection_t { bool valid; struct slope_t { FloatT max; typename GPS_Solver_Base<FloatT>::prn_t prn; } slope_HV[2]; FloatT wssr; FloatT wssr_sf; ///< for nominal bias consideration, sum(eigen.values(W (I - P))) FloatT weight_max; } FD; ///< Fault detection struct exclusion_t : public detection_t { typename GPS_Solver_Base<FloatT>::prn_t excluded; typename GPS_Solver_Base<FloatT>::pos_t user_position; typename GPS_Solver_Base<FloatT>::float_t receiver_error; typename GPS_Solver_Base<FloatT>::user_pvt_t::precision_t dop, sigma_pos; } FDE_min, FDE_2nd; ///< Fault exclusion }; template <class FloatT> struct GPS_Solver_RAIM_LSR_Options { bool skip_exclusion; GPS_Solver_RAIM_LSR_Options() : skip_exclusion(false) {} }; /* * Comment on implementation of protection level (PL) calculation * * To calculate PL, WSSR threshold is firstly required. * WSSR threshold is a function of P_fa (false alarm), DoF (degree of freedom) * corresponding to number of usable satellites minus 4, and nominal biases. * If nominal biases are configured as zeros, the threshold can be implemented * as a map whose key is DoF, without online calculation of CFD of chi-squred distribution. * However, in order to consider nominal biases, online calculation is required, * and chi-squred distribution also must be implemented. * Therefore, currently, PL is assumed to be calculated outside this program, * which can be performed with wssr, wssr_sf, slope_HV. */ template <class FloatT, class SolverBaseT = GPS_Solver_Base<FloatT> > struct GPS_Solver_RAIM_LSR : public SolverBaseT { typedef SolverBaseT super_t; typedef GPS_Solver_RAIM_LSR<FloatT, SolverBaseT> self_t; virtual ~GPS_Solver_RAIM_LSR() {} #if defined(__GNUC__) && (__GNUC__ < 5) #define inheritate_type(x) typedef typename super_t::x x; #else #define inheritate_type(x) using typename super_t::x; #endif inheritate_type(float_t); inheritate_type(matrix_t); inheritate_type(gps_time_t); inheritate_type(pos_t); inheritate_type(geometric_matrices_t); inheritate_type(measurement2_t); #undef inheritate_type typedef typename GPS_Solver_Base<float_t>::options_t::template merge_t< GPS_Solver_RAIM_LSR_Options<float_t>, super_t> options_t; protected: GPS_Solver_RAIM_LSR_Options<float_t> _options; public: options_t available_options() const { return options_t(super_t::available_options(), _options); } options_t available_options(const options_t &opt_wish) const { GPS_Solver_RAIM_LSR_Options<float_t> opt(opt_wish); return options_t(super_t::available_options(opt_wish), opt); } options_t update_options(const options_t &opt_wish){ _options = opt_wish; return options_t(super_t::update_options(opt_wish), _options); } typedef GPS_PVT_RAIM_LSR<float_t, typename super_t::user_pvt_t> user_pvt_t; typename super_t::template solver_interface_t<self_t> solve() const { return typename super_t::template solver_interface_t<self_t>(*this); } protected: typedef GPS_Solver_Base<FloatT> base_t; bool update_position_solution( // overriding function const geometric_matrices_t &geomat, typename base_t::user_pvt_t &res) const { if(!super_t::update_position_solution(geomat, res)){return false;} user_pvt_t &pvt(static_cast<user_pvt_t &>(res)); if(!(pvt.FD.valid = (pvt.used_satellites >= 5))){return true;} // Perform least square again for Fault Detection matrix_t S, W_dash; typename geometric_matrices_t::partial_t geomat2(geomat.partial(res.used_satellites)); geomat2.least_square(S); pvt.FD.wssr = geomat2.wssr_S(S, &W_dash); pvt.FD.wssr_sf = W_dash.trace(); pvt.FD.weight_max = *std::max_element(geomat2.W.cbegin(), geomat2.W.cend()); matrix_t slope_HV(geomat2.slope_HV(S, res.user_position.ecef2enu())); std::vector<int> prn_list(res.used_satellite_mask.indices_one()); for(unsigned i(0); i < 2; ++i){ // horizontal, vertical typename matrix_t::partial_t slope_HV_i(slope_HV.partial(slope_HV.rows(), 1, 0, i)); typename matrix_t::partial_t::const_iterator it( std::max_element(slope_HV_i.cbegin(), slope_HV_i.cend())); unsigned int row(it.row()); pvt.FD.slope_HV[i].max = *it; pvt.FD.slope_HV[i].prn = (typename GPS_Solver_Base<FloatT>::prn_t)prn_list[row]; } return true; } public: using super_t::user_pvt; protected: void user_pvt( // overriding function typename base_t::user_pvt_t &res, const measurement2_t &measurement, const gps_time_t &receiver_time, const pos_t &user_position_init, const float_t &receiver_error_init, const typename base_t::user_pvt_opt_t &opt = typename base_t::user_pvt_opt_t()) const { user_pvt_t &pvt(static_cast<user_pvt_t &>(res)); pvt.FD.valid = pvt.FDE_min.valid = pvt.FDE_2nd.valid = false; // Solution with full satellites super_t::user_pvt(res, measurement, receiver_time, user_position_init, receiver_error_init, opt); if(_options.skip_exclusion || !pvt.position_solved() || (!pvt.FD.valid) || (pvt.used_satellites < 6)){return;} // Generate full set std::deque<typename measurement2_t::value_type> fullset; for(typename measurement2_t::const_iterator it(measurement.begin()), it_end(measurement.end()); it != it_end; ++it){ if(!pvt.used_satellite_mask[it->prn]){continue;} fullset.push_back(*it); } // Subset calculation for Fault Exclusion pvt.FDE_min.wssr = pvt.FDE_2nd.wssr = pvt.FD.wssr; user_pvt_t pvt_FDE(pvt); typename base_t::user_pvt_opt_t opt_subset(true, false); // good init, without velocity for(unsigned int i(0); i < pvt.used_satellites - 1; ++i, fullset.push_back(fullset.front()), fullset.pop_front()){ pvt_FDE.FD.valid = false; measurement2_t subset(fullset.begin(), fullset.end() - 1); super_t::user_pvt(pvt_FDE, subset, receiver_time, pvt.user_position, pvt.receiver_error, opt_subset); if(!pvt_FDE.FD.valid){continue;} switch(pvt_FDE.error_code){ case user_pvt_t::ERROR_NO: case user_pvt_t::ERROR_VELOCITY_SKIPPED: break; default: continue; } typename user_pvt_t::exclusion_t *target(NULL); if(pvt_FDE.FD.wssr < pvt.FDE_min.wssr){ pvt.FDE_2nd = pvt.FDE_min; target = &pvt.FDE_min; }else if(pvt_FDE.FD.wssr < pvt.FDE_2nd.wssr){ target = &pvt.FDE_2nd; }else{ continue; } (typename user_pvt_t::detection_t &)(*target) = pvt_FDE.FD; target->excluded = fullset.back().prn; target->user_position = pvt_FDE.user_position; target->receiver_error = pvt_FDE.receiver_error; target->dop = pvt_FDE.dop; target->sigma_pos = pvt_FDE.sigma_pos; } } }; #endif /* __GPS_SOLVER_RAIM_H__ */