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

495 lines
12 KiB
C++
Raw Normal View History

2020-12-22 22:32:07 +01:00
/*
WARNING: This code uses internal Qt API - the QZipReader class for reading
ZIP files - and things may break if Qt changes the API. For Qt5 this is not
2020-12-23 23:05:12 +01:00
a problem as we can "see the future" now and there are no changes in all
2020-12-22 22:32:07 +01:00
the supported Qt5 versions up to the last one (5.15). In Qt6 the class
2020-12-22 22:50:46 +01:00
might change or even disappear in the future, but this is very unlikely
2020-12-28 14:42:16 +01:00
as there were no changes for several years and The Qt Company's policy
2020-12-28 14:05:51 +01:00
is: "do not invest any resources into any desktop related stuff unless
2020-12-23 23:05:12 +01:00
absolutely necessary". There is an issue (QTBUG-3897) since the year 2009 to
2020-12-22 22:50:46 +01:00
include the ZIP reader into the public API, which aptly illustrates the
2020-12-22 22:32:07 +01:00
effort The Qt Company is willing to make about anything desktop related...
*/
#include <QFileInfo>
#include <QXmlStreamReader>
#include <QBuffer>
#include <QImageReader>
2020-12-22 22:32:07 +01:00
#include <QPainter>
#include <QPixmapCache>
2020-12-22 22:32:07 +01:00
#include <private/qzipreader_p.h>
#include "kmzmap.h"
#define ZOOM_THRESHOLD 0.9
2020-12-23 23:05:12 +01:00
#define TL(m) ((m).bbox().topLeft())
#define BR(m) ((m).bbox().bottomRight())
2020-12-22 22:32:07 +01:00
bool KMZMap::resCmp(const Tile &m1, const Tile &m2)
2020-12-22 22:32:07 +01:00
{
return m1.resolution() > m2.resolution();
2020-12-22 22:32:07 +01:00
}
bool KMZMap::xCmp(const Tile &m1, const Tile &m2)
2020-12-22 22:32:07 +01:00
{
return TL(m1).lon() < TL(m2).lon();
2020-12-22 22:32:07 +01:00
}
bool KMZMap::yCmp(const Tile &m1, const Tile &m2)
2020-12-22 22:32:07 +01:00
{
return TL(m1).lat() > TL(m2).lat();
2020-12-22 22:32:07 +01:00
}
KMZMap::Tile::Tile(const Overlay &overlay, QZipReader &zip)
: _overlay(overlay)
2020-12-22 22:32:07 +01:00
{
QByteArray ba(zip.fileData(overlay.path()));
QBuffer img(&ba);
QImageReader ir(&img);
_size = ir.size();
2020-12-22 22:32:07 +01:00
}
void KMZMap::Tile::configure(const Projection &proj)
{
ReferencePoint tl(PointD(0, 0), proj.ll2xy(bbox().topLeft()));
ReferencePoint br(PointD(_size.width(), _size.height()),
proj.ll2xy(bbox().bottomRight()));
_transform = Transform(tl, br);
}
QRectF KMZMap::Tile::bounds() const
{
QTransform t;
t.rotate(-rotation());
QRectF b(0, 0, _size.width(), _size.height());
QPolygonF ma(t.map(b));
return ma.boundingRect();
}
qreal KMZMap::Tile::resolution() const
2020-12-28 14:05:51 +01:00
{
QRectF d(0, 0, _size.width(), _size.height());
qreal dy = d.center().y();
QPointF dl(d.left(), dy);
QPointF dr(d.right(), dy);
2020-12-28 14:05:51 +01:00
double cy = bbox().center().lat();
Coordinates cl(bbox().left(), cy);
Coordinates cr(bbox().right(), cy);
2020-12-28 14:05:51 +01:00
qreal ds = cl.distanceTo(cr);
qreal ps = QLineF(dl, dr).length();
return ds/ps;
}
bool KMZMap::createTiles(const QList<Overlay> &overlays, QZipReader &zip)
2020-12-22 22:32:07 +01:00
{
if (overlays.isEmpty()) {
_errorString = "No usable overlay found";
return false;
}
2020-12-22 22:32:07 +01:00
_tiles.reserve(overlays.size());
2020-12-22 22:32:07 +01:00
for (int i = 0; i < overlays.size(); i++) {
const Overlay &ol = overlays.at(i);
Tile tile(ol, zip);
if (tile.isValid())
_tiles.append(tile);
else {
_errorString = ol.path() + ": invalid/missing overlay image";
return false;
}
}
2020-12-22 22:32:07 +01:00
return true;
2020-12-22 22:32:07 +01:00
}
void KMZMap::computeLLBounds()
2020-12-22 22:32:07 +01:00
{
for (int i = 0; i < _tiles.size(); i++)
_llbounds |= _tiles.at(i).bbox();
2020-12-22 22:32:07 +01:00
}
void KMZMap::computeZooms()
{
std::sort(_tiles.begin(), _tiles.end(), resCmp);
2020-12-22 22:32:07 +01:00
_zooms.append(Zoom(0, _tiles.count() - 1));
for (int i = 1; i < _tiles.count(); i++) {
qreal last = _tiles.at(i-1).resolution();
qreal cur = _tiles.at(i).resolution();
2020-12-22 22:32:07 +01:00
if (cur < last * ZOOM_THRESHOLD) {
_zooms.last().last = i-1;
_zooms.append(Zoom(i, _tiles.count() - 1));
2020-12-22 22:32:07 +01:00
}
}
}
void KMZMap::computeBounds()
{
QVector<QPointF> offsets(_tiles.count());
2020-12-22 22:32:07 +01:00
for (int z = 0; z < _zooms.count(); z++) {
QList<Tile> m;
2020-12-22 22:32:07 +01:00
for (int i = _zooms.at(z).first; i <= _zooms.at(z).last; i++)
m.append(_tiles.at(i));
2020-12-22 22:32:07 +01:00
std::sort(m.begin(), m.end(), xCmp);
offsets[_tiles.indexOf(m.first())].setX(m.first().bounds().left());
2020-12-22 22:32:07 +01:00
for (int i = 1; i < m.size(); i++) {
qreal w = ll2xy(TL(m.at(i)), m.first().transform()).x();
offsets[_tiles.indexOf(m.at(i))].setX(w + m.at(i).bounds().left());
2020-12-22 22:32:07 +01:00
}
std::sort(m.begin(), m.end(), yCmp);
offsets[_tiles.indexOf(m.first())].setY(m.first().bounds().top());
2020-12-22 22:32:07 +01:00
for (int i = 1; i < m.size(); i++) {
qreal h = ll2xy(TL(m.at(i)), m.first().transform()).y();
offsets[_tiles.indexOf(m.at(i))].setY(h + m.at(i).bounds().top());
2020-12-22 22:32:07 +01:00
}
}
2020-12-23 23:05:12 +01:00
_adjust = 0;
_bounds = QVector<Bounds>(_tiles.count());
for (int i = 0; i < _tiles.count(); i++) {
2023-05-04 21:14:52 +02:00
QRectF xy(offsets.at(i), _tiles.at(i).bounds().size() / _mapRatio);
_bounds[i] = Bounds(_tiles.at(i).bbox(), xy);
_adjust = qMin(qMin(_tiles.at(i).bounds().left(),
_tiles.at(i).bounds().top()), _adjust);
2020-12-23 23:05:12 +01:00
}
_adjust = -_adjust;
2020-12-22 22:32:07 +01:00
}
double KMZMap::number(QXmlStreamReader &reader)
{
bool res;
double ret = reader.readElementText().toDouble(&res);
if (!res)
reader.raiseError(QString("Invalid %1").arg(reader.name().toString()));
return ret;
}
QString KMZMap::icon(QXmlStreamReader &reader)
{
QString href;
while (reader.readNextStartElement()) {
if (reader.name() == QLatin1String("href"))
href = reader.readElementText();
else
reader.skipCurrentElement();
}
return href;
}
RectC KMZMap::latLonBox(QXmlStreamReader &reader, double *rotation)
{
double top = NAN, bottom = NAN, right = NAN, left = NAN;
while (reader.readNextStartElement()) {
if (reader.name() == QLatin1String("north"))
top = number(reader);
else if (reader.name() == QLatin1String("south"))
bottom = number(reader);
else if (reader.name() == QLatin1String("west"))
left = number(reader);
else if (reader.name() == QLatin1String("east"))
right = number(reader);
else if (reader.name() == QLatin1String("rotation"))
*rotation = number(reader);
else
reader.skipCurrentElement();
}
return RectC(Coordinates(left, top), Coordinates(right, bottom));
}
void KMZMap::groundOverlay(QXmlStreamReader &reader, QList<Overlay> &overlays)
2020-12-22 22:32:07 +01:00
{
QString image;
RectC rect;
double rotation = 0;
while (reader.readNextStartElement()) {
if (reader.name() == QLatin1String("Icon"))
image = icon(reader);
else if (reader.name() == QLatin1String("LatLonBox"))
rect = latLonBox(reader, &rotation);
else
reader.skipCurrentElement();
}
if (rect.isValid())
overlays.append(Overlay(image, rect, rotation));
else
2020-12-22 22:32:07 +01:00
reader.raiseError("Invalid LatLonBox");
}
void KMZMap::document(QXmlStreamReader &reader, QList<Overlay> &overlays)
2020-12-22 22:32:07 +01:00
{
while (reader.readNextStartElement()) {
if (reader.name() == QLatin1String("Document"))
document(reader, overlays);
2020-12-22 22:32:07 +01:00
else if (reader.name() == QLatin1String("GroundOverlay"))
groundOverlay(reader, overlays);
2020-12-22 22:32:07 +01:00
else if (reader.name() == QLatin1String("Folder"))
folder(reader, overlays);
2020-12-22 22:32:07 +01:00
else
reader.skipCurrentElement();
}
}
void KMZMap::folder(QXmlStreamReader &reader, QList<Overlay> &overlays)
2020-12-22 22:32:07 +01:00
{
while (reader.readNextStartElement()) {
2020-12-23 23:05:12 +01:00
if (reader.name() == QLatin1String("GroundOverlay"))
groundOverlay(reader, overlays);
2020-12-22 22:32:07 +01:00
else if (reader.name() == QLatin1String("Folder"))
folder(reader, overlays);
2020-12-22 22:32:07 +01:00
else
reader.skipCurrentElement();
}
}
void KMZMap::kml(QXmlStreamReader &reader, QList<Overlay> &overlays)
2020-12-22 22:32:07 +01:00
{
while (reader.readNextStartElement()) {
if (reader.name() == QLatin1String("Document"))
document(reader, overlays);
2020-12-22 22:32:07 +01:00
else if (reader.name() == QLatin1String("GroundOverlay"))
groundOverlay(reader, overlays);
2020-12-22 22:32:07 +01:00
else if (reader.name() == QLatin1String("Folder"))
folder(reader, overlays);
2020-12-22 22:32:07 +01:00
else
reader.skipCurrentElement();
}
}
KMZMap::KMZMap(const QString &fileName, QObject *parent)
: Map(fileName, parent), _zoom(0), _mapIndex(-1), _zip(0), _mapRatio(1.0),
_valid(false)
2020-12-22 22:32:07 +01:00
{
QZipReader zip(fileName, QIODevice::ReadOnly);
QByteArray xml(zip.fileData("doc.kml"));
QXmlStreamReader reader(xml);
QList<Overlay> overlays;
2020-12-22 22:32:07 +01:00
if (reader.readNextStartElement()) {
if (reader.name() == QLatin1String("kml"))
kml(reader, overlays);
2020-12-22 22:32:07 +01:00
else
reader.raiseError("Not a KMZ file");
}
if (reader.error()) {
_errorString = "doc.kml:" + QString::number(reader.lineNumber()) + ": "
+ reader.errorString();
return;
}
if (!createTiles(overlays, zip))
return;
computeLLBounds();
2020-12-22 22:32:07 +01:00
computeZooms();
_valid = true;
}
KMZMap::~KMZMap()
{
delete _zip;
}
2020-12-22 22:32:07 +01:00
QRectF KMZMap::bounds()
{
2020-12-23 23:05:12 +01:00
QRectF rect;
2020-12-22 22:32:07 +01:00
2020-12-23 23:05:12 +01:00
for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).last; i++)
rect |= _bounds.at(i).xy;
2020-12-22 22:32:07 +01:00
2020-12-23 23:05:12 +01:00
rect.moveTopLeft(rect.topLeft() * 2);
return rect;
2020-12-22 22:32:07 +01:00
}
int KMZMap::zoomFit(const QSize &size, const RectC &br)
{
_zoom = 0;
_mapIndex = -1;
if (!br.isValid()) {
_zoom = _zooms.size() - 1;
return _zoom;
}
for (int z = 0; z < _zooms.count(); z++) {
for (int i = _zooms.at(z).first; i <= _zooms.at(z).last; i++) {
if (!_bounds.at(i).ll.contains(br.center()))
continue;
QRect sbr = QRectF(ll2xy(br.topLeft(), _tiles.at(i).transform()),
ll2xy(br.bottomRight(), _tiles.at(i).transform()))
.toRect().normalized();
2020-12-22 22:32:07 +01:00
if (sbr.size().width() > size.width()
|| sbr.size().height() > size.height())
return _zoom;
_zoom = z;
break;
}
}
return _zoom;
}
void KMZMap::setZoom(int zoom)
{
_mapIndex = -1;
_zoom = zoom;
}
int KMZMap::zoomIn()
{
_zoom = qMin(_zoom + 1, _zooms.size() - 1);
_mapIndex = -1;
return _zoom;
}
int KMZMap::zoomOut()
{
_zoom = qMax(_zoom - 1, 0);
_mapIndex = -1;
return _zoom;
}
QPointF KMZMap::ll2xy(const Coordinates &c)
{
if (_mapIndex < 0 || !_bounds.at(_mapIndex).ll.contains(c)) {
_mapIndex = _zooms.at(_zoom).first;
for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).last; i++) {
if (_bounds.at(i).ll.contains(c)) {
_mapIndex = i;
break;
}
}
}
QPointF p = ll2xy(c, _tiles.at(_mapIndex).transform());
if (_tiles.at(_mapIndex).rotation()) {
2020-12-23 23:05:12 +01:00
QTransform matrix;
matrix.rotate(-_tiles.at(_mapIndex).rotation());
2020-12-23 23:05:12 +01:00
return matrix.map(p) + _bounds.at(_mapIndex).xy.topLeft();
} else
return p + _bounds.at(_mapIndex).xy.topLeft();
2020-12-22 22:32:07 +01:00
}
Coordinates KMZMap::xy2ll(const QPointF &p)
{
int idx = _zooms.at(_zoom).first;
for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).last; i++) {
if (_bounds.at(i).xy.contains(p)) {
idx = i;
break;
}
}
QPointF p2 = p - _bounds.at(idx).xy.topLeft();
if (_tiles.at(idx).rotation()) {
2020-12-23 23:05:12 +01:00
QTransform matrix;
matrix.rotate(_tiles.at(idx).rotation());
return xy2ll(matrix.map(p2), _tiles.at(idx).transform());
2020-12-23 23:05:12 +01:00
} else
return xy2ll(p2, _tiles.at(idx).transform());
2020-12-22 22:32:07 +01:00
}
void KMZMap::draw(QPainter *painter, const QRectF &rect, Flags flags)
{
Q_UNUSED(flags);
QRectF er = rect.adjusted(-_adjust * _mapRatio, -_adjust * _mapRatio,
_adjust * _mapRatio, _adjust * _mapRatio);
2020-12-23 23:05:12 +01:00
2020-12-22 22:32:07 +01:00
for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).last; i++) {
2020-12-23 23:05:12 +01:00
QRectF ir = er.intersected(_bounds.at(i).xy);
2020-12-22 22:32:07 +01:00
if (!ir.isNull())
draw(painter, ir, i);
2020-12-22 22:32:07 +01:00
}
}
void KMZMap::load(const Projection &in, const Projection &out,
qreal deviceRatio, bool hidpi)
2020-12-22 22:32:07 +01:00
{
Q_UNUSED(out);
_projection = in;
_mapRatio = hidpi ? deviceRatio : 1.0;
for (int i = 0; i < _tiles.size(); i++)
_tiles[i].configure(_projection);
computeBounds();
2020-12-22 22:32:07 +01:00
Q_ASSERT(!_zip);
_zip = new QZipReader(path(), QIODevice::ReadOnly);
}
void KMZMap::unload()
{
_bounds.clear();
2020-12-22 22:32:07 +01:00
delete _zip;
_zip = 0;
}
void KMZMap::draw(QPainter *painter, const QRectF &rect, int mapIndex)
{
const Tile &map = _tiles.at(mapIndex);
2020-12-22 22:32:07 +01:00
const QPointF offset = _bounds.at(mapIndex).xy.topLeft();
QRectF pr = QRectF(rect.topLeft() - offset, rect.size());
QRectF sr(pr.topLeft() * _mapRatio, pr.size() * _mapRatio);
QString key(path() + "/" + map.path());
QPixmap pm;
2020-12-22 22:32:07 +01:00
painter->save();
painter->translate(offset);
if (map.rotation())
painter->rotate(-map.rotation());
if (QPixmapCache::find(key, &pm)) {
pm.setDevicePixelRatio(_mapRatio);
painter->drawPixmap(pr.topLeft(), pm, sr);
} else {
QByteArray ba(_zip->fileData(map.path()));
QImage img(QImage::fromData(ba));
pm = QPixmap::fromImage(img);
pm.setDevicePixelRatio(_mapRatio);
painter->drawPixmap(pr.topLeft(), pm, sr);
QPixmapCache::insert(key, pm);
}
//painter->setPen(Qt::red);
//painter->drawRect(map.bounds());
2020-12-22 22:32:07 +01:00
painter->restore();
}
Map *KMZMap::create(const QString &path, const Projection &proj, bool *isDir)
{
Q_UNUSED(proj);
if (isDir)
*isDir = false;
return new KMZMap(path);
}