1
0
mirror of https://github.com/tumic0/GPXSee.git synced 2024-10-06 06:43:22 +02:00

Added initial WMTS support

This commit is contained in:
Martin Tůma 2018-02-20 23:37:19 +01:00
parent 3202fc4c15
commit 1bc4833a81
18 changed files with 711 additions and 155 deletions

View File

@ -116,7 +116,10 @@ HEADERS += src/config.h \
src/map/primemeridian.h \ src/map/primemeridian.h \
src/map/linearunits.h \ src/map/linearunits.h \
src/map/ct.h \ src/map/ct.h \
src/map/mapsource.h src/map/mapsource.h \
src/map/tileloader.h \
src/map/wmtsmap.h \
src/map/wmts.h
SOURCES += src/main.cpp \ SOURCES += src/main.cpp \
src/common/coordinates.cpp \ src/common/coordinates.cpp \
src/common/rectc.cpp \ src/common/rectc.cpp \
@ -202,7 +205,10 @@ SOURCES += src/main.cpp \
src/map/angularunits.cpp \ src/map/angularunits.cpp \
src/map/primemeridian.cpp \ src/map/primemeridian.cpp \
src/map/linearunits.cpp \ src/map/linearunits.cpp \
src/map/mapsource.cpp src/map/mapsource.cpp \
src/map/tileloader.cpp \
src/map/wmtsmap.cpp \
src/map/wmts.cpp
RESOURCES += gpxsee.qrc RESOURCES += gpxsee.qrc
TRANSLATIONS = lang/gpxsee_cs.ts \ TRANSLATIONS = lang/gpxsee_cs.ts \
lang/gpxsee_sv.ts \ lang/gpxsee_sv.ts \

View File

@ -4,7 +4,8 @@
#include <QFileOpenEvent> #include <QFileOpenEvent>
#include <QNetworkProxyFactory> #include <QNetworkProxyFactory>
#include <QLibraryInfo> #include <QLibraryInfo>
#include "map/onlinemap.h" #include "map/wmts.h"
#include "map/tileloader.h"
#include "map/downloader.h" #include "map/downloader.h"
#include "map/ellipsoid.h" #include "map/ellipsoid.h"
#include "map/gcs.h" #include "map/gcs.h"
@ -36,7 +37,9 @@ App::App(int &argc, char **argv) : QApplication(argc, argv),
#endif // Q_OS_MAC #endif // Q_OS_MAC
QNetworkProxyFactory::setUseSystemConfiguration(true); QNetworkProxyFactory::setUseSystemConfiguration(true);
OnlineMap::setDownloader(new Downloader(this)); Downloader *dl = new Downloader(this);
TileLoader::setDownloader(dl);
WMTS::setDownloader(dl);
OPENGL_SET_SAMPLES(4); OPENGL_SET_SAMPLES(4);
loadDatums(); loadDatums();
loadPCSs(); loadPCSs();

View File

@ -33,7 +33,7 @@ public:
void unload(); void unload();
bool isValid() const {return _valid;} bool isValid() const {return _valid;}
const QString &errorString() const {return _errorString;} QString errorString() const {return _errorString;}
static bool isAtlas(const QString &path); static bool isAtlas(const QString &path);

View File

