/*! * \file galileo_e1_ls_pvt.cc * \brief Implementation of a Least Squares Position, Velocity, and Time * (PVT) solver, based on K.Borre's Matlab receiver. * \author Javier Arribas, 2011. jarribas(at)cttc.es * * ------------------------------------------------------------------------- * * Copyright (C) 2010-2015 (see AUTHORS file for a list of contributors) * * GNSS-SDR is a software defined Global Navigation * Satellite Systems receiver * * This file is part of GNSS-SDR. * * GNSS-SDR is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * GNSS-SDR is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNSS-SDR. If not, see . * * ------------------------------------------------------------------------- */ #include "hybrid_ls_pvt.h" #include #include "Galileo_E1.h" using google::LogMessage; hybrid_ls_pvt::hybrid_ls_pvt(int nchannels, std::string dump_filename, bool flag_dump_to_file) : Ls_Pvt() { // init empty ephemeris for all the available GNSS channels d_nchannels = nchannels; d_Gal_ephemeris = new Galileo_Navigation_Message[nchannels]; d_GPS_ephemeris = new Gps_Navigation_Message[nchannels]; d_dump_filename = dump_filename; d_flag_dump_enabled = flag_dump_to_file; d_galileo_current_time = 0; d_valid_GPS_obs = 0; d_valid_GAL_obs = 0; count_valid_position = 0; // ############# ENABLE DATA FILE LOG ################# if (d_flag_dump_enabled == true) { if (d_dump_file.is_open() == false) { try { d_dump_file.exceptions (std::ifstream::failbit | std::ifstream::badbit); d_dump_file.open(d_dump_filename.c_str(), std::ios::out | std::ios::binary); LOG(INFO) << "PVT lib dump enabled Log file: " << d_dump_filename.c_str(); } catch (std::ifstream::failure e) { LOG(WARNING) << "Exception opening PVT lib dump file " << e.what(); } } } } hybrid_ls_pvt::~hybrid_ls_pvt() { d_dump_file.close(); delete[] d_Gal_ephemeris; delete[] d_GPS_ephemeris; } bool hybrid_ls_pvt::get_PVT(std::map gnss_pseudoranges_map, double hybrid_current_time, bool flag_averaging) { std::map::iterator gnss_pseudoranges_iter; std::map::iterator galileo_ephemeris_iter; std::map::iterator gps_ephemeris_iter; int valid_pseudoranges = gnss_pseudoranges_map.size(); arma::mat W = arma::eye(valid_pseudoranges, valid_pseudoranges); // channels weights matrix arma::vec obs = arma::zeros(valid_pseudoranges); // pseudoranges observation vector arma::mat satpos = arma::zeros(3, valid_pseudoranges); // satellite positions matrix int Galileo_week_number = 0; int GPS_week; double utc = 0; double utc_tx_corrected = 0; //utc computed at tx_time_corrected, added for Galileo constellation (in GPS utc is directly computed at TX_time_corrected_s) double TX_time_corrected_s; double SV_clock_bias_s = 0; d_flag_averaging = flag_averaging; // ******************************************************************************** // ****** PREPARE THE LEAST SQUARES DATA (SV POSITIONS MATRIX AND OBS VECTORS) **** // ******************************************************************************** int valid_obs = 0; //valid observations counter int obs_counter = 0; int valid_obs_GPS_counter = 0; int valid_obs_GALILEO_counter = 0; for(gnss_pseudoranges_iter = gnss_pseudoranges_map.begin(); gnss_pseudoranges_iter != gnss_pseudoranges_map.end(); gnss_pseudoranges_iter++) { if (gnss_pseudoranges_iter->second.System == 'E') { //std::cout << "Satellite System: " << gnss_pseudoranges_iter->second.System <first); if (galileo_ephemeris_iter != galileo_ephemeris_map.end()) { /*! * \todo Place here the satellite CN0 (power level, or weight factor) */ W(obs_counter, obs_counter) = 1; // COMMON RX TIME PVT ALGORITHM MODIFICATION (Like RINEX files) // first estimate of transmit time //Galileo_week_number = galileo_ephemeris_iter->second.WN_5;//for GST //double sec_in_day = 86400; //double day_in_week = 7; // t = WN*sec_in_day*day_in_week + TOW; // t is Galileo System Time to use to compute satellite positions double Rx_time = hybrid_current_time; double Tx_time = Rx_time - gnss_pseudoranges_iter->second.Pseudorange_m/GALILEO_C_m_s; // 2- compute the clock drift using the clock model (broadcast) for this SV SV_clock_bias_s = galileo_ephemeris_iter->second.sv_clock_drift(Tx_time); // 3- compute the current ECEF position for this SV using corrected TX time TX_time_corrected_s = Tx_time - SV_clock_bias_s; galileo_ephemeris_iter->second.satellitePosition(TX_time_corrected_s); satpos(0,obs_counter) = galileo_ephemeris_iter->second.d_satpos_X; satpos(1,obs_counter) = galileo_ephemeris_iter->second.d_satpos_Y; satpos(2,obs_counter) = galileo_ephemeris_iter->second.d_satpos_Z; // 5- fill the observations vector with the corrected pseudoranges obs(obs_counter) = gnss_pseudoranges_iter->second.Pseudorange_m + SV_clock_bias_s*GALILEO_C_m_s; d_visible_satellites_IDs[valid_obs] = galileo_ephemeris_iter->second.i_satellite_PRN; d_visible_satellites_CN0_dB[valid_obs] = gnss_pseudoranges_iter->second.CN0_dB_hz; valid_obs++; valid_obs_GALILEO_counter ++; Galileo_week_number = galileo_ephemeris_iter->second.WN_5; //for GST //debug double GST = galileo_ephemeris_iter->second.Galileo_System_Time(Galileo_week_number, hybrid_current_time); utc = galileo_utc_model.GST_to_UTC_time(GST, Galileo_week_number); // this shoud be removed and it should be considered the utc_tx_corrected utc_tx_corrected = galileo_utc_model.GST_to_UTC_time(TX_time_corrected_s, Galileo_week_number); //std::cout<<"Gal UTC at TX_time_corrected_s = "<first; } } else if (gnss_pseudoranges_iter->second.System == 'G') { //std::cout << "Satellite System: " << gnss_pseudoranges_iter->second.System <first); if (gps_ephemeris_iter != gps_ephemeris_map.end()) { /*! * \todo Place here the satellite CN0 (power level, or weight factor) */ W(obs_counter, obs_counter) = 1; // COMMON RX TIME PVT ALGORITHM MODIFICATION (Like RINEX files) // first estimate of transmit time double Rx_time = hybrid_current_time; double Tx_time = Rx_time - gnss_pseudoranges_iter->second.Pseudorange_m/GPS_C_m_s; // 2- compute the clock drift using the clock model (broadcast) for this SV SV_clock_bias_s = gps_ephemeris_iter->second.sv_clock_drift(Tx_time); // 3- compute the current ECEF position for this SV using corrected TX time TX_time_corrected_s = Tx_time - SV_clock_bias_s; gps_ephemeris_iter->second.satellitePosition(TX_time_corrected_s); satpos(0, obs_counter) = gps_ephemeris_iter->second.d_satpos_X; satpos(1, obs_counter) = gps_ephemeris_iter->second.d_satpos_Y; satpos(2, obs_counter) = gps_ephemeris_iter->second.d_satpos_Z; // 5- fill the observations vector with the corrected pseudorranges obs(obs_counter) = gnss_pseudoranges_iter->second.Pseudorange_m + SV_clock_bias_s*GPS_C_m_s; d_visible_satellites_IDs[valid_obs] = gps_ephemeris_iter->second.i_satellite_PRN; d_visible_satellites_CN0_dB[valid_obs] = gnss_pseudoranges_iter->second.CN0_dB_hz; valid_obs++; valid_obs_GPS_counter++; // SV ECEF DEBUG OUTPUT DLOG(INFO) << "(new)ECEF satellite SV ID=" << gps_ephemeris_iter->second.i_satellite_PRN << " X=" << gps_ephemeris_iter->second.d_satpos_X << " [m] Y=" << gps_ephemeris_iter->second.d_satpos_Y << " [m] Z=" << gps_ephemeris_iter->second.d_satpos_Z << " [m] PR_obs=" << obs(obs_counter) << " [m]"; // compute the UTC time for this SV (just to print the asociated UTC timestamp) GPS_week = gps_ephemeris_iter->second.i_GPS_week; utc = gps_utc_model.utc_time(TX_time_corrected_s, GPS_week); } else // the ephemeris are not available for this SV { // no valid pseudorange for the current SV W(obs_counter, obs_counter) = 0; // SV de-activated obs(obs_counter) = 1; // to avoid algorithm problems (divide by zero) DLOG(INFO) << "No ephemeris data for SV " << gnss_pseudoranges_iter->first; } } obs_counter++; } // ******************************************************************************** // ****** SOLVE LEAST SQUARES****************************************************** // ******************************************************************************** d_valid_observations = valid_obs; d_valid_GPS_obs = valid_obs_GPS_counter; d_valid_GAL_obs = valid_obs_GALILEO_counter; LOG(INFO) << "HYBRID PVT: valid observations=" << valid_obs; if (valid_obs >= 4) { arma::vec mypos; DLOG(INFO) << "satpos=" << satpos; DLOG(INFO) << "obs="<< obs; DLOG(INFO) << "W=" << W; mypos = leastSquarePos(satpos, obs, W); // Compute GST and Gregorian time double GST = galileo_ephemeris_map.find(gnss_pseudoranges_iter->first)->second.Galileo_System_Time(Galileo_week_number, hybrid_current_time); utc = galileo_utc_model.GST_to_UTC_time(GST, Galileo_week_number); // get time string Gregorian calendar boost::posix_time::time_duration t = boost::posix_time::seconds(utc); // 22 August 1999 00:00 last Galileo start GST epoch (ICD sec 5.1.2) boost::posix_time::ptime p_time(boost::gregorian::date(1999, 8, 22), t); d_position_UTC_time = p_time; DLOG(INFO) << "HYBRID Position at TOW=" << hybrid_current_time << " in ECEF (X,Y,Z) = " << mypos; cart2geo(static_cast(mypos(0)), static_cast(mypos(1)), static_cast(mypos(2)), 4); //ToDo: Find an Observables/PVT random bug with some satellite configurations that gives an erratic PVT solution (i.e. height>50 km) if (d_height_m > 50000) { b_valid_position = false; LOG(INFO) << "Hybrid Position at " << boost::posix_time::to_simple_string(p_time) << " is Lat = " << d_latitude_d << " [deg], Long = " << d_longitude_d << " [deg], Height= " << d_height_m << " [m]"; //std::cout << "Hybrid Position at " << boost::posix_time::to_simple_string(p_time) // << " is Lat = " << d_latitude_d << " [deg], Long = " << d_longitude_d // << " [deg], Height= " << d_height_m << " [m]" << std::endl; return false; } LOG(INFO) << "Hybrid Position at " << boost::posix_time::to_simple_string(p_time) << " is Lat = " << d_latitude_d << " [deg], Long = " << d_longitude_d << " [deg], Height= " << d_height_m << " [m]"; // ###### Compute DOPs ######## compute_DOP(); // ######## LOG FILE ######### if(d_flag_dump_enabled == true) { // MULTIPLEXED FILE RECORDING - Record results to file try { double tmp_double; // PVT GPS time tmp_double = hybrid_current_time; d_dump_file.write((char*)&tmp_double, sizeof(double)); // ECEF User Position East [m] tmp_double = mypos(0); d_dump_file.write((char*)&tmp_double, sizeof(double)); // ECEF User Position North [m] tmp_double = mypos(1); d_dump_file.write((char*)&tmp_double, sizeof(double)); // ECEF User Position Up [m] tmp_double = mypos(2); d_dump_file.write((char*)&tmp_double, sizeof(double)); // User clock offset [s] tmp_double = mypos(3); d_dump_file.write((char*)&tmp_double, sizeof(double)); // GEO user position Latitude [deg] tmp_double = d_latitude_d; d_dump_file.write((char*)&tmp_double, sizeof(double)); // GEO user position Longitude [deg] tmp_double = d_longitude_d; d_dump_file.write((char*)&tmp_double, sizeof(double)); // GEO user position Height [m] tmp_double = d_height_m; d_dump_file.write((char*)&tmp_double, sizeof(double)); } catch (const std::ifstream::failure& e) { LOG(WARNING) << "Exception writing PVT LS dump file "<< e.what(); } } // MOVING AVERAGE PVT if (flag_averaging == true) { if (d_hist_longitude_d.size() == (unsigned int)d_averaging_depth) { // Pop oldest value d_hist_longitude_d.pop_back(); d_hist_latitude_d.pop_back(); d_hist_height_m.pop_back(); // Push new values d_hist_longitude_d.push_front(d_longitude_d); d_hist_latitude_d.push_front(d_latitude_d); d_hist_height_m.push_front(d_height_m); d_avg_latitude_d = 0; d_avg_longitude_d = 0; d_avg_height_m = 0; for (unsigned int i = 0; i < d_hist_longitude_d.size(); i++) { d_avg_latitude_d = d_avg_latitude_d + d_hist_latitude_d.at(i); d_avg_longitude_d = d_avg_longitude_d + d_hist_longitude_d.at(i); d_avg_height_m = d_avg_height_m + d_hist_height_m.at(i); } d_avg_latitude_d = d_avg_latitude_d / static_cast(d_averaging_depth); d_avg_longitude_d = d_avg_longitude_d / static_cast(d_averaging_depth); d_avg_height_m = d_avg_height_m / static_cast(d_averaging_depth); b_valid_position = true; return true; //indicates that the returned position is valid } else { // int current_depth=d_hist_longitude_d.size(); // Push new values d_hist_longitude_d.push_front(d_longitude_d); d_hist_latitude_d.push_front(d_latitude_d); d_hist_height_m.push_front(d_height_m); d_avg_latitude_d = d_latitude_d; d_avg_longitude_d = d_longitude_d; d_avg_height_m = d_height_m; b_valid_position = false; return false; //indicates that the returned position is not valid yet } } else { b_valid_position = true; return true; //indicates that the returned position is valid } } else { b_valid_position = false; return false; } return false; }