1
0
mirror of https://github.com/tumic0/GPXSee.git synced 2024-11-28 05:34:47 +01:00

Added support for thumbnail images in waypoint info

+ fixed and improved exif parser
This commit is contained in:
Martin Tůma 2019-03-13 20:48:25 +01:00
parent cdfd968592
commit 541e658741
8 changed files with 153 additions and 66 deletions

View File

@ -1,5 +1,9 @@
#include <QImageReader>
#include "tooltip.h" #include "tooltip.h"
#define THUMBNAIL_MAX_SIZE 240
void ToolTip::insert(const QString &key, const QString &value) void ToolTip::insert(const QString &key, const QString &value)
{ {
_list.append(KV(key, value)); _list.append(KV(key, value));
@ -7,16 +11,39 @@ void ToolTip::insert(const QString &key, const QString &value)
QString ToolTip::toString() QString ToolTip::toString()
{ {
if (_list.isEmpty()) QString html;
return QString();
QString ret = "<table>"; if (!_img.isNull()) {
QImageReader r(_img);
QSize size(r.size());
for (int i = 0; i < _list.count(); i++) if (size.isValid()) {
ret += "<tr><td align=\"right\"><b>" + _list.at(i).key() int width, height;
+ ":&nbsp;</b></td><td>" + _list.at(i).value() + "</td></tr>";
ret += "</table>"; if (size.width() > size.height()) {
width = qMin(size.width(), THUMBNAIL_MAX_SIZE);
return ret; qreal ratio = (qreal)size.width() / (qreal)size.height();
height = (int)(width / ratio);
} else {
height = qMin(size.height(), THUMBNAIL_MAX_SIZE);
qreal ratio = (qreal)size.height() / (qreal)size.width();
width = (int)(height / ratio);
}
html += "<div align=\"center\">";
html += QString("<img src=\"file:%0\" width=\"%1\" height=\"%2\"/>")
.arg(_img, QString::number(width), QString::number(height));
html += "</div>";
}
}
if (!_list.isEmpty()) {
html += "<table>";
for (int i = 0; i < _list.count(); i++)
html += "<tr><td align=\"right\"><b>" + _list.at(i).key()
+ ":&nbsp;</b></td><td>" + _list.at(i).value() + "</td></tr>";
html += "</table>";
}
return html;
} }

View File

@ -9,10 +9,12 @@ class ToolTip
{ {
public: public:
void insert(const QString &key, const QString &value); void insert(const QString &key, const QString &value);
void setImage(const QString &img) {_img = img;}
QString toString(); QString toString();
private: private:
QList<KV> _list; QList<KV> _list;
QString _img;
}; };
#endif // TOOLTIP_H #endif // TOOLTIP_H

View File

@ -27,6 +27,7 @@ QString WaypointItem::toolTip(Units units, CoordinatesFormat format)
if (!_waypoint.description().isNull()) if (!_waypoint.description().isNull())
tt.insert(qApp->translate("WaypointItem", "Description"), tt.insert(qApp->translate("WaypointItem", "Description"),
_waypoint.description()); _waypoint.description());
tt.setImage(_waypoint.image());
return tt.toString(); return tt.toString();
} }

View File