@ -61,7 +61,7 @@ bool Downloader::saveToDisk(const QString &filename, QIODevice *data)
QFile file(filename); QFile file(filename);
if (!file.open(QIODevice::WriteOnly)) { if (!file.open(QIODevice::WriteOnly)) {
qWarning("Error writing map tile: %s: %s\n", qWarning("Error writing file: %s: %s\n",
qPrintable(filename), qPrintable(file.errorString())); qPrintable(filename), qPrintable(file.errorString()));
return false; return false;
} }
@ -80,11 +80,11 @@ void Downloader::downloadFinished(QNetworkReply *reply)
QUrl origin = reply->request().attribute(ATTR_ORIGIN).toUrl(); QUrl origin = reply->request().attribute(ATTR_ORIGIN).toUrl();
if (origin.isEmpty()) { if (origin.isEmpty()) {
_errorDownloads.insert(url); _errorDownloads.insert(url);
qWarning("Error downloading map tile: %s: %s\n", qWarning("Error downloading file: %s: %s\n",
url.toEncoded().constData(), qPrintable(reply->errorString())); url.toEncoded().constData(), qPrintable(reply->errorString()));
} else { } else {
_errorDownloads.insert(origin); _errorDownloads.insert(origin);
qWarning("Error downloading map tile: %s -> %s: %s\n", qWarning("Error downloading file: %s -> %s: %s\n",
origin.toEncoded().constData(), url.toEncoded().constData(), origin.toEncoded().constData(), url.toEncoded().constData(),
qPrintable(reply->errorString())); qPrintable(reply->errorString()));
} }
@ -99,11 +99,11 @@ void Downloader::downloadFinished(QNetworkReply *reply)
if (location == url) { if (location == url) {
_errorDownloads.insert(url); _errorDownloads.insert(url);
qWarning("Error downloading map tile: %s: " qWarning("Error downloading file: %s: "
"redirect loop\n", url.toEncoded().constData()); "redirect loop\n", url.toEncoded().constData());
} else if (level >= MAX_REDIRECT_LEVEL) { } else if (level >= MAX_REDIRECT_LEVEL) {
_errorDownloads.insert(origin); _errorDownloads.insert(origin);
qWarning("Error downloading map tile: %s: " qWarning("Error downloading file: %s: "
"redirect level limit reached\n", "redirect level limit reached\n",
origin.toEncoded().constData()); origin.toEncoded().constData());
} else { } else {

View File

@ -40,6 +40,9 @@ public:
void setBackgroundColor(const QColor &color) {_backgroundColor = color;} void setBackgroundColor(const QColor &color) {_backgroundColor = color;}
virtual bool isValid() const {return true;}
virtual QString errorString() const {return QString();}
signals: signals:
void loaded(); void loaded();

View File

@ -13,7 +13,7 @@ bool MapList::loadSource(const QString &path, bool dir)
MapSource ms; MapSource ms;
Map *map; Map *map;
if (!ms.loadFile(path, &map)) { if (!(map = ms.loadFile(path))) {
if (dir) if (dir)
_errorString += path + ": " + ms.errorString() + "\n"; _errorString += path + ": " + ms.errorString() + "\n";
else else

View File

@ -1,6 +1,7 @@
#include <QFile> #include <QFile>
#include <QXmlStreamReader> #include <QXmlStreamReader>
#include "onlinemap.h" #include "onlinemap.h"
#include "wmtsmap.h"
#include "mapsource.h" #include "mapsource.h"
@ -121,13 +122,17 @@ RectC MapSource::bounds(QXmlStreamReader &reader)
return RectC(Coordinates(left, top), Coordinates(right, bottom)); return RectC(Coordinates(left, top), Coordinates(right, bottom));
} }
void MapSource::map(QXmlStreamReader &reader, Map **map) Map *MapSource::map(QXmlStreamReader &reader)
{ {
QString name, url; QString name, url, format, layer, style, tileMatrixSet;
Range z(ZOOM_MIN, ZOOM_MAX); Range z(ZOOM_MIN, ZOOM_MAX);
RectC b(Coordinates(BOUNDS_LEFT, BOUNDS_TOP), RectC b(Coordinates(BOUNDS_LEFT, BOUNDS_TOP),
Coordinates(BOUNDS_RIGHT, BOUNDS_BOTTOM)); Coordinates(BOUNDS_RIGHT, BOUNDS_BOTTOM));
const QXmlStreamAttributes &attr = reader.attributes();
bool wmts = (attr.hasAttribute("type") && attr.value("type") == "WMTS")
? true : false;
while (reader.readNextStartElement()) { while (reader.readNextStartElement()) {
if (reader.name() == "name") if (reader.name() == "name")
name = reader.readElementText(); name = reader.readElementText();
@ -139,34 +144,53 @@ void MapSource::map(QXmlStreamReader &reader, Map **map)
} else if (reader.name() == "bounds") { } else if (reader.name() == "bounds") {
b = bounds(reader); b = bounds(reader);
reader.skipCurrentElement(); reader.skipCurrentElement();
} else } else if (reader.name() == "format")
format = reader.readElementText();
else if (reader.name() == "layer")
layer = reader.readElementText();
else if (reader.name() == "style")
style = reader.readElementText();
else if (reader.name() == "tilematrixset")
tileMatrixSet = reader.readElementText();
else
reader.skipCurrentElement(); reader.skipCurrentElement();
} }
*map = reader.error() ? 0 : new OnlineMap(name, url, z, b); if (reader.error())
return 0;
else if (wmts)
return new WMTSMap(name, url, format, layer, style, tileMatrixSet);
else
return new OnlineMap(name, url, z, b);
} }
bool MapSource::loadFile(const QString &path, Map **map) Map *MapSource::loadFile(const QString &path)
{ {
QFile file(path); QFile file(path);
QXmlStreamReader reader; QXmlStreamReader reader;
Map *map = 0;
if (!file.open(QFile::ReadOnly | QFile::Text)) { if (!file.open(QFile::ReadOnly | QFile::Text)) {
_errorString = file.errorString(); _errorString = file.errorString();
return false; return map;
} }
reader.setDevice(&file); reader.setDevice(&file);
if (reader.readNextStartElement()) { if (reader.readNextStartElement()) {
if (reader.name() == "map") if (reader.name() == "map")
MapSource::map(reader, map); map = MapSource::map(reader);
else else
reader.raiseError("Not an online map source file"); reader.raiseError("Not an online map source file");
} }
_errorString = reader.error() ? QString("%1: %2").arg(reader.lineNumber()) if (!map)
.arg(reader.errorString()) : QString(); _errorString = QString("%1: %2").arg(reader.lineNumber())
.arg(reader.errorString());
else if (!map->isValid()) {
_errorString = map->errorString();
delete map; map = 0;
}
return !reader.error(); return map;
} }

