/** * @file SBAS solver * */ /* * Copyright (c) 2020, 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 __SBAS_SOLVER_H__ #define __SBAS_SOLVER_H__ #include "SBAS.h" #include "GPS_Solver_Base.h" #include "GPS_Solver.h" #include template struct SBAS_SinglePositioning_Options : public GPS_Solver_GeneralOptions { typedef GPS_Solver_GeneralOptions super_t; typename GPS_Solver_Base::options_t::template exclude_prn_t<120, 158> exclude_prn; // SBAS PRN ranges from 120 to 158 SBAS_SinglePositioning_Options() : super_t(), exclude_prn() { exclude_prn.set(true); // SBAS ranging is default off. } }; template > class SBAS_SinglePositioning : public SolverBaseT { private: SBAS_SinglePositioning &operator=(const SBAS_SinglePositioning &); public: typedef SBAS_SinglePositioning self_t; typedef SolverBaseT base_t; #if defined(__GNUC__) && (__GNUC__ < 5) #define inheritate_type(x) typedef typename base_t::x x; #else #define inheritate_type(x) using typename base_t::x; #endif inheritate_type(float_t); inheritate_type(prn_t); typedef SBAS_SpaceNode space_node_t; inheritate_type(gps_time_t); inheritate_type(xyz_t); inheritate_type(enu_t); inheritate_type(pos_t); typedef typename base_t::measurement_t measurement_t; typedef typename base_t::satellite_t satellite_t; typedef typename base_t::range_error_t range_error_t; typedef typename base_t::range_corrector_t range_corrector_t; typedef typename base_t::range_correction_t range_correction_t; inheritate_type(relative_property_t); #undef inheritate_type typedef typename GPS_Solver_Base::options_t::template merge_t< SBAS_SinglePositioning_Options, base_t> options_t; protected: SBAS_SinglePositioning_Options _options; public: struct satellites_t { const void *impl; satellite_t (*impl_select)(const void *, const prn_t &, const gps_time_t &); inline satellite_t select(const prn_t &prn, const gps_time_t &receiver_time) const { return impl_select(impl, prn, receiver_time); } static satellite_t select_broadcast( const void *ptr, const prn_t &prn, const gps_time_t &receiver_time){ const typename space_node_t::satellites_t &sats( reinterpret_cast(ptr)->satellites()); const typename space_node_t::satellites_t::const_iterator it_sat(sats.find(prn)); if((it_sat == sats.end()) // has ephemeris? || (!it_sat->second.ephemeris().is_valid(receiver_time))){ // valid ephemeris? return satellite_t::unavailable(); } struct impl_t { static inline const typename space_node_t::Satellite &sat(const void *ptr) { return *reinterpret_cast(ptr); } static xyz_t position(const void *ptr, const gps_time_t &t, const float_t &pseudo_range) { return sat(ptr).ephemeris().constellation(t, pseudo_range, false).position; } static xyz_t velocity(const void *ptr, const gps_time_t &t, const float_t &pseudo_range) { return sat(ptr).ephemeris().constellation(t, pseudo_range, true).velocity; } static float_t clock_error(const void *ptr, const gps_time_t &t, const float_t &pseudo_range) { // Clock correction is taken into account in position() return 0; } static float_t clock_error_dot(const void *ptr, const gps_time_t &t, const float_t &pseudo_range) { // Clock rate error is taken in account in velocity() return 0; } }; satellite_t res = { &(it_sat->second), impl_t::position, impl_t::velocity, impl_t::clock_error, impl_t::clock_error_dot}; return res; } satellites_t(const space_node_t &sn) : impl(&sn), impl_select(select_broadcast) {} } satellites; struct ionospheric_sbas_t : public range_corrector_t { const space_node_t &space_node; ionospheric_sbas_t(const space_node_t &sn) : range_corrector_t(), space_node(sn) {} bool is_available(const gps_time_t &t) const { return true; } float_t *calculate( const gps_time_t &t, const pos_t &usr_pos, const enu_t &sat_rel_pos, float_t &buf) const { // placeholder of checking availability and performing correction typedef typename space_node_t::available_satellites_t sats_t; sats_t sats(space_node.available_satellites(usr_pos.llh.longitude())); typename space_node_t::IonosphericGridPoints::PointProperty prop; for(typename sats_t::const_iterator it(sats.begin()); it != sats.end(); ++it){ prop = it->second ->ionospheric_grid_points().iono_correction(sat_rel_pos, usr_pos.llh); if(prop.is_available()){ return &(buf = prop.delay); } break; // TODO The nearest satellite is only checked } return NULL; } } ionospheric_sbas; struct tropospheric_sbas_t : public range_corrector_t { tropospheric_sbas_t() : range_corrector_t() {} bool is_available(const gps_time_t &t) const { return true; } float_t *calculate( const gps_time_t &t, const pos_t &usr_pos, const enu_t &sat_rel_pos, float_t &buf) const { return &(buf = space_node_t::tropo_correction( t.year(), sat_rel_pos, usr_pos.llh)); } } tropospheric_sbas; range_correction_t ionospheric_correction, tropospheric_correction; options_t available_options() const { return options_t(base_t::available_options(), _options); } options_t available_options(const options_t &opt_wish) const { SBAS_SinglePositioning_Options opt(opt_wish); return options_t(base_t::available_options(opt_wish), opt); } options_t update_options(const options_t &opt_wish){ _options = opt_wish; return options_t(base_t::update_options(opt_wish), _options); } SBAS_SinglePositioning(const space_node_t &sn) : base_t(), _options(available_options(options_t())), satellites(sn), ionospheric_sbas(sn), tropospheric_sbas() { // default ionospheric correction: Broadcasted IGP. ionospheric_correction.push_front(&ionospheric_sbas); // default troposheric correction: SBAS tropospheric_correction.push_front(&tropospheric_sbas); } ~SBAS_SinglePositioning(){} /** * Select satellite by using PRN and time * * @param prn satellite number * @param receiver_time receiver time * @return (satellite_t) If available, satellite.is_available() returning true is returned. */ satellite_t select_satellite( const prn_t &prn, const gps_time_t &receiver_time) const { if(_options.exclude_prn[prn]){return satellite_t::unavailable();} return satellites.select(prn, receiver_time); } /** * Calculate relative range and rate information to a satellite * * @param prn satellite number * @param measurement measurement (per satellite) containing pseudo range * @param receiver_error (temporal solution of) receiver clock error in meter * @param time_arrival time when signal arrive at receiver * @param usr_pos (temporal solution of) user position * @param usr_vel (temporal solution of) user velocity * @return (relative_property_t) relative information */ relative_property_t relative_property( const prn_t &prn, const typename measurement_t::mapped_type &measurement, const float_t &receiver_error, const gps_time_t &time_arrival, const pos_t &usr_pos, const xyz_t &usr_vel) const { relative_property_t res = {0}; float_t range; range_error_t range_error; if(!this->range(measurement, range, &range_error)){ return res; // If no range entry, return with weight = 0 } satellite_t sat(select_satellite(prn, time_arrival)); if(!sat.is_available()){return res;} // If satellite is unavailable, return with weight = 0 ///< The following procedure is based on Appendix.S with modification range -= receiver_error; // Clock error correction range += ((range_error.unknown_flag & range_error_t::SATELLITE_CLOCK) ? (sat.clock_error(time_arrival, range) * GPS_SpaceNode::light_speed) : range_error.value[range_error_t::SATELLITE_CLOCK]); // TODO WAAS long term clock correction (2.1.1.4.11) // Calculate satellite position xyz_t sat_pos(sat.position(time_arrival, range)); float_t geometric_range(usr_pos.xyz.dist(sat_pos)); // Calculate residual with Sagnac correction (A.4.4.11) res.range_residual = range + space_node_t::sagnac_correction(sat_pos, usr_pos.xyz) - geometric_range; // Setup design matrix res.los_neg[0] = -(sat_pos.x() - usr_pos.xyz.x()) / geometric_range; res.los_neg[1] = -(sat_pos.y() - usr_pos.xyz.y()) / geometric_range; res.los_neg[2] = -(sat_pos.z() - usr_pos.xyz.z()) / geometric_range; enu_t relative_pos(enu_t::relative(sat_pos, usr_pos.xyz)); // Tropospheric (2.1.4.10.3, A.4.2.4) res.range_residual += (range_error.unknown_flag & range_error_t::MASK_TROPOSPHERIC) ? tropospheric_correction(time_arrival, usr_pos, relative_pos) : range_error.value[range_error_t::TROPOSPHERIC]; // Ionospheric (2.1.4.10.2, A.4.4.10.4) if(range_error.unknown_flag & range_error_t::MASK_IONOSPHERIC){ res.range_residual += ionospheric_correction(time_arrival, usr_pos, relative_pos); }else{ res.range_residual += range_error.value[range_error_t::IONOSPHERIC]; } // TODO Fast corrections (2.1.1.4.12) // TODO Setup weight if(std::abs(res.range_residual) > _options.residual_mask){ // If residual is too big, gently exclude it by decreasing its weight. res.weight = 1E-8; }else{ float_t elv(relative_pos.elevation()); if(elv < _options.elevation_mask){ res.weight = 0; // exclude it when elevation is less than threshold }else{ // elevation weight based on "GPS実用プログラミング" @see GPS_Solver.h res.weight = std::pow(sin(elv)/0.8, 2); if(res.weight < 1E-3){res.weight = 1E-3;} } } res.range_corrected = range; xyz_t rel_vel(sat.velocity(time_arrival, range) - usr_vel); // Calculate velocity res.rate_relative_neg = res.los_neg[0] * rel_vel.x() + res.los_neg[1] * rel_vel.y() + res.los_neg[2] * rel_vel.z() + sat.clock_error_dot(time_arrival, range) * GPS_SpaceNode::light_speed; return res; } }; #endif /* __SBAS_SOLVER_H__ */