Newer
Older
/*===================================================================
QGroundControl Open Source Ground Control Station
(c) 2009, 2010 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
This file is part of the QGROUNDCONTROL project
QGROUNDCONTROL 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.
QGROUNDCONTROL 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 QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
======================================================================*/
/**
* @file
* @brief Implementation of class LogCompressor. This class reads in a file containing messages and translates it into a tab-delimited CSV file.
* @author Lorenz Meier <mavteam@student.ethz.ch>
*
*/
#include <QFile>
#include <QTemporaryFile>
#include <QTextStream>
#include <QStringList>
#include <QFileInfo>
#include <QList>
#include "LogCompressor.h"
#include <QDebug>
/**
* Initializes all the variables necessary for a compression run. This won't actually happen
* until startCompression(...) is called.
*/
LogCompressor::LogCompressor(QString logFileName, QString outFileName, QString delimiter) :
logFileName(logFileName),
outFileName(outFileName),
running(true),
currentDataLine(0),
delimiter(delimiter),
holeFillingEnabled(true)
{
}
void LogCompressor::run()
{
// Verify that the input file is useable
QFile infile(logFileName);
if (!infile.exists() || !infile.open(QIODevice::ReadOnly | QIODevice::Text)) {
emit logProcessingStatusChanged(tr("Log Compressor: Cannot start/compress log file, since input file %1 is not readable").arg(QFileInfo(infile.fileName()).absoluteFilePath()));
return;
}
// outFileName = logFileName;
QString outFileName;
QStringList parts = QFileInfo(infile.fileName()).absoluteFilePath().split(".", QString::SkipEmptyParts);
parts.replace(0, parts.first() + "_compressed");
parts.replace(parts.size()-1, "txt");
// Verify that the output file is useable
if (!outTmpFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
emit logProcessingStatusChanged(tr("Log Compressor: Cannot start/compress log file, since output file %1 is not writable").arg(QFileInfo(outTmpFile.fileName()).absoluteFilePath()));
return;
}
// First we search the input file through keySearchLimit number of lines
// looking for variables. This is neccessary before CSV files require
// the same number of fields for every line.
const unsigned int keySearchLimit = 15000;
unsigned int keyCounter = 0;
QTextStream in(&infile);
QMap<QString, int> messageMap;
Lorenz Meier
committed
while (!in.atEnd() && keyCounter < keySearchLimit) {
QString messageName = in.readLine().split(delimiter).at(2);
messageMap.insert(messageName, 0);
++keyCounter;
}
// Now update each key with its index in the output string. These are
// all offset by one to account for the first field: timestamp_ms.
int j;
for (i = messageMap.begin(), j = 1; i != messageMap.end(); ++i, ++j) {
i.value() = j;
}
// Open the output file and write the header line to it
QStringList headerList(messageMap.keys());
Lorenz Meier
committed
QString headerLine = "timestamp_ms" + delimiter + headerList.join(delimiter) + "\n";
Lorenz Meier
committed
// Clean header names from symbols Matlab considers as Latex syntax
headerLine = headerLine.replace("timestamp", "TIMESTAMP");
headerLine = headerLine.replace(":", "");
headerLine = headerLine.replace("_", "");
headerLine = headerLine.replace(".", "");
outTmpFile.write(headerLine.toLocal8Bit());
Lorenz Meier
committed
emit logProcessingStatusChanged(tr("Log compressor: Dataset contains dimensions: ") + headerLine);
// Template list stores a list for populating with data as it's parsed from messages.
QStringList templateList;
for (int i = 0; i < headerList.size() + 1; ++i) {
templateList << (holeFillingEnabled?"NaN":"");
}
// // Reset our position in the input file before we start the main processing loop.
// in.seek(0);
Lorenz Meier
committed
// // Search through all lines and build a list of unique timestamps
// QMap<quint64, QStringList> timestampMap;
// while (!in.atEnd()) {
// quint64 timestamp = in.readLine().split(delimiter).at(0).toULongLong();
// timestampMap.insert(timestamp, templateList);
// }
Lorenz Meier
committed
Lorenz Meier
committed
in.seek(0);
// Map of final output lines, key is time
QMap<quint64, QStringList> timestampMap;
// Run through the whole file and fill map of timestamps
Lorenz Meier
committed
while (!in.atEnd()) {
QStringList newLine = in.readLine().split(delimiter);
quint64 timestamp = newLine.at(0).toULongLong();
// Check if timestamp does exist - if not, add it
if (!timestampMap.contains(timestamp)) {
timestampMap.insert(timestamp, templateList);
}
Lorenz Meier
committed
QStringList list = timestampMap.value(timestamp);
QString currentDataName = newLine.at(2);
QString currentDataValue = newLine.at(3);
list.replace(messageMap.value(currentDataName), currentDataValue);
timestampMap.insert(timestamp, list);
}
int lineCounter = 0;
QStringList lastList = timestampMap.values().at(1);
Lorenz Meier
committed
foreach (QStringList list, timestampMap.values()) {
// Write this current time set out to the file
// only do so from the 2nd line on, since the first
// line could be incomplete
Lorenz Meier
committed
// Set the timestamp
list.replace(0,QString("%1").arg(timestampMap.keys().at(lineCounter)));
// Fill holes if necessary
if (holeFillingEnabled) {
int index = 0;
foreach (QString str, list) {
if (str == "" || str == "NaN") {
list.replace(index, lastList.at(index));
}
index++;
}
}
// Set last list
lastList = list;
Lorenz Meier
committed
// Write data columns
QString output = list.join(delimiter) + "\n";
outTmpFile.write(output.toLocal8Bit());
}
lineCounter++;
}
// We're now done with the source file
infile.close();
emit logProcessingStatusChanged(tr("Log Compressor: Writing output to file %1").arg(QFileInfo(outFileName).absoluteFilePath()));
// Clean up and update the status before we return.
currentDataLine = 0;
emit logProcessingStatusChanged(tr("Log compressor: Finished processing file: %1").arg(outFileName));
emit finishedFile(outFileName);
running = false;
}
/**
* @param holeFilling If hole filling is enabled, the compressor tries to fill empty data fields with previous
* values from the same variable (or NaN, if no previous value existed)
*/
void LogCompressor::startCompression(bool holeFilling)
{
holeFillingEnabled = holeFilling;
start();
}
bool LogCompressor::isFinished()
{
return !running;
}
int LogCompressor::getCurrentLine()
{
return currentDataLine;