View File

@ -11,13 +11,13 @@ class QXmlStreamReader;
class MapSource class MapSource
{ {
public: public:
bool loadFile(const QString &path, Map **map); Map *loadFile(const QString &path);
const QString &errorString() const {return _errorString;} const QString &errorString() const {return _errorString;}
private: private:
RectC bounds(QXmlStreamReader &reader); RectC bounds(QXmlStreamReader &reader);
Range zooms(QXmlStreamReader &reader); Range zooms(QXmlStreamReader &reader);
void map(QXmlStreamReader &reader, Map **map); Map *map(QXmlStreamReader &reader);
QString _errorString; QString _errorString;
}; };

View File

@ -41,7 +41,7 @@ public:
void unload(); void unload();
bool isValid() const {return _valid;} bool isValid() const {return _valid;}
const QString &errorString() const {return _errorString;} QString errorString() const {return _errorString;}
QPointF ll2pp(const Coordinates &c) const QPointF ll2pp(const Coordinates &c) const
{return _projection.ll2xy(c);} {return _projection.ll2xy(c);}

View File

@ -1,5 +1,3 @@
#include <QFileInfo>
#include <QDir>
#include <QPainter> #include <QPainter>
#include "common/coordinates.h" #include "common/coordinates.h"
#include "common/rectc.h" #include "common/rectc.h"
@ -41,45 +39,26 @@ static int scale2zoom(qreal scale)
return (int)log2(360.0/(scale * (qreal)TILE_SIZE)); return (int)log2(360.0/(scale * (qreal)TILE_SIZE));
} }
static bool loadTileFile(Tile &tile, const QString &file)
{
if (!tile.pixmap().load(file)) {
qWarning("%s: error loading tile file\n", qPrintable(file));
return false;
}
return true;
}
Downloader *OnlineMap::downloader;
OnlineMap::OnlineMap(const QString &name, const QString &url, OnlineMap::OnlineMap(const QString &name, const QString &url,
const Range &zooms, const RectC &bounds, QObject *parent) const Range &zooms, const RectC &bounds, QObject *parent)
: Map(parent), _name(name), _url(url), _zooms(zooms), _bounds(bounds) : Map(parent), _name(name), _zooms(zooms), _bounds(bounds)
{ {
_block = false; _block = false;
_zoom = _zooms.max(); _zoom = _zooms.max();
_tileLoader = TileLoader(url, TILES_DIR + "/" + name);
QString path = TILES_DIR + QString("/") + name;
if (!QDir().mkpath(path))
qWarning("Error creating tiles dir: %s\n", qPrintable(path));
} }
void OnlineMap::load() void OnlineMap::load()
{ {
connect(downloader, SIGNAL(finished()), this, SLOT(emitLoaded())); connect(TileLoader::downloader(), SIGNAL(finished()), this,
SLOT(emitLoaded()));
} }
void OnlineMap::unload() void OnlineMap::unload()
{ {
disconnect(downloader, SIGNAL(finished()), this, SLOT(emitLoaded())); disconnect(TileLoader::downloader(), SIGNAL(finished()), this,
} SLOT(emitLoaded()));
void OnlineMap::fillTile(Tile &tile)
{
tile.pixmap() = QPixmap(TILE_SIZE, TILE_SIZE);
tile.pixmap().fill(_backgroundColor);
} }
void OnlineMap::emitLoaded() void OnlineMap::emitLoaded()
@ -87,91 +66,6 @@ void OnlineMap::emitLoaded()
emit loaded(); emit loaded();
} }
void OnlineMap::loadTilesAsync(QList<Tile> &list)
{
QList<Download> dl;
for (int i = 0; i < list.size(); i++) {
Tile &t = list[i];
QString file = tileFile(t);
QFileInfo fi(file);
if (!fi.exists()) {
fillTile(t);
dl.append(Download(tileUrl(t), file));
} else
loadTileFile(t, file);
}
if (!dl.empty())
downloader->get(dl);
}
void OnlineMap::loadTilesSync(QList<Tile> &list)
{
QList<Download> dl;
for (int i = 0; i < list.size(); i++) {
Tile &t = list[i];
QString file = tileFile(t);
QFileInfo fi(file);
if (!fi.exists())
dl.append(Download(tileUrl(t), file));
else
loadTileFile(t, file);
}
if (dl.empty())
return;
QEventLoop wait;
connect(downloader, SIGNAL(finished()), &wait, SLOT(quit()));
if (downloader->get(dl))
wait.exec();
for (int i = 0; i < list.size(); i++) {
Tile &t = list[i];
if (t.pixmap().isNull()) {
QString file = tileFile(t);
QFileInfo fi(file);
if (!(fi.exists() && loadTileFile(t, file)))
fillTile(t);
}
}
}
QString OnlineMap::tileUrl(const Tile &tile) const
{
QString url(_url);
url.replace("$z", QString::number(tile.zoom()));
url.replace("$x", QString::number(tile.xy().x()));
url.replace("$y", QString::number(tile.xy().y()));
return url;
}
QString OnlineMap::tileFile(const Tile &tile) const
{
QString file = TILES_DIR + QString("/%1/%2-%3-%4").arg(name())
.arg(tile.zoom()).arg(tile.xy().x()).arg(tile.xy().y());
return file;
}
void OnlineMap::clearCache()
{
QString path = TILES_DIR + QString("/") + name();
QDir dir = QDir(path);
QStringList list = dir.entryList();
for (int i = 0; i < list.count(); i++)
dir.remove(list.at(i));
}
QRectF OnlineMap::bounds() const QRectF OnlineMap::bounds() const
{ {
return QRectF(ll2xy(_bounds.topLeft()), ll2xy(_bounds.bottomRight())); return QRectF(ll2xy(_bounds.topLeft()), ll2xy(_bounds.bottomRight()));
@ -245,15 +139,19 @@ void OnlineMap::draw(QPainter *painter, const QRectF &rect)
tiles.append(Tile(QPoint(tile.x() + i, tile.y() + j), _zoom)); tiles.append(Tile(QPoint(tile.x() + i, tile.y() + j), _zoom));
if (_block) if (_block)
loadTilesSync(tiles); _tileLoader.loadTilesSync(tiles);
else else
loadTilesAsync(tiles); _tileLoader.loadTilesAsync(tiles);
for (int i = 0; i < tiles.count(); i++) { for (int i = 0; i < tiles.count(); i++) {
Tile &t = tiles[i]; Tile &t = tiles[i];
QPoint tp(tl.x() + (t.xy().x() - tile.x()) * TILE_SIZE, QPoint tp(tl.x() + (t.xy().x() - tile.x()) * TILE_SIZE,
tl.y() + (t.xy().y() - tile.y()) * TILE_SIZE); tl.y() + (t.xy().y() - tile.y()) * TILE_SIZE);
painter->drawPixmap(tp, t.pixmap()); if (t.pixmap().isNull())
painter->fillRect(QRect(tp, QSize(TILE_SIZE, TILE_SIZE)),
_backgroundColor);
else
painter->drawPixmap(tp, t.pixmap());
} }
} }