@ -4,10 +4,12 @@
#define TIFF_MM 0x4D4D #define TIFF_MM 0x4D4D
#define TIFF_MAGIC 42 #define TIFF_MAGIC 42
TIFFFile::TIFFFile(QIODevice *device) : _device(device), _ifd(0) TIFFFile::TIFFFile(QIODevice *device) : _device(device), _ifd(0), _offset(0)
{ {
quint16 endian, magic; quint16 endian, magic;
_offset = _device->pos();
if (_device->read((char*)&endian, sizeof(endian)) < (qint64)sizeof(endian)) if (_device->read((char*)&endian, sizeof(endian)) < (qint64)sizeof(endian))
return; return;
if (endian == TIFF_II) if (endian == TIFF_II)

View File

@ -19,8 +19,10 @@ public:
bool isValid() const {return _ifd != 0;} bool isValid() const {return _ifd != 0;}
quint32 ifd() const {return _ifd;} quint32 ifd() const {return _ifd;}
bool seek(qint64 pos) {return _device->seek(pos);} bool seek(qint64 pos) {return _device->seek(_offset + pos);}
qint64 pos() const {return _device->pos();} qint64 pos() const {return _offset + _device->pos();}
QByteArray read(qint64 maxSize) {return _device->read(maxSize);}
template<class T> bool readValue(T &val) template<class T> bool readValue(T &val)
{ {
T data; T data;
@ -50,6 +52,7 @@ private:
QIODevice *_device; QIODevice *_device;
bool _be; bool _be;
quint32 _ifd; quint32 _ifd;
qint64 _offset;
}; };
#endif // TIFFFILE_H #endif // TIFFFILE_H

View File

@ -11,41 +11,106 @@
#define GPSLongitude 4 #define GPSLongitude 4
#define GPSAltitudeRef 5 #define GPSAltitudeRef 5
#define GPSAltitude 6 #define GPSAltitude 6
#define GPSTimeStamp 7
#define GPSDateStamp 29
static double altitude(TIFFFile &file, quint32 offset) QTime EXIFParser::time(TIFFFile &file, const IFDEntry &ts) const
{ {
if (!file.seek(offset)) if (!(ts.type == TIFF_RATIONAL && ts.count == 3))
return NAN; return QTime();
quint32 num, den; if (!file.seek(ts.offset))
if (!file.readValue(num)) return QTime();
return false;
if (!file.readValue(den))
return false;
return num/den;
}
static double coordinate(TIFFFile &file, quint32 offset)
{
if (!file.seek(offset))
return NAN;
double dms[3];
double hms[3];
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
quint32 num, den; quint32 num, den;
if (!file.readValue(num)) if (!file.readValue(num))
return false; return QTime();
if (!file.readValue(den)) if (!file.readValue(den))
return false; return QTime();
dms[i] = num/den; hms[i] = num/(double)den;
}
return QTime((int)hms[0], (int)hms[1], (int)hms[2]);
}
QDate EXIFParser::date(TIFFFile &file, const IFDEntry &ds) const
{
if (!(ds.type == TIFF_ASCII && ds.count == 11))
return QDate();
if (!file.seek(ds.offset))
return QDate();
QByteArray text(file.read(11));
if (text.size() < 11)
return QDate();
return QDate::fromString(text, "yyyy:MM:dd");
}
double EXIFParser::altitude(TIFFFile &file, const IFDEntry &alt,
const IFDEntry &altRef) const
{
if (!(alt.type == TIFF_RATIONAL && alt.count == 1
&& altRef.type == TIFF_BYTE && altRef.count == 1))
return NAN;
if (!file.seek(alt.offset))
return NAN;
quint32 num, den;
if (!file.readValue(num))
return NAN;
if (!file.readValue(den))
return NAN;
return altRef.offset ? -num/(double)den : num/(double)den;
}
double EXIFParser::coordinate(TIFFFile &file, const IFDEntry &ll) const
{
if (!(ll.type == TIFF_RATIONAL && ll.count == 3))
return NAN;
if (!file.seek(ll.offset))
return NAN;
double dms[3];
for (int i = 0; i < 3; i++) {
quint32 num, den;
if (!file.readValue(num))
return NAN;
if (!file.readValue(den))
return NAN;
dms[i] = num/(double)den;
} }
return dms[0] + dms[1]/60 + dms[2]/3600; return dms[0] + dms[1]/60 + dms[2]/3600;
} }
Coordinates EXIFParser::coordinates(TIFFFile &file, const IFDEntry &lon,
const IFDEntry &lonRef, const IFDEntry &lat, const IFDEntry &latRef) const
{
if (!(latRef.type == TIFF_ASCII && latRef.count == 2
&& lonRef.type == TIFF_ASCII && lonRef.count == 2))
return Coordinates();
Coordinates c(coordinate(file, lon), coordinate(file, lat));
if (!c.isValid())
return Coordinates();
if (lonRef.offset == 'W')
c.rlon() = -c.lon();
if (latRef.offset == 'S')
c.rlat() = -c.lat();
return c;
}
bool EXIFParser::readEntry(TIFFFile &file, const QSet<quint16> &tags, bool EXIFParser::readEntry(TIFFFile &file, const QSet<quint16> &tags,
QMap<quint16, IFDEntry> &entries) const QMap<quint16, IFDEntry> &entries) const
{ {
@ -96,8 +161,6 @@ bool EXIFParser::parseTIFF(QDataStream &stream, QVector<Waypoint> &waypoints)
return false; return false;
} }
qint64 offset = stream.device()->pos();
TIFFFile tiff(stream.device()); TIFFFile tiff(stream.device());
if (!tiff.isValid()) { if (!tiff.isValid()) {
_errorString = "Invalid EXIF data"; _errorString = "Invalid EXIF data";
@ -105,68 +168,46 @@ bool EXIFParser::parseTIFF(QDataStream &stream, QVector<Waypoint> &waypoints)
} }
QSet<quint16> exifTags; QSet<quint16> exifTags;
exifTags.insert(GPSIFDTag); exifTags << GPSIFDTag;
QMap<quint16, IFDEntry> exifEntries; QMap<quint16, IFDEntry> exif;
for (quint32 ifd = tiff.ifd(); ifd; ) { for (quint32 ifd = tiff.ifd(); ifd; ) {
if (!readIFD(tiff, offset + ifd, exifTags, exifEntries) if (!readIFD(tiff, ifd, exifTags, exif) || !tiff.readValue(ifd)) {
|| !tiff.readValue(ifd)) {
_errorString = "Invalid EXIF IFD"; _errorString = "Invalid EXIF IFD";
return false; return false;
} }
} }
if (!exifEntries.contains(GPSIFDTag)) { if (!exif.contains(GPSIFDTag)) {
_errorString = "GPS IFD not found"; _errorString = "GPS IFD not found";
return false; return false;
} }
QSet<quint16> gpsTags; QSet<quint16> gpsTags;
gpsTags.insert(GPSLatitude); gpsTags << GPSLatitude << GPSLongitude << GPSLatitudeRef << GPSLongitudeRef
gpsTags.insert(GPSLongitude); << GPSAltitude << GPSAltitudeRef << GPSDateStamp << GPSTimeStamp;
gpsTags.insert(GPSLatitudeRef); QMap<quint16, IFDEntry> gps;
gpsTags.insert(GPSLongitudeRef); for (quint32 ifd = exif.value(GPSIFDTag).offset; ifd; ) {
gpsTags.insert(GPSAltitude); if (!readIFD(tiff, ifd, gpsTags, gps) || !tiff.readValue(ifd)) {
gpsTags.insert(GPSAltitudeRef);
QMap<quint16, IFDEntry> gpsEntries;
for (quint32 ifd = exifEntries.value(GPSIFDTag).offset; ifd; ) {
if (!readIFD(tiff, offset + ifd, gpsTags, gpsEntries)
|| !tiff.readValue(ifd)) {
_errorString = "Invalid GPS IFD"; _errorString = "Invalid GPS IFD";
return false; return false;
} }
} }
IFDEntry lat(gpsEntries.value(GPSLatitude)); Coordinates c(coordinates(tiff, gps.value(GPSLongitude),
IFDEntry lon(gpsEntries.value(GPSLongitude)); gps.value(GPSLongitudeRef), gps.value(GPSLatitude),
IFDEntry latRef(gpsEntries.value(GPSLatitudeRef)); gps.value(GPSLatitudeRef)));
IFDEntry lonRef(gpsEntries.value(GPSLongitudeRef));
if (!(lat.type == TIFF_RATIONAL && lat.count == 3
&& lon.type == TIFF_RATIONAL && lon.count == 3
&& latRef.type == TIFF_ASCII && latRef.count == 2
&& lonRef.type == TIFF_ASCII && lonRef.count == 2)) {
_errorString = "Invalid/missing GPS IFD Lat/Lon entry";
return false;
}
Coordinates c(coordinate(tiff, offset + lon.offset),
coordinate(tiff, offset + lat.offset));
if (lonRef.offset == 'W')
c.rlon() = -c.lon();
if (latRef.offset == 'S')
c.rlat() = -c.lat();
if (!c.isValid()) { if (!c.isValid()) {
_errorString = "Invalid coordinates"; _errorString = "Invalid/missing GPS coordinates";
return false; return false;
} }
Waypoint wp(c); Waypoint wp(c);
QFile *file = static_cast<QFile*>(stream.device()); QFile *file = static_cast<QFile*>(stream.device());
wp.setName(QFileInfo(file->fileName()).fileName()); wp.setName(QFileInfo(file->fileName()).baseName());
IFDEntry alt(gpsEntries.value(GPSAltitude)); wp.setImage(file->fileName());
IFDEntry altRef(gpsEntries.value(GPSAltitudeRef)); wp.setElevation(altitude(tiff, gps.value(GPSAltitude),
if (alt.type == TIFF_RATIONAL && alt.count == 1 && altRef.type == TIFF_BYTE gps.value(GPSAltitudeRef)));
&& altRef.count == 1) wp.setTimestamp(QDateTime(date(tiff, gps.value(GPSDateStamp)),
wp.setElevation(altRef.offset ? -altitude(tiff, alt.offset) time(tiff, gps.value(GPSTimeStamp)), Qt::UTC));
: altitude(tiff, alt.offset));
waypoints.append(wp); waypoints.append(wp);

