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:
parent
cdfd968592
commit
541e658741
@ -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;
|
||||||
+ ": </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()
|
||||||
|
+ ": </b></td><td>" + _list.at(i).value() + "</td></tr>";
|
||||||
|
html += "</table>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return html;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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 ×tamp() const {return _timestamp;}
|
const QDateTime ×tamp() 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 ×tamp) {_timestamp = timestamp;}
|
void setTimestamp(const QDateTime ×tamp) {_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;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user