View File

@ -5,9 +5,7 @@
#include "common/range.h" #include "common/range.h"
#include "common/rectc.h" #include "common/rectc.h"
#include "map.h" #include "map.h"
#include "tile.h" #include "tileloader.h"
class Downloader;
class OnlineMap : public Map class OnlineMap : public Map
{ {
@ -36,10 +34,7 @@ public:
void draw(QPainter *painter, const QRectF &rect); void draw(QPainter *painter, const QRectF &rect);
void setBlockingMode(bool block) {_block = block;} void setBlockingMode(bool block) {_block = block;}
void clearCache(); void clearCache() {_tileLoader.clearCache();}
static void setDownloader(Downloader *downloader)
{OnlineMap::downloader = downloader;}
void load(); void load();
void unload(); void unload();
@ -50,22 +45,14 @@ private slots:
private: private:
QPointF ll2xy(const Coordinates &c) const; QPointF ll2xy(const Coordinates &c) const;
Coordinates xy2ll(const QPointF &p) const; Coordinates xy2ll(const QPointF &p) const;
void fillTile(Tile &tile);
QString tileUrl(const Tile &tile) const;
QString tileFile(const Tile &tile) const;
void loadTilesAsync(QList<Tile> &list);
void loadTilesSync(QList<Tile> &list);
int limitZoom(int zoom) const; int limitZoom(int zoom) const;
TileLoader _tileLoader;
QString _name; QString _name;
QString _url;
Range _zooms; Range _zooms;
RectC _bounds; RectC _bounds;
int _zoom; int _zoom;
bool _block; bool _block;
static Downloader *downloader;
}; };
#endif // ONLINEMAP_H #endif // ONLINEMAP_H