View File

@ -1,6 +1,7 @@
#ifndef EXIFPARSER_H #ifndef EXIFPARSER_H
#define EXIFPARSER_H #define EXIFPARSER_H
#include <QDateTime>
#include "parser.h" #include "parser.h"
class QDataStream; class QDataStream;
@ -29,6 +30,14 @@ private:
bool readEntry(TIFFFile &file, const QSet<quint16> &tags, bool readEntry(TIFFFile &file, const QSet<quint16> &tags,
QMap<quint16, IFDEntry> &entries) const; QMap<quint16, IFDEntry> &entries) const;
double coordinate(TIFFFile &file, const IFDEntry &ll) const;
Coordinates coordinates(TIFFFile &file, const IFDEntry &lon,
const IFDEntry &lonRef, const IFDEntry &lat, const IFDEntry &latRef) const;
double altitude(TIFFFile &file, const IFDEntry &alt,
const IFDEntry &altRef) const;
QDate date(TIFFFile &file, const IFDEntry &ds) const;
QTime time(TIFFFile &file, const IFDEntry &ts) const;
QString _errorString; QString _errorString;
}; };

View File

@ -5,7 +5,6 @@
#include <QDateTime> #include <QDateTime>
#include <QHash> #include <QHash>
#include <QDebug> #include <QDebug>
#include <cmath>
#include "common/coordinates.h" #include "common/coordinates.h"
class Waypoint class Waypoint
@ -18,6 +17,7 @@ public:
const Coordinates &coordinates() const {return _coordinates;} const Coordinates &coordinates() const {return _coordinates;}
const QString &name() const {return _name;} const QString &name() const {return _name;}
const QString &description() const {return _description;} const QString &description() const {return _description;}
const QString &image() const {return _image;}
const QDateTime &timestamp() const {return _timestamp;} const QDateTime &timestamp() const {return _timestamp;}
qreal elevation() const {return _elevation;} qreal elevation() const {return _elevation;}
@ -28,6 +28,7 @@ public:
{_description = description;} {_description = description;}
void setTimestamp(const QDateTime &timestamp) {_timestamp = timestamp;} void setTimestamp(const QDateTime &timestamp) {_timestamp = timestamp;}
void setElevation(qreal elevation) {_elevation = elevation;} void setElevation(qreal elevation) {_elevation = elevation;}
void setImage(const QString &image) {_image = image;}
bool hasElevation() const {return !std::isnan(_elevation);} bool hasElevation() const {return !std::isnan(_elevation);}
@ -39,6 +40,7 @@ private:
Coordinates _coordinates; Coordinates _coordinates;
QString _name; QString _name;
QString _description; QString _description;
QString _image;
QDateTime _timestamp; QDateTime _timestamp;
qreal _elevation; qreal _elevation;
}; };