目录
前言
上篇讲解了AirMap文件夹下的AirspaceManager类,本篇按照顺序本来要轮到AirspaceManagement但是里面的文件在pro中都没有被引用,所以暂时跳过,后边需要用到了再单独开篇讲解,本篇我们讲解AnalysizeView文件夹中的源码。
一.ExifParser
exif全称Exchangeable Image File,翻译为可交换图像文件。以下内容来自百科。先学习下什么是可交换图像文件。
可交换图像文件格式。EXIF是一种规范,它定义了用于数码相机的图像、声音和标签的数据。EXIF由日本电子工业发展协会(JEIDA)提出,用于支持和规范数码相机存储的信息类型。Exif是一种图象文件格式,它的数据存储与JPEG格式是完全相同的。实际上Exif格式就是在JPEG格式头部插入了数码照片的信息,包括拍摄时的光圈、快门、白平衡、ISO、焦距、日期时间等。
Exif信息是可以被任意编辑的,因此只有参考的功能。Exif信息以0xFFE1作为开头标记,后两个字节表示Exif信息的长度。所以Exif信息最大为64 kb,而内部采用TIFF格式。
这个类的作用就是读取exif数据中时间信息,还有就是将原有exif数据拿到后插入gps数据,并修改数据中的某些属性。这个方法可能用的会比较多,比如我们想在图像数据中增加一些自定义的信息比如gps信息。下面看代码:
#ifndef EXIFPARSER_H
#define EXIFPARSER_H
#include <QGeoCoordinate>
#include <QDebug>
#include "GeoTagController.h"
//可交换图像文件(Exchangeable Image File)
class ExifParser
{
public:
//构造函数
ExifParser();
~ExifParser();
//读取时间
double readTime(QByteArray& buf);
//写入
bool write(QByteArray& buf, GeoTagWorker::cameraFeedbackPacket& geotag);
};
#endif // EXIFPARSER_H
cc文件
#include "ExifParser.h"
#include <math.h>
#include <QtEndian>
#include <QDateTime>
ExifParser::ExifParser()
{
}
ExifParser::~ExifParser()
{
}
double ExifParser::readTime(QByteArray& buf)
{
//创建个tiff头
//标签图像文件格式(Tagged Image File Format,简写为TIFF)是一种灵活的位图格式,主要用来存储包括照片和艺术图在内的图像
QByteArray tiffHeader("\x49\x49\x2A", 3);
//创建个数据头
QByteArray createDateHeader("\x04\x90\x02", 3);
// find header position
//找到tiff头的位置
uint32_t tiffHeaderIndex = buf.indexOf(tiffHeader);
// find creation date header index
//找到数据头的位置
uint32_t createDateHeaderIndex = buf.indexOf(createDateHeader);
// extract size of date-time string, -1 accounting for null-termination
//提取日期时间字符串的大小,-1用于null终止
uint32_t* sizeString = reinterpret_cast<uint32_t*>(buf.mid(createDateHeaderIndex + 4, 4).data());
//解析
uint32_t createDateStringSize = qFromLittleEndian(*sizeString) - 1;
// extract location of date-time string
//提取日期时间字符串的位置
uint32_t* dataIndex = reinterpret_cast<uint32_t*>(buf.mid(createDateHeaderIndex + 8, 4).data());
uint32_t createDateStringDataIndex = qFromLittleEndian(*dataIndex) + tiffHeaderIndex;
// read out data of create date-time field
// 读取创建日期-时间字段的数据
QString createDate = buf.mid(createDateStringDataIndex, createDateStringSize);
QStringList createDateList = createDate.split(' ');
if (createDateList.count() < 2) {
qWarning() << "Could not decode creation time and date: " << createDateList;
return -1.0;
}
QStringList dateList = createDateList[0].split(':');
if (dateList.count() < 3) {
qWarning() << "Could not decode creation date: " << dateList;
return -1.0;
}
QStringList timeList = createDateList[1].split(':');
if (timeList.count() < 3) {
qWarning() << "Could not decode creation time: " << timeList;
return -1.0;
}
//构造时间
QDate date(dateList[0].toInt(), dateList[1].toInt(), dateList[2].toInt());
QTime time(timeList[0].toInt(), timeList[1].toInt(), timeList[2].toInt());
QDateTime tagTime(date, time);
return tagTime.toMSecsSinceEpoch()/1000.0;
}
//构造exif数据
bool ExifParser::write(QByteArray& buf, GeoTagWorker::cameraFeedbackPacket& geotag)
{
//Exif信息以0xFFE1作为开头标记
QByteArray app1Header("\xff\xe1", 2);
//定位到头后边
uint32_t app1HeaderInd = buf.indexOf(app1Header);
uint16_t *conversionPointer = reinterpret_cast<uint16_t *>(buf.mid(app1HeaderInd + 2, 2).data());
//数据大小
uint16_t app1Size = *conversionPointer;
uint16_t app1SizeEndian = qFromBigEndian(app1Size) + 0xa5; // change wrong endian
//tiff头
QByteArray tiffHeader("\x49\x49\x2A", 3);
//获取tiff头的位置
uint32_t tiffHeaderInd = buf.indexOf(tiffHeader);
conversionPointer = reinterpret_cast<uint16_t *>(buf.mid(tiffHeaderInd + 8, 2).data());
//获取tiff数据字段数目
uint16_t numberOfTiffFields = *conversionPointer;
//每个字段的偏移量
uint32_t nextIfdOffsetInd = tiffHeaderInd + 10 + 12 * (numberOfTiffFields);
conversionPointer = reinterpret_cast<uint16_t *>(buf.mid(nextIfdOffsetInd, 2).data());
uint16_t nextIfdOffset = *conversionPointer;
// Definition of useful unions and structs
union char2uint32_u {
char c[4];
uint32_t i;
};
union char2uint16_u {
char c[2];
uint16_t i;
};
// This struct describes a standart field used in exif files
struct field_s {
uint16_t tagID; // Describes which information is added here, e.g. GPS Lat 描述此处添加的信息,例如GPS Lat
uint16_t type; // Describes the data type, e.g. string, uint8_t,...描述数据类型,例如string, uint8_t,..
uint32_t size; // Describes the size 描述大小
uint32_t content; // Either contains the information, or the offset to the exif header where the information is stored (if 32 bits is not enough)要么包含信息,要么存储信息的exif头的偏移量(如果32位不够的话)
};
// This struct contains all the fields that we want to add to the image 这个结构体包含我们想要添加到图像中的所有字段
struct fields_s {
field_s gpsVersion; //定位版本
field_s gpsLatRef; //维度
field_s gpsLat; //维度数据
field_s gpsLonRef; //经度
field_s gpsLon; //经度数据
field_s gpsAltRef;
field_s gpsAlt;
field_s gpsMapDatum; //gps地图原点
uint32_t finishedDataField;
};
// These are the additional information that can not be put into a single uin32_t 这些附加信息不能放入单个uin32_t中
struct extended_s {
uint32_t gpsLat[6];
uint32_t gpsLon[6];
uint32_t gpsAlt[2];
char mapDatum[7];// = {0x57,0x47,0x53,0x2D,0x38,0x34,0x00};
};
// This struct contains all the information we want to add to the image 包含上述数据的总得数据结构体
struct readable_s {
fields_s fields;
extended_s extendedData;
};
// This union is used because for writing the information we have to use a char array, but we still want the information to be available in a more descriptive way
// 之所以使用这个联合,是因为在写入信息时必须使用char数组,但我们仍然希望以一种更具描述性的方式提供信息
union {
char c[0xa3];
readable_s readable;
} gpsData;
char2uint32_u gpsIFDInd;
gpsIFDInd.i = nextIfdOffset;
// this will stay constant 这个会保持不变
QByteArray gpsInfo("\x25\x88\x04\x00\x01\x00\x00\x00", 8);
gpsInfo.append(gpsIFDInd.c[0]);
gpsInfo.append(gpsIFDInd.c[1]);
gpsInfo.append(gpsIFDInd.c[2]);
gpsInfo.append(gpsIFDInd.c[3]);
// filling values to gpsData 将值填充到gpsData
uint32_t gpsDataExtInd = gpsIFDInd.i + 2 + sizeof(fields_s);
// Filling up the fields with the corresponding values用相应的值填充字段
gpsData.readable.fields.gpsVersion.tagID = 0;
gpsData.readable.fields.gpsVersion.type = 1;
gpsData.readable.fields.gpsVersion.size = 4;
gpsData.readable.fields.gpsVersion.content = 2;
gpsData.readable.fields.gpsLatRef.tagID = 1;
gpsData.readable.fields.gpsLatRef.type = 2;
gpsData.readable.fields.gpsLatRef.size = 2;
gpsData.readable.fields.gpsLatRef.content = geotag.latitude > 0 ? 'N' : 'S';
gpsData.readable.fields.gpsLat.tagID = 2;
gpsData.readable.fields.gpsLat.type = 5;
gpsData.readable.fields.gpsLat.size = 3;
gpsData.readable.fields.gpsLat.content = gpsDataExtInd;
gpsData.readable.fields.gpsLonRef.tagID = 3;
gpsData.readable.fields.gpsLonRef.type = 2;
gpsData.readable.fields.gpsLonRef.size = 2;
gpsData.readable.fields.gpsLonRef.content = geotag.longitude > 0 ? 'E' : 'W';
gpsData.readable.fields.gpsLon.tagID = 4;
gpsData.readable.fields.gpsLon.type = 5;
gpsData.readable.fields.gpsLon.size = 3;
gpsData.readable.fields.gpsLon.content = gpsDataExtInd + 6 * 4;
gpsData.readable.fields.gpsAltRef.tagID = 5;
gpsData.readable.fields.gpsAltRef.type = 1;
gpsData.readable.fields.gpsAltRef.size = 1;
gpsData.readable.fields.gpsAltRef.content = 0x00;
gpsData.readable.fields.gpsAlt.tagID = 6;
gpsData.readable.fields.gpsAlt.type = 5;
gpsData.readable.fields.gpsAlt.size = 1;
gpsData.readable.fields.gpsAlt.content = gpsDataExtInd + 6 * 4 * 2;
gpsData.readable.fields.gpsMapDatum.tagID = 18;
gpsData.readable.fields.gpsMapDatum.type = 2;
gpsData.readable.fields.gpsMapDatum.size = 7;
gpsData.readable.fields.gpsMapDatum.content = gpsDataExtInd + 6 * 4 * 2 + 2 * 4;
gpsData.readable.fields.finishedDataField = 0;
// Filling up the additional information that does not fit into the fields 填充不符合字段的附加信息
gpsData.readable.extendedData.gpsLat[0] = abs(static_cast<int>(geotag.latitude));
gpsData.readable.extendedData.gpsLat[1] = 1;
gpsData.readable.extendedData.gpsLat[2] = static_cast<int>((fabs(geotag.latitude) - floor(fabs(geotag.latitude))) * 60.0);
gpsData.readable.extendedData.gpsLat[3] = 1;
gpsData.readable.extendedData.gpsLat[4] = static_cast<int>((fabs(geotag.latitude) * 60.0 - floor(fabs(geotag.latitude) * 60.0)) * 60000.0);
gpsData.readable.extendedData.gpsLat[5] = 1000;
gpsData.readable.extendedData.gpsLon[0] = abs(static_cast<int>(geotag.longitude));
gpsData.readable.extendedData.gpsLon[1] = 1;
gpsData.readable.extendedData.gpsLon[2] = static_cast<int>((fabs(geotag.longitude) - floor(fabs(geotag.longitude))) * 60.0);
gpsData.readable.extendedData.gpsLon[3] = 1;
gpsData.readable.extendedData.gpsLon[4] = static_cast<int>((fabs(geotag.longitude) * 60.0 - floor(fabs(geotag.longitude) * 60.0)) * 60000.0);
gpsData.readable.extendedData.gpsLon[5] = 1000;
gpsData.readable.extendedData.gpsAlt[0] = geotag.altitude * 100;
gpsData.readable.extendedData.gpsAlt[1] = 100;
gpsData.readable.extendedData.mapDatum[0] = 'W';
gpsData.readable.extendedData.mapDatum[1] = 'G';
gpsData.readable.extendedData.mapDatum[2] = 'S';
gpsData.readable.extendedData.mapDatum[3] = '-';
gpsData.readable.extendedData.mapDatum[4] = '8';
gpsData.readable.extendedData.mapDatum[5] = '4';
gpsData.readable.extendedData.mapDatum[6] = 0x00;
// remove 12 spaces from image description, as otherwise we need to loop through every field and correct the new address values
// 从图像描述中删除12个空格,否则我们需要循环遍历每个字段并纠正新的地址值
buf.remove(nextIfdOffsetInd + 4, 12);
// TODO correct size in image description
// insert Gps Info to image file 插入Gps信息到图像文件
buf.insert(nextIfdOffsetInd, gpsInfo, 12);
char numberOfFields[2] = {0x08, 0x00};
// insert number of gps specific fields that we want to add 插入我们想要添加的GPS特定字段的数量
buf.insert(gpsIFDInd.i + tiffHeaderInd, numberOfFields, 2);
// insert the gps data 插入gps数据
buf.insert(gpsIFDInd.i + 2 + tiffHeaderInd, gpsData.c, 0xa3);
// update the new file size and exif offsets 更新新的文件大小和exif偏移量
char2uint16_u converter;
converter.i = qToBigEndian(app1SizeEndian);
buf.replace(app1HeaderInd + 2, 2, converter.c, 2);
converter.i = nextIfdOffset + 12 + 0xa5;
buf.replace(nextIfdOffsetInd + 12, 2, converter.c, 2);
converter.i = (numberOfTiffFields) + 1;
buf.replace(tiffHeaderInd + 8, 2, converter.c, 2);
return true;
}
二.ULogParser
这个类主要用于从日志文件中提取为图像标记所用的地理和时间信息,以下为主要方法的注释
bool ULogParser::getTagsFromLog(QByteArray& log, QList<GeoTagWorker::cameraFeedbackPacket>& cameraFeedback, QString& errorMessage)
{
errorMessage.clear();
//verify it's an ULog file 验证文件有效性
if(!log.contains(_ULogMagic)) {
errorMessage = tr("Could not detect ULog file header magic");
return false;
}
int index = ULOG_FILE_HEADER_LEN;
bool geotagFound = false;
while(index < log.count() - 1) {
//获取header信息
ULogMessageHeader header;
memset(&header, 0, sizeof(header));
memcpy(&header, log.data() + index, ULOG_MSG_HEADER_LEN);
//根据不同的类型进行处理
switch (header.msgType) {
case (int)ULogMessageType::FORMAT:
{
//获取格式信息
ULogMessageFormat format_msg;
memset(&format_msg, 0, sizeof(format_msg));
memcpy(&format_msg, log.data() + index, ULOG_MSG_HEADER_LEN + header.msgSize);
QString fmt(format_msg.format);
int posSeparator = fmt.indexOf(':');
QString messageName = fmt.left(posSeparator);
QString messageFields = fmt.mid(posSeparator + 1, header.msgSize - posSeparator - 1);
if(messageName == QLatin1String("camera_capture")) {
parseFieldFormat(messageFields);
}
break;
}
case (int)ULogMessageType::ADD_LOGGED_MSG:
{
//获取AddLogged信息
ULogMessageAddLogged addLoggedMsg;
memset(&addLoggedMsg, 0, sizeof(addLoggedMsg));
memcpy(&addLoggedMsg, log.data() + index, ULOG_MSG_HEADER_LEN + header.msgSize);
QString messageName(addLoggedMsg.msgName);
//如果得到了camera_capture 则对_cameraCaptureMsgID进行赋值
if(messageName.contains(QLatin1String("camera_capture"))) {
_cameraCaptureMsgID = addLoggedMsg.msgID;
geotagFound = true;
}
break;
}
case (int)ULogMessageType::DATA:
{
//获取data信息
uint16_t msgID = -1;
memcpy(&msgID, log.data() + index + ULOG_MSG_HEADER_LEN, 2);
//如果坐标数据已经在前面找到了 并且图像id和当前解析的id相同
if (geotagFound && msgID == _cameraCaptureMsgID) {
// Completely dynamic parsing, so that changing/reordering the message format will not break the parser
// 完全动态解析,因此更改/重新排序消息格式不会破坏解析器
GeoTagWorker::cameraFeedbackPacket feedback;
memset(&feedback, 0, sizeof(feedback));
memcpy(&feedback.timestamp, log.data() + index + 5 + _cameraCaptureOffsets.value(QStringLiteral("timestamp")), 8);
feedback.timestamp /= 1.0e6; // to seconds
memcpy(&feedback.timestampUTC, log.data() + index + 5 + _cameraCaptureOffsets.value(QStringLiteral("timestamp_utc")), 8);
feedback.timestampUTC /= 1.0e6; // to seconds
memcpy(&feedback.imageSequence, log.data() + index + 5 + _cameraCaptureOffsets.value(QStringLiteral("seq")), 4);
memcpy(&feedback.latitude, log.data() + index + 5 + _cameraCaptureOffsets.value(QStringLiteral("lat")), 8);
memcpy(&feedback.longitude, log.data() + index + 5 + _cameraCaptureOffsets.value(QStringLiteral("lon")), 8);
feedback.longitude = fmod(180.0 + feedback.longitude, 360.0) - 180.0;
memcpy(&feedback.altitude, log.data() + index + 5 + _cameraCaptureOffsets.value(QStringLiteral("alt")), 4);
memcpy(&feedback.groundDistance, log.data() + index + 5 + _cameraCaptureOffsets.value(QStringLiteral("ground_distance")), 4);
memcpy(&feedback.captureResult, log.data() + index + 5 + _cameraCaptureOffsets.value(QStringLiteral("result")), 1);
cameraFeedback.append(feedback);
}
break;
}
default:
break;
}
index += (3 + header.msgSize);
}
if (cameraFeedback.count() == 0) {
errorMessage = tr("Could not detect camera_capture packets in ULog");
return false;
}
return true;
}
三.PX4LogParser
这个类和上个类功能相近,只是处理的文件格式不同,该类主要处理px4类型的日志文件从中提取标记信息。主要代码如下:
bool PX4LogParser::getTagsFromLog(QByteArray& log, QList<GeoTagWorker::cameraFeedbackPacket>& cameraFeedback)
{
// general message header 通用消息头
char header[] = {(char)0xA3, (char)0x95, (char)0x00};
// header for GPOS message header GPOS消息头
char gposHeaderHeader[] = {(char)0xA3, (char)0x95, (char)0x80, (char)0x10, (char)0x00};
int gposHeaderOffset;
// header for GPOS message GPOS消息
char gposHeader[] = {(char)0xA3, (char)0x95, (char)0x10, (char)0x00};
int gposOffsets[3] = {3, 7, 11};
int gposLengths[3] = {4, 4, 4};
// header for trigger message header 触发消息头
char triggerHeaderHeader[] = {(char)0xA3, (char)0x95, (char)0x80, (char)0x37, (char)0x00};
int triggerHeaderOffset;
// header for trigger message 触发消息
char triggerHeader[] = {(char)0xA3, (char)0x95, (char)0x37, (char)0x00};
int triggerOffsets[2] = {3, 11};
int triggerLengths[2] = {8, 4};
// extract header information: message lengths 提取消息头信息:消息长度
uint8_t* iptr = reinterpret_cast<uint8_t*>(log.mid(log.indexOf(gposHeaderHeader) + 4, 1).data());
gposHeaderOffset = static_cast<int>(qFromLittleEndian(*iptr));
iptr = reinterpret_cast<uint8_t*>(log.mid(log.indexOf(triggerHeaderHeader) + 4, 1).data());
triggerHeaderOffset = static_cast<int>(qFromLittleEndian(*iptr));
// extract trigger data
int index = 1;
int sequence = -1;
while(index < log.count() - 1) {
// first extract trigger 首先提取触发消息头
index = log.indexOf(triggerHeader, index + 1);
// check for whether last entry has been passed
if (index < 0) {
break;
}
if (log.indexOf(header, index + 1) != index + triggerHeaderOffset) {
continue;
}
//构建feedback
GeoTagWorker::cameraFeedbackPacket feedback;
memset(&feedback, 0, sizeof(feedback));
//为feedback赋值
uint64_t* time = reinterpret_cast<uint64_t*>(log.mid(index + triggerOffsets[0], triggerLengths[0]).data());
double timeDouble = static_cast<double>(qFromLittleEndian(*time)) / 1.0e6;
uint32_t* seq = reinterpret_cast<uint32_t*>(log.mid(index + triggerOffsets[1], triggerLengths[1]).data());
int seqInt = static_cast<int>(qFromLittleEndian(*seq));
if (sequence >= seqInt || sequence + 20 < seqInt) { // assume that logging has not skipped more than 20 triggers. this prevents wrong header detection
continue;
}
feedback.timestamp = timeDouble;
feedback.imageSequence = seqInt;
sequence = seqInt;
// second extract position
bool lookForGpos = true;
while (lookForGpos) {
int gposIndex = log.indexOf(gposHeader, index + 1);
if (gposIndex < 0) {
cameraFeedback.append(feedback);
break;
}
index = gposIndex;
// verify that at an offset of gposHeaderOffset the next log message starts
if (gposIndex + gposHeaderOffset == log.indexOf(header, gposIndex + 1)) {
int32_t* lat = reinterpret_cast<int32_t*>(log.mid(gposIndex + gposOffsets[0], gposLengths[0]).data());
feedback.latitude = static_cast<double>(qFromLittleEndian(*lat))/1.0e7;
int32_t* lon = reinterpret_cast<int32_t*>(log.mid(gposIndex + gposOffsets[1], gposLengths[1]).data());
feedback.longitude = static_cast<double>(qFromLittleEndian(*lon))/1.0e7;
feedback.longitude = fmod(180.0 + feedback.longitude, 360.0) - 180.0;
float* alt = reinterpret_cast<float*>(log.mid(gposIndex + gposOffsets[2], gposLengths[2]).data());
feedback.altitude = qFromLittleEndian(*alt);
cameraFeedback.append(feedback);
break;
}
}
}
return true;
}
四.GeoTagController
这个类主要是使用以上三个类所构造的方法,该类包含两块一个是worker,他继承自线程类,用于开线程处理图像的标记写入。controller用于管理线程要处理的文件,文件夹,以及线程处理的进度跟踪和完成信号以及错误处理,下面看代码
/****************************************************************************
*
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#ifndef GeoTagController_H
#define GeoTagController_H
#include "QmlObjectListModel.h"
#include "Fact.h"
#include "FactMetaData.h"
#include <QObject>
#include <QString>
#include <QThread>
#include <QFileInfoList>
#include <QElapsedTimer>
#include <QDebug>
#include <QGeoCoordinate>
//继承自线程 地理信息标记工作线程
class GeoTagWorker : public QThread
{
Q_OBJECT
public:
GeoTagWorker();
//三个属性的getset方法
void setLogFile (const QString& logFile) { _logFile = logFile; }
void setImageDirectory (const QString& imageDirectory) { _imageDirectory = imageDirectory; }
void setSaveDirectory (const QString& saveDirectory) { _saveDirectory = saveDirectory; }
QString logFile () const { return _logFile; }
QString imageDirectory () const { return _imageDirectory; }
QString saveDirectory () const { return _saveDirectory; }
//取消
void cancelTagging () { _cancel = true; }
//相机反馈的包
struct cameraFeedbackPacket {
double timestamp; //时间信息
double timestampUTC; //utc信息
uint32_t imageSequence; //图像次序
double latitude; //纬度
double longitude; //经度
float altitude; //高度
float groundDistance; //地面距离
float attitudeQuaternion[4]; //高度四元数
uint8_t captureResult; //截图结果
};
protected:
void run() final;
signals:
void error (QString errorMsg);//出错
void taggingComplete ();//完成
void progressChanged (double progress);//进度
private:
bool triggerFiltering();
bool _cancel;
QString _logFile;
QString _imageDirectory;
QString _saveDirectory;
QFileInfoList _imageList;
QList<double> _imageTime;
QList<cameraFeedbackPacket> _triggerList;
QList<int> _imageIndices;
QList<int> _triggerIndices;
};
/// Controller for GeoTagPage.qml. Supports geotagging images based on logfile camera tags.
/// GeoTagPage.qml控制器。 支持基于日志文件相机标签的地理标记图像
class GeoTagController : public QObject
{
Q_OBJECT
public:
GeoTagController();
~GeoTagController();
Q_PROPERTY(QString logFile READ logFile WRITE setLogFile NOTIFY logFileChanged)
Q_PROPERTY(QString imageDirectory READ imageDirectory WRITE setImageDirectory NOTIFY imageDirectoryChanged)
Q_PROPERTY(QString saveDirectory READ saveDirectory WRITE setSaveDirectory NOTIFY saveDirectoryChanged)
/// Set to an error message is geotagging fails
Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorMessageChanged)
/// Progress indicator: 0-100
Q_PROPERTY(double progress READ progress NOTIFY progressChanged)
/// true: Currently in the process of tagging 目前正在标记过程中
Q_PROPERTY(bool inProgress READ inProgress NOTIFY inProgressChanged)
//开始和取消
Q_INVOKABLE void startTagging();
Q_INVOKABLE void cancelTagging() { _worker.cancelTagging(); }
QString logFile () const { return _worker.logFile(); }
QString imageDirectory () const { return _worker.imageDirectory(); }
QString saveDirectory () const { return _worker.saveDirectory(); }
double progress () const { return _progress; }
bool inProgress () const { return _worker.isRunning(); }
QString errorMessage () const { return _errorMessage; }
void setLogFile (QString file);
void setImageDirectory (QString dir);
void setSaveDirectory (QString dir);
signals:
void logFileChanged (QString logFile);
void imageDirectoryChanged (QString imageDirectory);
void saveDirectoryChanged (QString saveDirectory);
void progressChanged (double progress);
void inProgressChanged ();
void errorMessageChanged (QString errorMessage);
private slots:
void _workerProgressChanged (double progress);
void _workerError (QString errorMsg);
void _setErrorMessage (const QString& error);
private:
QString _errorMessage;
double _progress;
bool _inProgress;
GeoTagWorker _worker;
};
#endif
cc文件如下
/****************************************************************************
*
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#include "GeoTagController.h"
#include "QGCLoggingCategory.h"
#include <math.h>
#include <QtEndian>
#include <QDebug>
#include <cfloat>
#include <QDir>
#include <QUrl>
#include "ExifParser.h"
#include "ULogParser.h"
#include "PX4LogParser.h"
static const char* kTagged = "/TAGGED";
GeoTagController::GeoTagController()
: _progress(0)
, _inProgress(false)
{
//绑定线程函数的几个状态 进度改变 错误 开始和完成
connect(&_worker, &GeoTagWorker::progressChanged, this, &GeoTagController::_workerProgressChanged);
connect(&_worker, &GeoTagWorker::error, this, &GeoTagController::_workerError);
connect(&_worker, &GeoTagWorker::started, this, &GeoTagController::inProgressChanged);
connect(&_worker, &GeoTagWorker::finished, this, &GeoTagController::inProgressChanged);
}
GeoTagController::~GeoTagController()
{
}
//设置日志文件
void GeoTagController::setLogFile(QString filename)
{
filename = QUrl(filename).toLocalFile();
if (!filename.isEmpty()) {
_worker.setLogFile(filename);
emit logFileChanged(filename);
}
}
//设置图像目录
void GeoTagController::setImageDirectory(QString dir)
{
dir = QUrl(dir).toLocalFile();
if (!dir.isEmpty()) {
_worker.setImageDirectory(dir);
emit imageDirectoryChanged(dir);
if(_worker.saveDirectory() == "") {
QDir saveDirectory = QDir(_worker.imageDirectory() + kTagged);
if(saveDirectory.exists()) {
_setErrorMessage(tr("Images have alreay been tagged. Existing images will be removed."));
return;
}
}
}
_errorMessage.clear();
emit errorMessageChanged(_errorMessage);
}
void GeoTagController::setSaveDirectory(QString dir)
{
dir = QUrl(dir).toLocalFile();
if (!dir.isEmpty()) {
_worker.setSaveDirectory(dir);
emit saveDirectoryChanged(dir);
//-- Check and see if there are images already there
QDir saveDirectory = QDir(_worker.saveDirectory());
saveDirectory.setFilter(QDir::Files | QDir::Readable | QDir::NoSymLinks | QDir::Writable);
QStringList nameFilters;
nameFilters << "*.jpg" << "*.JPG";
saveDirectory.setNameFilters(nameFilters);
QStringList imageList = saveDirectory.entryList();
if(!imageList.isEmpty()) {
_setErrorMessage(tr("The save folder already contains images."));
return;
}
}
_errorMessage.clear();
emit errorMessageChanged(_errorMessage);
}
//开始标记
void GeoTagController::startTagging()
{
_errorMessage.clear();
emit errorMessageChanged(_errorMessage);
QDir imageDirectory = QDir(_worker.imageDirectory());
if(!imageDirectory.exists()) {
_setErrorMessage(tr("Cannot find the image directory."));
return;
}
if(_worker.saveDirectory() == "") {
QDir oldTaggedFolder = QDir(_worker.imageDirectory() + kTagged);
if(oldTaggedFolder.exists()) {
oldTaggedFolder.removeRecursively();
if(!imageDirectory.mkdir(_worker.imageDirectory() + kTagged)) {
_setErrorMessage(tr("Couldn't replace the previously tagged images"));
return;
}
}
} else {
QDir saveDirectory = QDir(_worker.saveDirectory());
if(!saveDirectory.exists()) {
_setErrorMessage(tr("Cannot find the save directory."));
return;
}
}
_worker.start();
}
void GeoTagController::_workerProgressChanged(double progress)
{
_progress = progress;
emit progressChanged(progress);
}
void GeoTagController::_workerError(QString errorMessage)
{
_errorMessage = errorMessage;
emit errorMessageChanged(errorMessage);
}
void GeoTagController::_setErrorMessage(const QString& error)
{
_errorMessage = error;
emit errorMessageChanged(error);
}
GeoTagWorker::GeoTagWorker()
: _cancel(false)
{
}
//线程开始
void GeoTagWorker::run()
{
_cancel = false;
emit progressChanged(1);
double nSteps = 5;
// Load Images
_imageList.clear();
//获取图像文件夹
QDir imageDirectory = QDir(_imageDirectory);
//设置过滤条件
imageDirectory.setFilter(QDir::Files | QDir::Readable | QDir::NoSymLinks | QDir::Writable);
//设置排列方式
imageDirectory.setSorting(QDir::Name);
//设置名称过滤
QStringList nameFilters;
nameFilters << "*.jpg" << "*.JPG";
imageDirectory.setNameFilters(nameFilters);
_imageList = imageDirectory.entryInfoList();
if(_imageList.isEmpty()) {
emit error(tr("The image directory doesn't contain images, make sure your images are of the JPG format"));
return;
}
emit progressChanged((100/nSteps));
// Parse EXIF 转化exif数据
ExifParser exifParser;
_imageTime.clear();
//遍历刚刚获取到的图像文件
for (int i = 0; i < _imageList.size(); ++i) {
QFile file(_imageList.at(i).absoluteFilePath());
if (!file.open(QIODevice::ReadOnly)) {
emit error(tr("Geotagging failed. Couldn't open an image."));
return;
}
QByteArray imageBuffer = file.readAll();
file.close();
//获取图像信息
_imageTime.append(exifParser.readTime(imageBuffer));
emit progressChanged((100/nSteps) + ((100/nSteps) / _imageList.size())*i);
if (_cancel) {
qCDebug(GeotaggingLog) << "Tagging cancelled";
emit error(tr("Tagging cancelled"));
return;
}
}
// Load log 加载日志文件
bool isULog = _logFile.endsWith(".ulg", Qt::CaseSensitive);
QFile file(_logFile);
if (!file.open(QIODevice::ReadOnly)) {
emit error(tr("Geotagging failed. Couldn't open log file."));
return;
}
QByteArray log = file.readAll();
file.close();
// Instantiate appropriate parser
_triggerList.clear();
bool parseComplete = false;
QString errorString;
if (isULog) {
ULogParser parser;
//从日志文件获取tag信息
parseComplete = parser.getTagsFromLog(log, _triggerList, errorString);
} else {
//否则从px4文件获取tag信息
PX4LogParser parser;
parseComplete = parser.getTagsFromLog(log, _triggerList);
}
//异常处理
if (!parseComplete) {
if (_cancel) {
qCDebug(GeotaggingLog) << "Tagging cancelled";
emit error(tr("Tagging cancelled"));
return;
} else {
qCDebug(GeotaggingLog) << "Log parsing failed";
errorString = tr("%1 - tagging cancelled").arg(errorString.isEmpty() ? tr("Log parsing failed") : errorString);
emit error(errorString);
return;
}
}
emit progressChanged(3*(100/nSteps));
qCDebug(GeotaggingLog) << "Found " << _triggerList.count() << " trigger logs.";
if (_cancel) {
qCDebug(GeotaggingLog) << "Tagging cancelled";
emit error(tr("Tagging cancelled"));
return;
}
// Filter Trigger 过滤刚刚收集到的trigger信息
if (!triggerFiltering()) {
qCDebug(GeotaggingLog) << "Geotagging failed in trigger filtering";
emit error(tr("Geotagging failed in trigger filtering"));
return;
}
emit progressChanged(4*(100/nSteps));
if (_cancel) {
qCDebug(GeotaggingLog) << "Tagging cancelled";
emit error(tr("Tagging cancelled"));
return;
}
// Tag images 获取要标记的图像数量
int maxIndex = std::min(_imageIndices.count(), _triggerIndices.count());
maxIndex = std::min(maxIndex, _imageList.count());
for(int i = 0; i < maxIndex; i++) {
int imageIndex = _imageIndices[i];
if (imageIndex >= _imageList.count()) {
emit error(tr("Geotagging failed. Requesting image #%1, but only %2 images present.").arg(imageIndex).arg(_imageList.count()));
return;
}
QFile fileRead(_imageList.at(_imageIndices[i]).absoluteFilePath());
if (!fileRead.open(QIODevice::ReadOnly)) {
emit error(tr("Geotagging failed. Couldn't open an image."));
return;
}
QByteArray imageBuffer = fileRead.readAll();
fileRead.close();
//在要处理的图像想写入刚刚收集到的数据
if (!exifParser.write(imageBuffer, _triggerList[_triggerIndices[i]])) {
emit error(tr("Geotagging failed. Couldn't write to image."));
return;
} else {
//如果exifParser写入失败就用文件进行写入然后保存
QFile fileWrite;
if(_saveDirectory == "") {
fileWrite.setFileName(_imageDirectory + "/TAGGED/" + _imageList.at(_imageIndices[i]).fileName());
} else {
fileWrite.setFileName(_saveDirectory + "/" + _imageList.at(_imageIndices[i]).fileName());
}
if (!fileWrite.open(QFile::WriteOnly)) {
emit error(tr("Geotagging failed. Couldn't write to an image."));
return;
}
fileWrite.write(imageBuffer);
fileWrite.close();
}
emit progressChanged(4*(100/nSteps) + ((100/nSteps) / maxIndex)*i);
if (_cancel) {
qCDebug(GeotaggingLog) << "Tagging cancelled";
emit error(tr("Tagging cancelled"));
return;
}
}
if (_cancel) {
qCDebug(GeotaggingLog) << "Tagging cancelled";
emit error(tr("Tagging cancelled"));
return;
}
emit progressChanged(100);
}
bool GeoTagWorker::triggerFiltering()
{
_imageIndices.clear();
_triggerIndices.clear();
if(_imageList.count() > _triggerList.count()) { // Logging dropouts
qCDebug(GeotaggingLog) << "Detected missing feedback packets.";
} else if (_imageList.count() < _triggerList.count()) { // Camera skipped frames
qCDebug(GeotaggingLog) << "Detected missing image frames.";
}
for(int i = 0; i < _imageList.count() && i < _triggerList.count(); i++) {
_imageIndices.append(static_cast<int>(_triggerList[i].imageSequence));
_triggerIndices.append(i);
}
return true;
}
五.LogDownloadController
这个类主要处理log文件的下载,感觉好像是从飞行器设备上下载下来,首先有了下载后的数据,才有前几步骤,日志文件的解析和图像文件的标注。
/****************************************************************************
*
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#ifndef LogDownloadController_H
#define LogDownloadController_H
#include <QObject>
#include <QTimer>
#include <QAbstractListModel>
#include <QLocale>
#include <QElapsedTimer>
#include <memory>
#include "UASInterface.h"
#include "AutoPilotPlugin.h"
class MultiVehicleManager;
class UASInterface;
class Vehicle;
class QGCLogEntry;
struct LogDownloadData;
//为其创建日志类
Q_DECLARE_LOGGING_CATEGORY(LogDownloadLog)
//-----------------------------------------------------------------------------
// QGCLogEntry的容器
class QGCLogModel : public QAbstractListModel
{
Q_OBJECT
public:
enum QGCLogModelRoles {
ObjectRole = Qt::UserRole + 1
};
QGCLogModel(QObject *parent = nullptr);
//数量
Q_PROPERTY(int count READ count NOTIFY countChanged)
//获取
Q_INVOKABLE QGCLogEntry* get(int index);
//操作 数量获取 插入 清理操作
int count (void) const;
void append (QGCLogEntry* entry);
void clear (void);
QGCLogEntry*operator[] (int i);
int rowCount (const QModelIndex & parent = QModelIndex()) const;
QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const;
signals:
void countChanged ();
protected:
QHash<int, QByteArray> roleNames() const;
private:
QList<QGCLogEntry*> _logEntries;
};
//-----------------------------------------------------------------------------
// 每一个log的字段属性 id 时间 大小 大小字符串 接收与否 选中与否 状态
class QGCLogEntry : public QObject {
Q_OBJECT
Q_PROPERTY(uint id READ id CONSTANT)
Q_PROPERTY(QDateTime time READ time NOTIFY timeChanged)
Q_PROPERTY(uint size READ size NOTIFY sizeChanged)
Q_PROPERTY(QString sizeStr READ sizeStr NOTIFY sizeChanged)
Q_PROPERTY(bool received READ received NOTIFY receivedChanged)
Q_PROPERTY(bool selected READ selected WRITE setSelected NOTIFY selectedChanged)
Q_PROPERTY(QString status READ status NOTIFY statusChanged)
public:
QGCLogEntry(uint logId, const QDateTime& dateTime = QDateTime(), uint logSize = 0, bool received = false);
//以上属性的get方法
uint id () const { return _logID; }
uint size () const { return _logSize; }
QString sizeStr () const;
QDateTime time () const { return _logTimeUTC; }
bool received () const { return _received; }
bool selected () const { return _selected; }
QString status () const { return _status; }
//以上属性的set方法
void setId (uint id_) { _logID = id_; }
void setSize (uint size_) { _logSize = size_; emit sizeChanged(); }
void setTime (QDateTime date_) { _logTimeUTC = date_; emit timeChanged(); }
void setReceived (bool rec_) { _received = rec_; emit receivedChanged(); }
void setSelected (bool sel_) { _selected = sel_; emit selectedChanged(); }
void setStatus (QString stat_) { _status = stat_; emit statusChanged(); }
//以上属性的更改信号
signals:
void idChanged ();
void timeChanged ();
void sizeChanged ();
void receivedChanged ();
void selectedChanged ();
void statusChanged ();
//以上属性对应的私有变量
private:
uint _logID;
uint _logSize;
QDateTime _logTimeUTC;
bool _received;
bool _selected;
QString _status;
};
//-----------------------------------------------------------------------------
class LogDownloadController : public QObject
{
Q_OBJECT
public:
LogDownloadController(void);
//属性 容器类 是否正在请求 是否正在下载
Q_PROPERTY(QGCLogModel* model READ model NOTIFY modelChanged)
Q_PROPERTY(bool requestingList READ requestingList NOTIFY requestingListChanged)
Q_PROPERTY(bool downloadingLogs READ downloadingLogs NOTIFY downloadingLogsChanged)
QGCLogModel* model () { return &_logEntriesModel; }
bool requestingList () const{ return _requestingLogEntries; }
bool downloadingLogs () const{ return _downloadingLogs; }
Q_INVOKABLE void refresh ();
Q_INVOKABLE void download (QString path = QString());
Q_INVOKABLE void eraseAll ();
Q_INVOKABLE void cancel ();
void downloadToDirectory(const QString& dir);
signals:
void requestingListChanged ();
void downloadingLogsChanged ();
void modelChanged ();
void selectionChanged ();
//槽函数
private slots:
void _setActiveVehicle (Vehicle* vehicle);
void _logEntry (UASInterface *uas, uint32_t time_utc, uint32_t size, uint16_t id, uint16_t num_logs, uint16_t last_log_num);
void _logData (UASInterface *uas, uint32_t ofs, uint16_t id, uint8_t count, const uint8_t *data);
void _processDownload ();
private:
bool _entriesComplete ();
bool _chunkComplete () const;
bool _logComplete () const;
void _findMissingEntries();
void _receivedAllEntries();
void _receivedAllData ();
void _resetSelection (bool canceled = false);
void _findMissingData ();
void _requestLogList (uint32_t start, uint32_t end);
void _requestLogData (uint16_t id, uint32_t offset, uint32_t count, int retryCount = 0);
bool _prepareLogDownload();
void _setDownloading (bool active);
void _setListing (bool active);
void _updateDataRate ();
QGCLogEntry* _getNextSelected();
UASInterface* _uas;
LogDownloadData* _downloadData;
QTimer _timer;
QGCLogModel _logEntriesModel;
Vehicle* _vehicle;
bool _requestingLogEntries;
bool _downloadingLogs;
int _retries;
int _apmOneBased;
QString _downloadPath;
};
#endif
cc文件内容众多,有的类是在看不明白,要结合实际场景去理解,还有的类比较简单所以没有打注释。
/****************************************************************************
*
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
*
* QGroundControl is licensed according to the terms in the file
* COPYING.md in the root of the source code directory.
*
****************************************************************************/
#include "LogDownloadController.h"
#include "MultiVehicleManager.h"
#include "QGCMAVLink.h"
#include "UAS.h"
#include "QGCApplication.h"
#include "QGCToolbox.h"
#include "QGCMapEngine.h"
#include "ParameterManager.h"
#include "Vehicle.h"
#include "SettingsManager.h"
#include <QDebug>
#include <QSettings>
#include <QUrl>
#include <QBitArray>
#include <QtCore/qmath.h>
#define kTimeOutMilliseconds 500
#define kGUIRateMilliseconds 17
#define kTableBins 512
#define kChunkSize (kTableBins * MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN) //90
QGC_LOGGING_CATEGORY(LogDownloadLog, "LogDownloadLog")
//-----------------------------------------------------------------------------
struct LogDownloadData {
LogDownloadData(QGCLogEntry* entry);
QBitArray chunk_table;
uint32_t current_chunk;
QFile file;
QString filename;
uint ID;
QGCLogEntry* entry;
uint written;
size_t rate_bytes;
qreal rate_avg;
QElapsedTimer elapsed;
void advanceChunk()
{
current_chunk++;
chunk_table = QBitArray(chunkBins(), false);
}
//qCeil向上取整qFloor向下取整 qRound 四舍五入
// The number of MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN bins in the current chunk
// 当前块中MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN容器的数量
uint32_t chunkBins() const
{
return qMin(qCeil((entry->size() - current_chunk*kChunkSize)/static_cast<qreal>(MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN)),
kTableBins);
}
// The number of kChunkSize chunks in the file
// 文件中kChunkSize块的数量
uint32_t numChunks() const
{
return qCeil(entry->size() / static_cast<qreal>(kChunkSize));
}
// True if all bins in the chunk have been set to val
// 如果chunk中的所有容器都被设置为val,则为True
bool chunkEquals(const bool val) const
{
return chunk_table == QBitArray(chunk_table.size(), val);
}
};
//----------------------------------------------------------------------------------------
// 构造LogDownloadData
LogDownloadData::LogDownloadData(QGCLogEntry* entry_)
: ID(entry_->id())
, entry(entry_)
, written(0)
, rate_bytes(0)
, rate_avg(0)
{
}
//----------------------------------------------------------------------------------------
// 构造QGCLogEntry
QGCLogEntry::QGCLogEntry(uint logId, const QDateTime& dateTime, uint logSize, bool received)
: _logID(logId)
, _logSize(logSize)
, _logTimeUTC(dateTime)
, _received(received)
, _selected(false)
{
_status = tr("Pending");
}
//----------------------------------------------------------------------------------------
QString
QGCLogEntry::sizeStr() const
{
return QGCMapEngine::bigSizeToString(_logSize);
}
//----------------------------------------------------------------------------------------
LogDownloadController::LogDownloadController(void)
: _uas(nullptr)
, _downloadData(nullptr)
, _vehicle(nullptr)
, _requestingLogEntries(false)
, _downloadingLogs(false)
, _retries(0)
, _apmOneBased(0)
{
MultiVehicleManager *manager = qgcApp()->toolbox()->multiVehicleManager();
//绑定槽函数
//接收MultiVehicleManager传来的activeVehicleChanged 当前激活设备更改
connect(manager, &MultiVehicleManager::activeVehicleChanged, this, &LogDownloadController::_setActiveVehicle);
//接收计时器的超时函数
connect(&_timer, &QTimer::timeout, this, &LogDownloadController::_processDownload);
//设置激活的设备为MultiVehicleManager中的激活设备
_setActiveVehicle(manager->activeVehicle());
}
//----------------------------------------------------------------------------------------
void LogDownloadController::_processDownload()
{
//根据两个状态做不同操作
if(_requestingLogEntries) {
_findMissingEntries();
} else if(_downloadingLogs) {
_findMissingData();
}
}
//----------------------------------------------------------------------------------------
void LogDownloadController::_setActiveVehicle(Vehicle* vehicle)
{
// 如果uas为空
if(_uas) {
//清理数据 解绑槽函数
_logEntriesModel.clear();
disconnect(_uas, &UASInterface::logEntry, this, &LogDownloadController::_logEntry);
disconnect(_uas, &UASInterface::logData, this, &LogDownloadController::_logData);
_uas = nullptr;
}
//否这设置新的激活设备
_vehicle = vehicle;
if(_vehicle) {
//更新uas 关于uas后边轮到了会讲解 理解为无人机的序列号
_uas = vehicle->uas();
//绑定槽函数
connect(_uas, &UASInterface::logEntry, this, &LogDownloadController::_logEntry);
connect(_uas, &UASInterface::logData, this, &LogDownloadController::_logData);
}
}
//----------------------------------------------------------------------------------------
void LogDownloadController::_logEntry(UASInterface* uas, uint32_t time_utc, uint32_t size, uint16_t id, uint16_t num_logs, uint16_t /*last_log_num*/)
{
//-- Do we care? 判断uas数据有效性
if(!_uas || uas != _uas || !_requestingLogEntries) {
return;
}
//-- If this is the first, pre-fill it 如果这是第一个,提前填好
if(!_logEntriesModel.count() && num_logs > 0) {
//-- Is this APM? They send a first entry with bogus ID and only the
// count is valid. From now on, all entries are 1-based.
// 这是APM吗? 他们发送的第一个条目是假的ID,只有计算是有效的。 从现在开始,所有的条目都是基于1的。
if(_vehicle->firmwareType() == MAV_AUTOPILOT_ARDUPILOTMEGA) {
_apmOneBased = 1;
}
for(int i = 0; i < num_logs; i++) {
QGCLogEntry *entry = new QGCLogEntry(i);
_logEntriesModel.append(entry);
}
}
//-- Update this log record 更新此日志记录
if(num_logs > 0) {
//-- Skip if empty (APM first packet) 如果为空则跳过(APM第一个包)
if(size || _vehicle->firmwareType() != MAV_AUTOPILOT_ARDUPILOTMEGA) {
id -= _apmOneBased;
if(id < _logEntriesModel.count()) {
//获取到QGCLogEntry
QGCLogEntry* entry = _logEntriesModel[id];
//设置值
entry->setSize(size);
entry->setTime(QDateTime::fromSecsSinceEpoch(time_utc));
entry->setReceived(true);
entry->setStatus(tr("Available"));
} else {
qWarning() << "Received log entry for out-of-bound index:" << id;
//已收到越界索引的日志条目
}
}
} else {
//-- No logs to list
_receivedAllEntries();
}
//-- Reset retry count 重置重试次数
_retries = 0;
//-- Do we have it all? 如果数据完全了
if(_entriesComplete()) {
_receivedAllEntries();
} else {
//-- Reset timer
_timer.start(kTimeOutMilliseconds);
}
}
//----------------------------------------------------------------------------------------
//遍历所有数据QGCLogEntry查看received的值是否为true
bool LogDownloadController::_entriesComplete()
{
//-- Iterate entries and look for a gap
int num_logs = _logEntriesModel.count();
for(int i = 0; i < num_logs; i++) {
QGCLogEntry* entry = _logEntriesModel[i];
if(entry) {
if(!entry->received()) {
return false;
}
}
}
return true;
}
//----------------------------------------------------------------------------------------
//更改QGCLogEntry的选中状态
void LogDownloadController::_resetSelection(bool canceled)
{
int num_logs = _logEntriesModel.count();
for(int i = 0; i < num_logs; i++) {
QGCLogEntry* entry = _logEntriesModel[i];
if(entry) {
if(entry->selected()) {
if(canceled) {
entry->setStatus(tr("Canceled"));
}
entry->setSelected(false);
}
}
}
emit selectionChanged();
}
//----------------------------------------------------------------------------------------
//收完所有的QGCLogEntry了
void LogDownloadController::_receivedAllEntries()
{
_timer.stop();
_setListing(false);
}
//----------------------------------------------------------------------------------------
void LogDownloadController::_findMissingEntries()
{
int start = -1;
int end = -1;
int num_logs = _logEntriesModel.count();
//-- Iterate entries and look for a gap 迭代条目并寻找空白
// 查到未接收的文件,记录下标
for(int i = 0; i < num_logs; i++) {
QGCLogEntry* entry = _logEntriesModel[i];
if(entry) {
//如果还未接收
if(!entry->received()) {
//记录start为当前下标
//如果start当前已经有值,记录end为当前下标
if(start < 0)
start = i;
else
end = i;
}else{
//直到找到了一个已经接收的文件
if(start >= 0) {
break;
}
}
}
}
//-- Is there something missing?
// 如果有
if(start >= 0) {
//-- Have we tried too many times?
//如果尝试次数已经大于2
if(_retries++ > 2) {
for(int i = 0; i < num_logs; i++) {
//再遍历一次设置状态为错误
QGCLogEntry* entry = _logEntriesModel[i];
if(entry && !entry->received()) {
entry->setStatus(tr("Error"));
}
}
//-- Give up 放弃
_receivedAllEntries();
qWarning() << "Too many errors retreiving log list. Giving up.";
return;
}
//-- Is it a sequence or just one entry? 它是一个序列还是一个条目
if(end < 0) {
end = start;
}
//-- APM "Fix"
start += _apmOneBased;
end += _apmOneBased;
//-- Request these entries again
_requestLogList((uint32_t)start, (uint32_t) end);
} else {
_receivedAllEntries();
}
}
void LogDownloadController::_updateDataRate(void)
{
if (_downloadData->elapsed.elapsed() >= kGUIRateMilliseconds) {
//-- Update download rate
qreal rrate = _downloadData->rate_bytes / (_downloadData->elapsed.elapsed() / 1000.0);
_downloadData->rate_avg = (_downloadData->rate_avg * 0.95) + (rrate * 0.05);
_downloadData->rate_bytes = 0;
//-- Update status
const QString status = QString("%1 (%2/s)").arg(QGCMapEngine::bigSizeToString(_downloadData->written),
QGCMapEngine::bigSizeToString(_downloadData->rate_avg));
_downloadData->entry->setStatus(status);
_downloadData->elapsed.start();
}
}
//----------------------------------------------------------------------------------------
void LogDownloadController::_logData(UASInterface* uas, uint32_t ofs, uint16_t id, uint8_t count, const uint8_t* data)
{
//验证uas有效性
if(!_uas || uas != _uas || !_downloadData) {
return;
}
//-- APM "Fix"
id -= _apmOneBased;
if(_downloadData->ID != id) {
//收到错误的日志数据
qWarning() << "Received log data for wrong log";
return;
}
//忽略未对齐的传入数据包
if ((ofs % MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN) != 0) {
qWarning() << "Ignored misaligned incoming packet @" << ofs;
return;
}
bool result = false;
uint32_t timeout_time = kTimeOutMilliseconds;
if(ofs <= _downloadData->entry->size()) {
//chunk数量
const uint32_t chunk = ofs / kChunkSize;
//忽略无序块而的数据包
if (chunk != _downloadData->current_chunk) {
qWarning() << "Ignored packet for out of order chunk actual:expected" << chunk << _downloadData->current_chunk;
return;
}
const uint16_t bin = (ofs - chunk*kChunkSize) / MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN;
//收到超出范围的数据
if (bin >= _downloadData->chunk_table.size()) {
qWarning() << "Out of range bin received";
} else
_downloadData->chunk_table.setBit(bin);
if (_downloadData->file.pos() != ofs) {
// Seek to correct position 找到正确的位置
if (!_downloadData->file.seek(ofs)) {
qWarning() << "Error while seeking log file offset";
return;
}
}
//-- Write chunk to file 将chunk写入文件
if(_downloadData->file.write((const char*)data, count)) {
_downloadData->written += count;
_downloadData->rate_bytes += count;
_updateDataRate();
result = true;
//-- reset retries
_retries = 0;
//-- Reset timer
_timer.start(timeout_time);
//-- Do we have it all? 是否完整了
if(_logComplete()) {
_downloadData->entry->setStatus(tr("Downloaded"));
//-- Check for more
_receivedAllData();
} else if (_chunkComplete()) {
_downloadData->advanceChunk();
_requestLogData(_downloadData->ID,
_downloadData->current_chunk*kChunkSize,
_downloadData->chunk_table.size()*MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN);
} else if (bin < _downloadData->chunk_table.size() - 1 && _downloadData->chunk_table.at(bin+1)) {
// Likely to be grabbing fragments and got to the end of a gap
// 查找缺失文件
_findMissingData();
}
} else {
qWarning() << "Error while writing log file chunk";
}
} else {
qWarning() << "Received log offset greater than expected";
}
if(!result) {
_downloadData->entry->setStatus(tr("Error"));
}
}
//----------------------------------------------------------------------------------------
//检查chunk完整性
bool LogDownloadController::_chunkComplete() const
{
return _downloadData->chunkEquals(true);
}
//----------------------------------------------------------------------------------------
//检查log完整性
bool LogDownloadController::_logComplete() const
{
return _chunkComplete() && (_downloadData->current_chunk+1) == _downloadData->numChunks();
}
//----------------------------------------------------------------------------------------
//在这里发起接收log文件的请求
void LogDownloadController::_receivedAllData()
{
_timer.stop();
//-- Anything queued up for download?
if(_prepareLogDownload()) {
//-- Request Log 请求
_requestLogData(_downloadData->ID, 0, _downloadData->chunk_table.size()*MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN);
//开启计时
_timer.start(kTimeOutMilliseconds);
} else {
_resetSelection();
_setDownloading(false);
}
}
//----------------------------------------------------------------------------------------
// 查找丢失文件
void LogDownloadController::_findMissingData()
{
if (_logComplete()) {
_receivedAllData();
return;
} else if (_chunkComplete()) {
_downloadData->advanceChunk();
}
_retries++;
#if 0
// Trying the change to infinite log download. This way if retries hit 100% failure the data rate will
// slowly fall to 0 and the user can Cancel. This should work better on really crappy links.
if(_retries > 5) {
_downloadData->entry->setStatus(tr("Timed Out"));
//-- Give up
qWarning() << "Too many errors retreiving log data. Giving up.";
_receivedAllData();
return;
}
#endif
_updateDataRate();
uint16_t start = 0, end = 0;
const int size = _downloadData->chunk_table.size();
for (; start < size; start++) {
if (!_downloadData->chunk_table.testBit(start)) {
break;
}
}
for (end = start; end < size; end++) {
if (_downloadData->chunk_table.testBit(end)) {
break;
}
}
const uint32_t pos = _downloadData->current_chunk*kChunkSize + start*MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN,
len = (end - start)*MAVLINK_MSG_LOG_DATA_FIELD_DATA_LEN;
_requestLogData(_downloadData->ID, pos, len, _retries);
}
//----------------------------------------------------------------------------------------
// 请求log文件
void LogDownloadController::_requestLogData(uint16_t id, uint32_t offset, uint32_t count, int retryCount)
{
if (_vehicle) {
//获取weakLink
WeakLinkInterfacePtr weakLink = _vehicle->vehicleLinkManager()->primaryLink();
//如果weakLink没有过期
if (!weakLink.expired()) {
SharedLinkInterfacePtr sharedLink = weakLink.lock();
//-- APM "Fix"
id += _apmOneBased;
qCDebug(LogDownloadLog) << "Request log data (id:" << id << "offset:" << offset << "size:" << count << "retryCount" << retryCount << ")";
//请求
mavlink_message_t msg;
mavlink_msg_log_request_data_pack_chan(
qgcApp()->toolbox()->mavlinkProtocol()->getSystemId(),
qgcApp()->toolbox()->mavlinkProtocol()->getComponentId(),
sharedLink->mavlinkChannel(),
&msg,
_vehicle->id(),
_vehicle->defaultComponentId(),
id, offset, count);
_vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
}
}
}
//----------------------------------------------------------------------------------------
void LogDownloadController::refresh(void)
{
_logEntriesModel.clear();
//-- Get first 50 entries
_requestLogList(0, 49);
}
//----------------------------------------------------------------------------------------
// 获取日志列表
void LogDownloadController::_requestLogList(uint32_t start, uint32_t end)
{
if(_vehicle && _uas) {
qCDebug(LogDownloadLog) << "Request log entry list (" << start << "through" << end << ")";
_setListing(true);
WeakLinkInterfacePtr weakLink = _vehicle->vehicleLinkManager()->primaryLink();
if (!weakLink.expired()) {
SharedLinkInterfacePtr sharedLink = weakLink.lock();
mavlink_message_t msg;
mavlink_msg_log_request_list_pack_chan(
qgcApp()->toolbox()->mavlinkProtocol()->getSystemId(),
qgcApp()->toolbox()->mavlinkProtocol()->getComponentId(),
sharedLink->mavlinkChannel(),
&msg,
_vehicle->id(),
_vehicle->defaultComponentId(),
start,
end);
_vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
}
//-- Wait 5 seconds before bitching about not getting anything
_timer.start(5000);
}
}
//----------------------------------------------------------------------------------------
//下载到指定的文件夹
void LogDownloadController::download(QString path)
{
QString dir = path;
if (dir.isEmpty()) {
dir = qgcApp()->toolbox()->settingsManager()->appSettings()->logSavePath();
}
downloadToDirectory(dir);
}
//下载
void LogDownloadController::downloadToDirectory(const QString& dir)
{
//-- Stop listing just in case
_receivedAllEntries();
//-- Reset downloads, again just in case
delete _downloadData;
_downloadData = nullptr;
_downloadPath = dir;
if(!_downloadPath.isEmpty()) {
if(!_downloadPath.endsWith(QDir::separator()))
_downloadPath += QDir::separator();
//-- Iterate selected entries and shown them as waiting
int num_logs = _logEntriesModel.count();
for(int i = 0; i < num_logs; i++) {
QGCLogEntry* entry = _logEntriesModel[i];
if(entry) {
if(entry->selected()) {
entry->setStatus(tr("Waiting"));
}
}
}
//-- Start download process
_setDownloading(true);
_receivedAllData();
}
}
//----------------------------------------------------------------------------------------
QGCLogEntry* LogDownloadController::_getNextSelected()
{
//-- Iterate entries and look for a selected file
int num_logs = _logEntriesModel.count();
for(int i = 0; i < num_logs; i++) {
QGCLogEntry* entry = _logEntriesModel[i];
if(entry) {
if(entry->selected()) {
return entry;
}
}
}
return nullptr;
}
//----------------------------------------------------------------------------------------
//log下载钱的准备工作
bool LogDownloadController::_prepareLogDownload()
{
delete _downloadData;
_downloadData = nullptr;
QGCLogEntry* entry = _getNextSelected();
if(!entry) {
return false;
}
//-- Deselect file 取消选择文件
entry->setSelected(false);
emit selectionChanged();
bool result = false;
QString ftime;
if(entry->time().date().year() < 2010) {
ftime = tr("UnknownDate");
} else {
ftime = entry->time().toString(QStringLiteral("yyyy-M-d-hh-mm-ss"));
}
//创建容器
_downloadData = new LogDownloadData(entry);
//构造名称
_downloadData->filename = QString("log_") + QString::number(entry->id()) + "_" + ftime;
//如果PX4 修改为对应后缀 否则后缀为bin
if (_vehicle->firmwareType() == MAV_AUTOPILOT_PX4) {
QString loggerParam = QStringLiteral("SYS_LOGGER");
if (_vehicle->parameterManager()->parameterExists(FactSystem::defaultComponentId, loggerParam) &&
_vehicle->parameterManager()->getParameter(FactSystem::defaultComponentId, loggerParam)->rawValue().toInt() == 0) {
_downloadData->filename += ".px4log";
} else {
_downloadData->filename += ".ulg";
}
} else {
_downloadData->filename += ".bin";
}
//设置名称
_downloadData->file.setFileName(_downloadPath + _downloadData->filename);
//-- Append a number to the end if the filename already exists
// 如果文件名已经存在,则在末尾追加一个数字
if (_downloadData->file.exists()){
uint num_dups = 0;
QStringList filename_spl = _downloadData->filename.split('.');
do {
num_dups +=1;
_downloadData->file.setFileName(filename_spl[0] + '_' + QString::number(num_dups) + '.' + filename_spl[1]);
} while( _downloadData->file.exists());
}
//-- Create file 创建文件
if (!_downloadData->file.open(QIODevice::WriteOnly)) {
qWarning() << "Failed to create log file:" << _downloadData->filename;
} else {
//-- Preallocate file
if(!_downloadData->file.resize(entry->size())) {
qWarning() << "Failed to allocate space for log file:" << _downloadData->filename;
} else {
_downloadData->current_chunk = 0;
_downloadData->chunk_table = QBitArray(_downloadData->chunkBins(), false);
_downloadData->elapsed.start();
result = true;
}
}
if(!result) {
if (_downloadData->file.exists()) {
_downloadData->file.remove();
}
_downloadData->entry->setStatus(tr("Error"));
delete _downloadData;
_downloadData = nullptr;
}
return result;
}
//----------------------------------------------------------------------------------------
void LogDownloadController::_setDownloading(bool active)
{
if (_downloadingLogs != active) {
_downloadingLogs = active;
_vehicle->vehicleLinkManager()->setCommunicationLostEnabled(!active);
emit downloadingLogsChanged();
}
}
//----------------------------------------------------------------------------------------
void LogDownloadController::_setListing(bool active)
{
if (_requestingLogEntries != active) {
_requestingLogEntries = active;
_vehicle->vehicleLinkManager()->setCommunicationLostEnabled(!active);
emit requestingListChanged();
}
}
//----------------------------------------------------------------------------------------
//删除log请求
void LogDownloadController::eraseAll(void)
{
if(_vehicle && _uas) {
WeakLinkInterfacePtr weakLink = _vehicle->vehicleLinkManager()->primaryLink();
if (!weakLink.expired()) {
SharedLinkInterfacePtr sharedLink = weakLink.lock();
mavlink_message_t msg;
mavlink_msg_log_erase_pack_chan(
qgcApp()->toolbox()->mavlinkProtocol()->getSystemId(),
qgcApp()->toolbox()->mavlinkProtocol()->getComponentId(),
sharedLink->mavlinkChannel(),
&msg,
qgcApp()->toolbox()->multiVehicleManager()->activeVehicle()->id(), qgcApp()->toolbox()->multiVehicleManager()->activeVehicle()->defaultComponentId());
_vehicle->sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
}
refresh();
}
}
//----------------------------------------------------------------------------------------
//取消下载
void LogDownloadController::cancel(void)
{
if(_uas){
_receivedAllEntries();
}
if(_downloadData) {
_downloadData->entry->setStatus(tr("Canceled"));
if (_downloadData->file.exists()) {
_downloadData->file.remove();
}
delete _downloadData;
_downloadData = 0;
}
_resetSelection(true);
_setDownloading(false);
}
//-----------------------------------------------------------------------------
QGCLogModel::QGCLogModel(QObject* parent)
: QAbstractListModel(parent)
{
}
//-----------------------------------------------------------------------------
//获取指定文件
QGCLogEntry* QGCLogModel::get(int index)
{
if (index < 0 || index >= _logEntries.count()) {
return nullptr;
}
return _logEntries[index];
}
//-----------------------------------------------------------------------------
int QGCLogModel::count() const
{
return _logEntries.count();
}
//-----------------------------------------------------------------------------
void QGCLogModel::append(QGCLogEntry* object)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership);
_logEntries.append(object);
endInsertRows();
emit countChanged();
}
//-----------------------------------------------------------------------------
void QGCLogModel::clear(void)
{
if(!_logEntries.isEmpty()) {
beginRemoveRows(QModelIndex(), 0, _logEntries.count());
while (_logEntries.count()) {
QGCLogEntry* entry = _logEntries.last();
if(entry) entry->deleteLater();
_logEntries.removeLast();
}
endRemoveRows();
emit countChanged();
}
}
//-----------------------------------------------------------------------------
//[] 符号重载
QGCLogEntry* QGCLogModel::operator[](int index)
{
return get(index);
}
//-----------------------------------------------------------------------------
int QGCLogModel::rowCount(const QModelIndex& /*parent*/) const
{
return _logEntries.count();
}
//-----------------------------------------------------------------------------
QVariant QGCLogModel::data(const QModelIndex & index, int role) const {
if (index.row() < 0 || index.row() >= _logEntries.count())
return QVariant();
if (role == ObjectRole)
return QVariant::fromValue(_logEntries[index.row()]);
return QVariant();
}
//-----------------------------------------------------------------------------
QHash<int, QByteArray> QGCLogModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[ObjectRole] = "logEntry";
return roles;
}
总结
因为LogDownloadController类的内容过大,所以为了篇幅不要太长所以我觉得到这里本篇可以结束了,本篇一共分析了5个类,其中PX4LogParser、ULogParser处理log文件,对log文件进行解析,解析后的数据用于图像标注,ExifParser类就是对图像进行标注,利用exif数据将位置信息写入图像。GeoTagController主要负责统筹以上各类的工作。LogDownloadController类主要用于为以上操作提供资源,也就是下载log文件。下篇我们对该文件夹下剩余的两个类进行分析。