View File

@ -77,9 +77,14 @@ public:
Projection &operator=(const Projection &p); Projection &operator=(const Projection &p);
bool isNull() const {return (_gcs == 0 && _ct == 0 && _units.isNull());}
bool isValid() const {return (_gcs == 0 || _ct == 0 || _units.isNull());}
QPointF ll2xy(const Coordinates &c) const; QPointF ll2xy(const Coordinates &c) const;
Coordinates xy2ll(const QPointF &p) const; Coordinates xy2ll(const QPointF &p) const;
const LinearUnits &units() const {return _units;}
private: private:
const GCS * _gcs; const GCS * _gcs;
CT *_ct; CT *_ct;

106
src/map/tileloader.cpp Normal file
View File

@ -0,0 +1,106 @@
#include <QDir>
#include <QFileInfo>
#include <QEventLoop>
#include "downloader.h"
#include "tileloader.h"
static bool loadTileFile(Tile &tile, const QString &file)
{
if (!tile.pixmap().load(file)) {
qWarning("%s: error loading tile file\n", qPrintable(file));
return false;
}
return true;
}
Downloader *TileLoader::_downloader = 0;
TileLoader::TileLoader(const QString &url, const QString &dir)
: _url(url), _dir(dir)
{
if (!QDir().mkpath(_dir))
qWarning("Error creating tiles dir: %s\n", qPrintable(_dir));
}
void TileLoader::loadTilesAsync(QList<Tile> &list)
{
QList<Download> dl;
for (int i = 0; i < list.size(); i++) {
Tile &t = list[i];
QString file = tileFile(t);
QFileInfo fi(file);
if (!fi.exists())
dl.append(Download(tileUrl(t), file));
else
loadTileFile(t, file);
}
if (!dl.empty())
_downloader->get(dl);
}
void TileLoader::loadTilesSync(QList<Tile> &list)
{
QList<Download> dl;
for (int i = 0; i < list.size(); i++) {
Tile &t = list[i];
QString file = tileFile(t);
QFileInfo fi(file);
if (!fi.exists())
dl.append(Download(tileUrl(t), file));
else
loadTileFile(t, file);
}
if (dl.empty())
return;
QEventLoop wait;
QObject::connect(_downloader, SIGNAL(finished()), &wait, SLOT(quit()));
if (_downloader->get(dl))
wait.exec();
for (int i = 0; i < list.size(); i++) {
Tile &t = list[i];
if (t.pixmap().isNull()) {
QString file = tileFile(t);
if (QFileInfo(file).exists())
loadTileFile(t, file);
}
}
}
void TileLoader::clearCache()
{
QDir dir = QDir(_dir);
QStringList list = dir.entryList();
for (int i = 0; i < list.count(); i++)
dir.remove(list.at(i));
}
QString TileLoader::tileUrl(const Tile &tile) const
{
QString url(_url);
url.replace("$z", QString::number(tile.zoom()));
url.replace("$x", QString::number(tile.xy().x()));
url.replace("$y", QString::number(tile.xy().y()));
return url;
}
QString TileLoader::tileFile(const Tile &tile) const
{
QString file = _dir + QString("/%1-%2-%3").arg(tile.zoom())
.arg(tile.xy().x()).arg(tile.xy().y());
return file;
}

33
src/map/tileloader.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef TILELOADER_H
#define TILELOADER_H
#include <QString>
#include "tile.h"
#include "downloader.h"
class TileLoader
{
public:
TileLoader() {}
TileLoader(const QString &url, const QString &dir);
void loadTilesAsync(QList<Tile> &list);
void loadTilesSync(QList<Tile> &list);
void clearCache();
static Downloader *downloader() {return _downloader;}
static void setDownloader(Downloader *downloader)
{_downloader = downloader;}
private:
void fillTile(Tile &tile);
QString tileUrl(const Tile &tile) const;
QString tileFile(const Tile &tile) const;
QString _url;
QString _dir;
static Downloader *_downloader;
};
#endif // TILELOADER_Honlinemap

169
src/map/wmts.cpp Normal file
View File

