1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
|
/***********************************************/
/**
* @file gnssIonex2GriddedDataTimeSeries.cpp
*
* @brief Converts TEC maps from IGS IONEX file format to GROOPS GriddedDataTimeSeries format.
**
* @author Sebastian Strasser
* @date 2021-09-13
*/
/***********************************************/
// Latex documentation
#define DOCSTRING docstring
static const char *docstring = R"(
Converts TEC maps from IGS \href{https://files.igs.org/pub/data/format/ionex1.pdf}{IONEX file format}
to GROOPS \file{gridded data time series}{griddedDataTimeSeries} format.
Currently only supports 2D TEC maps.
See also \program{GnssGriddedDataTimeSeries2Ionex}, \configClass{IonosphereMap}{gnssParametrizationType:ionosphereMap}.
)";
/***********************************************/
#include "programs/program.h"
#include "base/string.h"
#include "files/fileGriddedDataTimeSeries.h"
/***** CLASS ***********************************/
/** @brief Converts TEC maps from IGS IONEX file format to GROOPS GriddedDataTimeSeries format.
* @ingroup programsGroup */
class GnssIonex2GriddedDataTimeSeries
{
Bool getLine(InFile &file, std::string &line, std::string &label) const;
Bool testLabel(const std::string &labelInLine, const std::string &label, Bool optional=TRUE) const;
public:
void run(Config &config, Parallel::CommunicatorPtr comm);
};
GROOPS_REGISTER_PROGRAM(GnssIonex2GriddedDataTimeSeries, SINGLEPROCESS, "Converts TEC maps from IGS IONEX file format to GROOPS GriddedDataTimeSeries format.", Conversion, Gnss, Grid, TimeSplines)
/***********************************************/
void GnssIonex2GriddedDataTimeSeries::run(Config &config, Parallel::CommunicatorPtr /*comm*/)
{
try
{
FileName fileNameOut;
std::vector<FileName> fileNamesIn;
readConfig(config, "outputfileGriddedDataTimeSeries", fileNameOut, Config::MUSTSET, "", "");
readConfig(config, "inputfileIonex", fileNamesIn, Config::MUSTSET, "", "");
if(isCreateSchema(config)) return;
GriddedData grid;
std::vector<Matrix> data;
std::vector<Time> times;
for(const auto &fileName : fileNamesIn)
{
logStatus<<"read IONEX file <"<<fileName<<">"<<Log::endl;
InFile file(fileName);
file.exceptions(std::ios::badbit|std::ios::failbit);
Double factor = 0.1;
std::vector<Angle> latitudes, longitudes;
Double height = 0;
Double radius = DEFAULT_GRS80_a;
// read header
std::string line, label;
getLine(file, line, label);
testLabel(label, "IONEX VERSION / TYPE", FALSE);
while(getLine(file, line, label))
{
if(testLabel(label, "END OF HEADER"))
break;
else if(testLabel(label, "MAP DIMENSION"))
{
if(String::toInt(line.substr(0,6)) > 2)
throw(Exception("only 2-dimensional maps are supported yet"));
}
else if(testLabel(label, "LAT1 / LAT2 / DLAT"))
{
Double lat1 = String::toDouble(line.substr(2,6));
Double lat2 = String::toDouble(line.substr(8,6));
Double dlat = String::toDouble(line.substr(14,6));
for(Double lat=lat1; (dlat > 0 ? lat<=lat2 : lat>=lat2); lat+=dlat)
latitudes.push_back(Angle(lat*DEG2RAD));
}
else if(testLabel(label, "LON1 / LON2 / DLON"))
{
Double lon1 = String::toDouble(line.substr(2,6));
Double lon2 = String::toDouble(line.substr(8,6));
Double dlon = String::toDouble(line.substr(14,6));
for(Double lon=lon1; (dlon > 0 ? lon<=lon2 : lon>=lon2); lon+=dlon)
longitudes.push_back(Angle(lon*DEG2RAD));
}
else if(testLabel(label, "HGT1 / HGT2 / DHGT"))
{
height = 1e3*String::toDouble(line.substr(2,6));
}
else if(testLabel(label, "BASE RADIUS"))
{
radius = 1e3*String::toDouble(line.substr(2,6));
}
else if(testLabel(label, "EXPONENT"))
{
factor = std::pow(10., String::toInt(line.substr(0,6)));
}
}
if(!grid.points.size())
{
GriddedDataRectangular gridRectangular;
gridRectangular.longitudes = longitudes;
gridRectangular.latitudes = latitudes;
gridRectangular.heights.resize(latitudes.size(), height);
gridRectangular.ellipsoid = Ellipsoid(radius, 0.); // sphere
grid.init(gridRectangular);
}
// read data
while(getLine(file, line, label))
if(testLabel(label, "START OF TEC MAP"))
{
getLine(file, line, label);
testLabel(label, "EPOCH OF CURRENT MAP", FALSE);
Time time = date2time(String::toInt(line.substr(0,6)), String::toInt(line.substr(6,6)), String::toInt(line.substr(12,6)),
String::toInt(line.substr(18,6)), String::toInt(line.substr(24,6)), String::toInt(line.substr(30,6)));
if(times.size() && time < times.back())
{
logWarning<<"skipping epoch "<<time.dateTimeStr()<<" (unsorted)"<<Log::endl;
continue;
}
// replace possible duplicate epoch by newer one (e.g. 00:00 from file 2 instead of 24:00 from file 1)
if(times.size() && time == times.back())
data.pop_back();
else
times.push_back(time);
Vector epochData(latitudes.size()*longitudes.size());
while(getLine(file, line, label))
{
if(testLabel(label, "END OF TEC MAP"))
break;
testLabel(label, "LAT/LON1/LON2/DLON/H", FALSE);
const Angle lat (String::toDouble(line.substr( 2,6))*DEG2RAD);
const Angle lon1(String::toDouble(line.substr( 8,6))*DEG2RAD);
const Angle lon2(String::toDouble(line.substr(14,6))*DEG2RAD);
const Angle dlon(String::toDouble(line.substr(20,6))*DEG2RAD);
const UInt count = static_cast<UInt>(std::round((lon2-lon1)/dlon))+1;
const UInt idxLat = std::distance(latitudes.begin(), std::find(latitudes.begin(), latitudes.end(), lat));
const UInt idxLon = std::distance(longitudes.begin(), std::find(longitudes.begin(), longitudes.end(), lon1));
for(UInt i=0; i<(count+15)/16; i++)
{
getLine(file, line, label);
for(UInt k=0; k<std::min(count-i*16, UInt(16)); k++)
epochData.at(idxLat*longitudes.size() + (idxLon+i*16+k)%longitudes.size()) = factor * String::toDouble(line.substr(k*5,5));
}
}
data.push_back(epochData);
}
}
logStatus<<"write gridded data time series to <"<<fileNameOut<<">"<<Log::endl;
writeFileGriddedDataTimeSeries(fileNameOut, 1, times, grid, data);
}
catch(std::exception &e)
{
GROOPS_RETHROW(e)
}
}
/***********************************************/
Bool GnssIonex2GriddedDataTimeSeries::getLine(InFile &file, std::string &line, std::string &label) const
{
try
{
std::getline(file, line);
if(line.size() && line.back() == '\r')
line.pop_back();
if(line.size()<80)
line.resize(80,' ');
label = line.substr(60,20);
return TRUE;
}
catch(...)
{
line.clear();
line.resize(80,' ');
label = line.substr(60,20);
return FALSE;
}
}
/***********************************************/
Bool GnssIonex2GriddedDataTimeSeries::testLabel(const std::string &labelInLine, const std::string &label, Bool optional) const
{
if(labelInLine.find(label)!=std::string::npos)
return TRUE;
if(optional)
return FALSE;
throw(Exception(std::string("In line '")+labelInLine+"' label '"+label+"' expected\n"));
}
/***********************************************/
|