/** * @file GPS solver * - Mainly, single positioning * */ /* * Copyright (c) 2016, 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_H__ #define __GPS_SOLVER_H__ #include <utility> #include <vector> #include <exception> #include <cmath> #include <cstddef> #include "param/bit_array.h" #include "GPS.h" #include "GPS_Solver_Base.h" #include "NTCM.h" template <class FloatT> struct GPS_Solver_GeneralOptions { FloatT elevation_mask; FloatT residual_mask; GPS_Solver_GeneralOptions() : elevation_mask(0), // elevation mask default is 0 [deg] residual_mask(30) { // range residual mask is 30 [m] } }; template <class FloatT> struct GPS_SinglePositioning_Options : public GPS_Solver_GeneralOptions<FloatT> { // PRN ranges from 1 to 256 (including GPS compatible systems such as QZSS) typename GPS_Solver_Base<FloatT>::options_t::template exclude_prn_t<1, 256> exclude_prn; GPS_SinglePositioning_Options() : GPS_Solver_GeneralOptions<FloatT>(), exclude_prn() { } }; template <class FloatT, class SolverBaseT = GPS_Solver_Base<FloatT> > class GPS_SinglePositioning : public SolverBaseT { private: GPS_SinglePositioning<FloatT> &operator=(const GPS_SinglePositioning<FloatT> &); public: typedef GPS_SinglePositioning<FloatT> 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(matrix_t); inheritate_type(prn_t); typedef typename base_t::space_node_t space_node_t; inheritate_type(gps_time_t); typedef typename space_node_t::Satellite satellite_t; inheritate_type(xyz_t); inheritate_type(llh_t); inheritate_type(enu_t); inheritate_type(pos_t); inheritate_type(prn_obs_t); typedef typename base_t::measurement_t measurement_t; inheritate_type(measurement_items_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); inheritate_type(geometric_matrices_t); inheritate_type(user_pvt_t); #undef inheritate_type typedef typename GPS_Solver_Base<float_t>::options_t::template merge_t< GPS_SinglePositioning_Options<float_t>, base_t> options_t; protected: const space_node_t &_space_node; GPS_SinglePositioning_Options<float_t> _options; public: const space_node_t &space_node() const {return _space_node;} struct klobuchar_t : public range_corrector_t { const space_node_t &space_node; klobuchar_t(const space_node_t &sn) : range_corrector_t(), space_node(sn) {} bool is_available(const gps_time_t &t) const { return space_node.is_valid_iono(); } 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.iono_correction(sat_rel_pos, usr_pos.llh, t)); } } ionospheric_klobuchar; struct ntcm_gl_t : public range_corrector_t { float_t f_10_7; ntcm_gl_t() : range_corrector_t(), f_10_7(-1) {} bool is_available(const gps_time_t &t) const { return f_10_7 >= 0; } float_t *calculate( const gps_time_t &t, const pos_t &usr_pos, const enu_t &sat_rel_pos, float_t &buf) const { if(!is_available(t)){return NULL;} typename space_node_t::pierce_point_res_t pp( space_node_t::pierce_point(sat_rel_pos, usr_pos.llh)); return &(buf = -space_node_t::tec2delay(space_node_t::slant_factor(sat_rel_pos) * NTCM_GL_Generic<float_t>::tec_vert( pp.latitude, pp.longitude, t.year(), f_10_7))); } } ionospheric_ntcm_gl; struct tropospheric_simplified_t : public range_corrector_t { tropospheric_simplified_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(sat_rel_pos, usr_pos.llh)); } } tropospheric_simplified; 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 { GPS_SinglePositioning_Options<float_t> 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); } GPS_SinglePositioning(const space_node_t &sn) : base_t(), _space_node(sn), _options(available_options(options_t())), ionospheric_klobuchar(sn), ionospheric_ntcm_gl(), tropospheric_simplified(), ionospheric_correction(), tropospheric_correction() { // default ionospheric correction: // Broadcasted Klobuchar parameters are at least required for solution. ionospheric_correction.push_front(&ionospheric_klobuchar); // default troposheric correction: simplified tropospheric_correction.push_front(&tropospheric_simplified); } ~GPS_SinglePositioning(){} struct residual_t { float_t &residual; float_t &los_neg_x; float_t &los_neg_y; float_t &los_neg_z; float_t &weight; }; /** * Get corrected range in accordance with current status * * @param sat satellite * @param range "corrected" pseudo range subtracted by (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 residual calculated residual with line of site vector, and weight; * When weight is equal to or less than zero, the calculated results should not be used. * @param error Some correction can be overwritten. If its unknown_flag is zero, * corrections will be skipped as possible. @see range_errors_t * @return (float_t) corrected range just including delay, and excluding receiver/satellite error. */ float_t range_corrected( const satellite_t &sat, float_t range, const gps_time_t &time_arrival, const pos_t &usr_pos, residual_t &residual, const range_error_t &error = range_error_t::not_corrected) const { // Clock error correction range += ((error.unknown_flag & range_error_t::SATELLITE_CLOCK) ? (sat.clock_error(time_arrival, range) * space_node_t::light_speed) : error.value[range_error_t::SATELLITE_CLOCK]); // Calculate satellite position xyz_t sat_pos(sat.position(time_arrival, range)); float_t geometric_range(usr_pos.xyz.dist(sat_pos)); // Calculate residual residual.residual = range - geometric_range; // Setup design matrix residual.los_neg_x = -(sat_pos.x() - usr_pos.xyz.x()) / geometric_range; residual.los_neg_y = -(sat_pos.y() - usr_pos.xyz.y()) / geometric_range; residual.los_neg_z = -(sat_pos.z() - usr_pos.xyz.z()) / geometric_range; enu_t relative_pos(enu_t::relative(sat_pos, usr_pos.xyz)); if(error.unknown_flag & range_error_t::MASK_IONOSPHERIC){ residual.residual += ionospheric_correction(time_arrival, usr_pos, relative_pos); }else{ residual.residual += error.value[range_error_t::IONOSPHERIC]; } // Tropospheric residual.residual += (error.unknown_flag & range_error_t::MASK_TROPOSPHERIC) ? tropospheric_correction(time_arrival, usr_pos, relative_pos) : error.value[range_error_t::TROPOSPHERIC]; // Setup weight if(std::abs(residual.residual) > _options.residual_mask){ // If residual is too big, gently exclude it by decreasing its weight. residual.weight = 1E-8; }else{ float_t elv(relative_pos.elevation()); if(elv < _options.elevation_mask){ residual.weight = 0; // exclude it when elevation is less than threshold }else{ /* elevation weight based on "GPS���p�v���O���~���O" * elevation[deg] : 90 53 45 30 15 10 5 * sigma(s) : 0.80 1.00 1.13 1.60 3.09 4.61 9.18 * weight(s^-2) : 1.56 1.00 0.78 0.39 0.10 0.05 0.01 */ residual.weight = std::pow(sin(elv)/0.8, 2); // weight=1 @ elv=53[deg] if(residual.weight < 1E-3){residual.weight = 1E-3;} } } return range; } /** * Get relative rate (negative polarity) in accordance with current status * * @param sat satellite * @param range "corrected" pseudo range subtracted by (temporal solution of) receiver clock error in meter * @param time_arrival time when signal arrive at receiver * @param usr_vel (temporal solution of) user velocity * @param los_neg_x line of site X * @param los_neg_y line of site Y * @param los_neg_z line of site Z * @return (float_t) relative rate. */ float_t rate_relative_neg( const satellite_t &sat, const float_t &range, const gps_time_t &time_arrival, const xyz_t &usr_vel, const float_t &los_neg_x, const float_t &los_neg_y, const float_t &los_neg_z) const { xyz_t rel_vel(sat.velocity(time_arrival, range) - usr_vel); // Calculate velocity return los_neg_x * rel_vel.x() + los_neg_y * rel_vel.y() + los_neg_z * rel_vel.z() + sat.clock_error_dot(time_arrival, range) * space_node_t::light_speed; // considering clock rate error } /** * Check availability of a satellite with which observation is associated * * @param prn satellite number * @param receiver_time receiver time * @return (const satellite_t *) If available, const pointer to satellite is returned, * otherwise NULL. */ const satellite_t *is_available( const typename space_node_t::satellites_t::key_type &prn, const gps_time_t &receiver_time) const { if(_options.exclude_prn[prn]){return NULL;} const typename space_node_t::satellites_t &sats(_space_node.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 NULL; } return &(it_sat->second); } /** * 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 } const satellite_t *sat(is_available(prn, time_arrival)); if(!sat){return res;} // If satellite is unavailable, return with weight = 0 residual_t residual = { res.range_residual, res.los_neg[0], res.los_neg[1], res.los_neg[2], res.weight, }; res.range_corrected = range_corrected( *sat, range - receiver_error, time_arrival, usr_pos, residual, range_error); res.rate_relative_neg = rate_relative_neg(*sat, res.range_corrected, time_arrival, usr_vel, res.los_neg[0], res.los_neg[1], res.los_neg[2]); return res; } /** * Calculate User position/velocity with hint * This is optimized version for GPS-only constellation * * @param res (out) calculation results and matrices used for calculation * @param measurement PRN, pseudo-range, pseudo-range rate information * @param receiver_time receiver time at measurement * @param user_position_init initial solution of user position in XYZ meters and LLH * @param receiver_error_init initial solution of receiver clock error in meters * @param good_init if true, initial position and clock error are goodly guessed. * @param with_velocity if true, perform velocity estimation. * @see update_ephemeris(), register_ephemeris */ void user_pvt( user_pvt_t &res, const measurement_t &measurement, const gps_time_t &receiver_time, const pos_t &user_position_init, const float_t &receiver_error_init, const bool &good_init = true, const bool &with_velocity = true) const { res.receiver_time = receiver_time; if(!ionospheric_correction.select(receiver_time)){ res.error_code = user_pvt_t::ERROR_INVALID_IONO_MODEL; return; } typename base_t::measurement2_t measurement2; measurement2.reserve(measurement.size()); for(typename measurement_t::const_iterator it(measurement.begin()), it_end(measurement.end()); it != it_end; ++it){ float_t range; if(!this->range(it->second, range)){continue;} // No range entry if(!is_available(it->first, receiver_time)){continue;} // No satellite typename base_t::measurement2_t::value_type v = { it->first, &(it->second), this}; // prn, measurement, solver measurement2.push_back(v); } base_t::user_pvt( res, measurement2, receiver_time, user_position_init, receiver_error_init, typename base_t::user_pvt_opt_t(good_init, with_velocity)); } xyz_t *satellite_position( const prn_t &prn, const gps_time_t &time, xyz_t &res) const { const satellite_t *sat(is_available(prn, time)); return sat ? &(res = sat->position(time)) : NULL; } }; #endif /* __GPS_SOLVER_H__ */