@ -0,0 +1,169 @@
#include <QXmlStreamReader>
#include <QFile>
#include <QFileInfo>
#include <QEventLoop>
#include <QTextStream>
#include "downloader.h"
#include "pcs.h"
#include "wmts.h"
Downloader *WMTS::_downloader = 0;
bool WMTS::createProjection(const QString &crs)
{
QStringList list(crs.split(':'));
QString authority, code;
const PCS *pcs;
switch (list.size()) {
case 2:
authority = list.at(0);
code = list.at(1);
break;
case 7:
authority = list.at(4);
code = list.at(6);
break;
default:
return false;
}
if (authority != "EPSG")
return false;
if (!(pcs = PCS::pcs(code.toInt())))
return false;
_projection = Projection(pcs->gcs(), pcs->method(), pcs->setup(),
pcs->units());
return true;
}
void WMTS::tileMatrix(QXmlStreamReader &reader)
{
Zoom zoom;
while (reader.readNextStartElement()) {
if (reader.name() == "ScaleDenominator")
zoom.scaleDenominator = reader.readElementText().toDouble();
else if (reader.name() == "TopLeftCorner") {
QString str = reader.readElementText();
QTextStream(&str) >> zoom.topLeft.rx() >> zoom.topLeft.ry();
} else if (reader.name() == "TileWidth")
zoom.tile.setWidth(reader.readElementText().toInt());
else if (reader.name() == "TileHeight")
zoom.tile.setHeight(reader.readElementText().toInt());
else if (reader.name() == "MatrixWidth")
zoom.matrix.setWidth(reader.readElementText().toInt());
else if (reader.name() == "MatrixHeight")
zoom.matrix.setHeight(reader.readElementText().toInt());
else
reader.skipCurrentElement();
}
_zooms.append(zoom);
}
void WMTS::tileMatrixSet(QXmlStreamReader &reader, const QString &set)
{
QString id;
while (reader.readNextStartElement()) {
if (reader.name() == "Identifier")
id = reader.readElementText();
else if (reader.name() == "SupportedCRS" && id == set) {
if (!createProjection(reader.readElementText()))
reader.raiseError("Invalid/unknown CRS");
} else if (reader.name() == "TileMatrix" && id == set)
tileMatrix(reader);
else
reader.skipCurrentElement();
}
}
void WMTS::contents(QXmlStreamReader &reader, const QString &set)
{
while (reader.readNextStartElement()) {
if (reader.name() == "TileMatrixSet")
tileMatrixSet(reader, set);
else
reader.skipCurrentElement();
}
}
void WMTS::capabilities(QXmlStreamReader &reader, const QString &set)
{
while (reader.readNextStartElement()) {
if (reader.name() == "Contents")
contents(reader, set);
else
reader.skipCurrentElement();
}
}
bool WMTS::parseCapabilities(const QString &path, const QString &tileMatrixSet)
{
QFile file(path);
QXmlStreamReader reader;
if (!file.open(QFile::ReadOnly | QFile::Text)) {
_errorString = file.errorString();
return false;
}
reader.setDevice(&file);
if (reader.readNextStartElement()) {
if (reader.name() == "Capabilities")
capabilities(reader, tileMatrixSet);
else
reader.raiseError("Not a Capabilities XML file");
}
_errorString = reader.error() ? QString("%1:%2: %3").arg(path)
.arg(reader.lineNumber()).arg(reader.errorString()) : QString();
return reader.error() ? false : true;
}
bool WMTS::getCapabilities(const QString &url, const QString &file)
{
QList<Download> dl;
QString capabilitiesUrl = QString("%1?service=WMTS&Version=1.0.0"
"&request=GetCapabilities").arg(url);
dl.append(Download(capabilitiesUrl, file));
QEventLoop wait;
QObject::connect(_downloader, SIGNAL(finished()), &wait, SLOT(quit()));
if (_downloader->get(dl))
wait.exec();
if (QFileInfo(file).exists())
return true;
else {
_errorString = "Error downloading capabilities XML file";
return false;
}
}
bool WMTS::load(const QString &file, const QString &url,
const QString &tileMatrixSet)
{
if (!QFileInfo(file).exists())
if (!getCapabilities(url, file))
return false;
if (!parseCapabilities(file, tileMatrixSet))
return false;
if (_projection.isNull()) {
_errorString = "Missing CRS definition";
return false;
}
if (_zooms.isEmpty()) {
_errorString = "No tile matrix found";
return false;
}
return true;
}

50
src/map/wmts.h Normal file
View File

@ -0,0 +1,50 @@
#ifndef WMTS_H
#define WMTS_H
#include <QSize>
#include <QList>
#include "projection.h"
class QXmlStreamReader;
class Downloader;
class WMTS
{
public:
struct Zoom {
qreal scaleDenominator;
QPointF topLeft;
QSize tile;
QSize matrix;
};
bool load(const QString &path, const QString &url,
const QString &tileMatrixSet);
const QString &errorString() const {return _errorString;}
const QList<Zoom> &zooms() {return _zooms;}
const Projection &projection() {return _projection;}
static Downloader *downloader() {return _downloader;}
static void setDownloader(Downloader *downloader)
{_downloader = downloader;}
private:
bool createProjection(const QString &crs);
void tileMatrix(QXmlStreamReader &reader);
void tileMatrixSet(QXmlStreamReader &reader, const QString &set);
void contents(QXmlStreamReader &reader, const QString &set);
void capabilities(QXmlStreamReader &reader, const QString &set);
bool parseCapabilities(const QString &path, const QString &tileMatrixSet);
bool getCapabilities(const QString &url, const QString &file);
QList<Zoom> _zooms;
Projection _projection;
QString _errorString;
static Downloader *_downloader;
};
#endif // WMTS_H

203
src/map/wmtsmap.cpp Normal file
View File

@ -0,0 +1,203 @@
#include <QPainter>
#include "common/rectc.h"
#include "config.h"
#include "transform.h"
#include "wmts.h"
#include "wmtsmap.h"
#define CAPABILITIES_FILE "capabilities.xml"
WMTSMap::WMTSMap(const QString &name, const QString &url, const QString &format,
const QString &layer, const QString &style, const QString &tileMatrixSet,
QObject *parent) : Map(parent), _name(name), _url(url),
_tileMatrixSet(tileMatrixSet), _zoom(0), _valid(false)
{
QString dir(TILES_DIR + "/" + _name);
QString file = dir + "/" + CAPABILITIES_FILE;
QString tileUrl = QString("%1?service=WMTS&Version=1.0.0&request=GetTile"
"&Format=%2&Layer=%3&Style=%4&TileMatrixSet=%5&TileMatrix=$z&TileRow=$y"
"&TileCol=$x").arg(_url).arg(format).arg(layer).arg(style)
.arg(_tileMatrixSet);
_tileLoader = TileLoader(tileUrl, dir);
WMTS wmts;
if (!wmts.load(file, _url, _tileMatrixSet)) {
_errorString = wmts.errorString();
return;
}
_zooms = wmts.zooms();
_projection = wmts.projection();
updateTransform();
_block = false;
_valid = true;
}
qreal WMTSMap::sd2res(qreal scaleDenominator) const
{
return scaleDenominator * 0.28e-3 * _projection.units().fromMeters(1.0);
}
void WMTSMap::updateTransform()
{
const WMTS::Zoom &z = _zooms.at(_zoom);
ReferencePoint tl, br;
qreal pixelSpan = sd2res(z.scaleDenominator);
QPointF tileSpan(z.tile.width() * pixelSpan, z.tile.height() * pixelSpan);
QPointF bottomRight(z.topLeft.x() + tileSpan.x() * z.matrix.width(),
z.topLeft.y() - tileSpan.y() * z.matrix.height());
tl.xy = QPoint(0, 0);
tl.pp = z.topLeft;
br.xy = QPoint(z.tile.width() * z.matrix.width(),
z.tile.height() * z.matrix.height());
br.pp = bottomRight;
QList<ReferencePoint> points;
points << tl << br;
Transform tr(points);
_transform = tr.transform();
_inverted = _transform.inverted();
}
void WMTSMap::load()
{
connect(TileLoader::downloader(), SIGNAL(finished()), this,
SLOT(emitLoaded()));
}
void WMTSMap::unload()
{
disconnect(TileLoader::downloader(), SIGNAL(finished()), this,
SLOT(emitLoaded()));
}
void WMTSMap::clearCache()
{
QString dir(TILES_DIR + "/" + _name);
QString file = dir + "/" + CAPABILITIES_FILE;
_tileLoader.clearCache();
WMTS wmts;
if (!wmts.load(file, _url, _tileMatrixSet))
return;
_zooms = wmts.zooms();
_projection = wmts.projection();
if (_zoom >= _zooms.size())
_zoom = _zooms.size() - 1;
updateTransform();
}
void WMTSMap::emitLoaded()
{
emit loaded();
}
QRectF WMTSMap::bounds() const
{
const WMTS::Zoom &z = _zooms.at(_zoom);
return QRectF(QPointF(0, 0), QSize(z.tile.width() * z.matrix.width(),
z.tile.height() * z.matrix.height()));
}
qreal WMTSMap::zoomFit(const QSize &size, const RectC &br)
{
_zoom = 0;
if (br.isValid()) {
QRectF tbr(_projection.ll2xy(br.topLeft()),
_projection.ll2xy(br.bottomRight()));
QPointF sc(tbr.width() / size.width(), tbr.height() / size.height());
qreal resolution = qMax(qAbs(sc.x()), qAbs(sc.y()));
for (int i = 0; i < _zooms.size(); i++) {
if (sd2res(_zooms.at(i).scaleDenominator) < resolution)
break;
_zoom = i;
}
}
updateTransform();
return _zoom;
}
qreal WMTSMap::zoomFit(qreal resolution, const Coordinates &c)
{
Q_UNUSED(c);
_zoom = 0;
for (int i = 0; i < _zooms.size(); i++) {
if (sd2res(_zooms.at(i).scaleDenominator) < resolution)
break;
_zoom = i;
}
updateTransform();
return _zoom;
}
qreal WMTSMap::resolution(const QPointF &p) const
{
Q_UNUSED(p);
return sd2res(_zooms.at(_zoom).scaleDenominator);
}
qreal WMTSMap::zoomIn()
{
_zoom = qMin(_zoom + 1, _zooms.size() - 1);
updateTransform();
return _zoom;
}
qreal WMTSMap::zoomOut()
{
_zoom = qMax(_zoom - 1, 0);
updateTransform();
return _zoom;
}
void WMTSMap::draw(QPainter *painter, const QRectF &rect)
{
const WMTS::Zoom &z = _zooms.at(_zoom);
QPoint tl = QPoint((int)floor(rect.left() / (qreal)z.tile.width()),
(int)floor(rect.top() / (qreal)z.tile.height()));
QPoint br = QPoint((int)floor(rect.right() / (qreal)z.tile.width()),
(int)floor(rect.bottom() / (qreal)z.tile.height()));
QList<Tile> tiles;
for (int i = tl.x(); i <= br.x(); i++)
for (int j = tl.y(); j <= br.y(); j++)
tiles.append(Tile(QPoint(i, j), _zoom));
if (_block)
_tileLoader.loadTilesSync(tiles);
else
_tileLoader.loadTilesAsync(tiles);
for (int i = 0; i < tiles.count(); i++) {
Tile &t = tiles[i];
QPoint tp(t.xy().x() * z.tile.width(), t.xy().y() * z.tile.height());
if (t.pixmap().isNull())
painter->fillRect(QRect(tp, z.tile), _backgroundColor);
else
painter->drawPixmap(tp, t.pixmap());
}
}
QPointF WMTSMap::ll2xy(const Coordinates &c) const
{
return _transform.map(_projection.ll2xy(c));
}
Coordinates WMTSMap::xy2ll(const QPointF &p) const
{
return _projection.xy2ll(_inverted.map(p));
}

69
src/map/wmtsmap.h Normal file
View File

@ -0,0 +1,69 @@
#ifndef WMTSMAP_H
#define WMTSMAP_H
#include <QTransform>
#include "projection.h"
#include "map.h"
#include "wmts.h"
#include "tileloader.h"
class WMTSMap : public Map
{
Q_OBJECT
public:
WMTSMap(const QString &name, const QString &url, const QString &format,
const QString &layer, const QString &style, const QString &tileMatrixSet,
QObject *parent = 0);
const QString &name() const {return _name;}
QRectF bounds() const;
qreal resolution(const QPointF &p) const;
qreal zoom() const {return _zoom;}
qreal zoomFit(const QSize &size, const RectC &br);
qreal zoomFit(qreal resolution, const Coordinates &c);
qreal zoomIn();
qreal zoomOut();
QPointF ll2xy(const Coordinates &c)
{return static_cast<const WMTSMap &>(*this).ll2xy(c);}
Coordinates xy2ll(const QPointF &p)
{return static_cast<const WMTSMap &>(*this).xy2ll(p);}
void draw(QPainter *painter, const QRectF &rect);
void setBlockingMode(bool block) {_block = block;}
void clearCache();
void load();
void unload();
bool isValid() const {return _valid;}
QString errorString() const {return _errorString;}
private slots:
void emitLoaded();
private:
qreal sd2res(qreal scaleDenominator) const;
void updateTransform();
QPointF ll2xy(const Coordinates &c) const;
Coordinates xy2ll(const QPointF &p) const;
QString _name, _url, _tileMatrixSet;
TileLoader _tileLoader;
QList<WMTS::Zoom> _zooms;
Projection _projection;
QTransform _transform, _inverted;
int _zoom;
bool _block;
bool _valid;
QString _errorString;
};
#endif // WMTSMAP_H