diff --git a/gpxsee.pro b/gpxsee.pro index b5475f9e..6e9fb860 100644 --- a/gpxsee.pro +++ b/gpxsee.pro @@ -116,7 +116,10 @@ HEADERS += src/config.h \ src/map/primemeridian.h \ src/map/linearunits.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 \ src/common/coordinates.cpp \ src/common/rectc.cpp \ @@ -202,7 +205,10 @@ SOURCES += src/main.cpp \ src/map/angularunits.cpp \ src/map/primemeridian.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 TRANSLATIONS = lang/gpxsee_cs.ts \ lang/gpxsee_sv.ts \ diff --git a/src/GUI/app.cpp b/src/GUI/app.cpp index ed4e374a..7f95a2fc 100644 --- a/src/GUI/app.cpp +++ b/src/GUI/app.cpp @@ -4,7 +4,8 @@ #include #include #include -#include "map/onlinemap.h" +#include "map/wmts.h" +#include "map/tileloader.h" #include "map/downloader.h" #include "map/ellipsoid.h" #include "map/gcs.h" @@ -36,7 +37,9 @@ App::App(int &argc, char **argv) : QApplication(argc, argv), #endif // Q_OS_MAC QNetworkProxyFactory::setUseSystemConfiguration(true); - OnlineMap::setDownloader(new Downloader(this)); + Downloader *dl = new Downloader(this); + TileLoader::setDownloader(dl); + WMTS::setDownloader(dl); OPENGL_SET_SAMPLES(4); loadDatums(); loadPCSs(); diff --git a/src/map/atlas.h b/src/map/atlas.h index 6657aa3e..55d909da 100644 --- a/src/map/atlas.h +++ b/src/map/atlas.h @@ -33,7 +33,7 @@ public: void unload(); bool isValid() const {return _valid;} - const QString &errorString() const {return _errorString;} + QString errorString() const {return _errorString;} static bool isAtlas(const QString &path); diff --git a/src/map/downloader.cpp b/src/map/downloader.cpp index c6f91f0a..8a628c54 100644 --- a/src/map/downloader.cpp +++ b/src/map/downloader.cpp @@ -61,7 +61,7 @@ bool Downloader::saveToDisk(const QString &filename, QIODevice *data) QFile file(filename); 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())); return false; } @@ -80,11 +80,11 @@ void Downloader::downloadFinished(QNetworkReply *reply) QUrl origin = reply->request().attribute(ATTR_ORIGIN).toUrl(); if (origin.isEmpty()) { _errorDownloads.insert(url); - qWarning("Error downloading map tile: %s: %s\n", + qWarning("Error downloading file: %s: %s\n", url.toEncoded().constData(), qPrintable(reply->errorString())); } else { _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(), qPrintable(reply->errorString())); } @@ -99,11 +99,11 @@ void Downloader::downloadFinished(QNetworkReply *reply) if (location == url) { _errorDownloads.insert(url); - qWarning("Error downloading map tile: %s: " + qWarning("Error downloading file: %s: " "redirect loop\n", url.toEncoded().constData()); } else if (level >= MAX_REDIRECT_LEVEL) { _errorDownloads.insert(origin); - qWarning("Error downloading map tile: %s: " + qWarning("Error downloading file: %s: " "redirect level limit reached\n", origin.toEncoded().constData()); } else { diff --git a/src/map/map.h b/src/map/map.h index f6839878..9f72ba57 100644 --- a/src/map/map.h +++ b/src/map/map.h @@ -40,6 +40,9 @@ public: void setBackgroundColor(const QColor &color) {_backgroundColor = color;} + virtual bool isValid() const {return true;} + virtual QString errorString() const {return QString();} + signals: void loaded(); diff --git a/src/map/maplist.cpp b/src/map/maplist.cpp index 5cdae0be..ab5488d6 100644 --- a/src/map/maplist.cpp +++ b/src/map/maplist.cpp @@ -13,7 +13,7 @@ bool MapList::loadSource(const QString &path, bool dir) MapSource ms; Map *map; - if (!ms.loadFile(path, &map)) { + if (!(map = ms.loadFile(path))) { if (dir) _errorString += path + ": " + ms.errorString() + "\n"; else diff --git a/src/map/mapsource.cpp b/src/map/mapsource.cpp index 26a0da18..ffa3de86 100644 --- a/src/map/mapsource.cpp +++ b/src/map/mapsource.cpp @@ -1,6 +1,7 @@ #include #include #include "onlinemap.h" +#include "wmtsmap.h" #include "mapsource.h" @@ -121,13 +122,17 @@ RectC MapSource::bounds(QXmlStreamReader &reader) 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); RectC b(Coordinates(BOUNDS_LEFT, BOUNDS_TOP), Coordinates(BOUNDS_RIGHT, BOUNDS_BOTTOM)); + const QXmlStreamAttributes &attr = reader.attributes(); + bool wmts = (attr.hasAttribute("type") && attr.value("type") == "WMTS") + ? true : false; + while (reader.readNextStartElement()) { if (reader.name() == "name") name = reader.readElementText(); @@ -139,34 +144,53 @@ void MapSource::map(QXmlStreamReader &reader, Map **map) } else if (reader.name() == "bounds") { b = bounds(reader); 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(); } - *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); QXmlStreamReader reader; + Map *map = 0; if (!file.open(QFile::ReadOnly | QFile::Text)) { _errorString = file.errorString(); - return false; + return map; } reader.setDevice(&file); if (reader.readNextStartElement()) { if (reader.name() == "map") - MapSource::map(reader, map); + map = MapSource::map(reader); else reader.raiseError("Not an online map source file"); } - _errorString = reader.error() ? QString("%1: %2").arg(reader.lineNumber()) - .arg(reader.errorString()) : QString(); + if (!map) + _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; } diff --git a/src/map/mapsource.h b/src/map/mapsource.h index 24bade0c..92c800de 100644 --- a/src/map/mapsource.h +++ b/src/map/mapsource.h @@ -11,13 +11,13 @@ class QXmlStreamReader; class MapSource { public: - bool loadFile(const QString &path, Map **map); + Map *loadFile(const QString &path); const QString &errorString() const {return _errorString;} private: RectC bounds(QXmlStreamReader &reader); Range zooms(QXmlStreamReader &reader); - void map(QXmlStreamReader &reader, Map **map); + Map *map(QXmlStreamReader &reader); QString _errorString; }; diff --git a/src/map/offlinemap.h b/src/map/offlinemap.h index 73c81dac..901f101e 100644 --- a/src/map/offlinemap.h +++ b/src/map/offlinemap.h @@ -41,7 +41,7 @@ public: void unload(); bool isValid() const {return _valid;} - const QString &errorString() const {return _errorString;} + QString errorString() const {return _errorString;} QPointF ll2pp(const Coordinates &c) const {return _projection.ll2xy(c);} diff --git a/src/map/onlinemap.cpp b/src/map/onlinemap.cpp index 85080237..e20108e7 100644 --- a/src/map/onlinemap.cpp +++ b/src/map/onlinemap.cpp @@ -1,5 +1,3 @@ -#include -#include #include #include "common/coordinates.h" #include "common/rectc.h" @@ -41,45 +39,26 @@ static int scale2zoom(qreal scale) 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, 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; _zoom = _zooms.max(); - - QString path = TILES_DIR + QString("/") + name; - if (!QDir().mkpath(path)) - qWarning("Error creating tiles dir: %s\n", qPrintable(path)); + _tileLoader = TileLoader(url, TILES_DIR + "/" + name); } void OnlineMap::load() { - connect(downloader, SIGNAL(finished()), this, SLOT(emitLoaded())); + connect(TileLoader::downloader(), SIGNAL(finished()), this, + SLOT(emitLoaded())); } void OnlineMap::unload() { - disconnect(downloader, SIGNAL(finished()), this, SLOT(emitLoaded())); -} - -void OnlineMap::fillTile(Tile &tile) -{ - tile.pixmap() = QPixmap(TILE_SIZE, TILE_SIZE); - tile.pixmap().fill(_backgroundColor); + disconnect(TileLoader::downloader(), SIGNAL(finished()), this, + SLOT(emitLoaded())); } void OnlineMap::emitLoaded() @@ -87,91 +66,6 @@ void OnlineMap::emitLoaded() emit loaded(); } -void OnlineMap::loadTilesAsync(QList &list) -{ - QList 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 &list) -{ - QList 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 { 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)); if (_block) - loadTilesSync(tiles); + _tileLoader.loadTilesSync(tiles); else - loadTilesAsync(tiles); + _tileLoader.loadTilesAsync(tiles); for (int i = 0; i < tiles.count(); i++) { Tile &t = tiles[i]; QPoint tp(tl.x() + (t.xy().x() - tile.x()) * 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()); } } diff --git a/src/map/onlinemap.h b/src/map/onlinemap.h index 572ab6cb..33195b0c 100644 --- a/src/map/onlinemap.h +++ b/src/map/onlinemap.h @@ -5,9 +5,7 @@ #include "common/range.h" #include "common/rectc.h" #include "map.h" -#include "tile.h" - -class Downloader; +#include "tileloader.h" class OnlineMap : public Map { @@ -36,10 +34,7 @@ public: void draw(QPainter *painter, const QRectF &rect); void setBlockingMode(bool block) {_block = block;} - void clearCache(); - - static void setDownloader(Downloader *downloader) - {OnlineMap::downloader = downloader;} + void clearCache() {_tileLoader.clearCache();} void load(); void unload(); @@ -50,22 +45,14 @@ private slots: private: QPointF ll2xy(const Coordinates &c) 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 &list); - void loadTilesSync(QList &list); int limitZoom(int zoom) const; + TileLoader _tileLoader; QString _name; - QString _url; Range _zooms; RectC _bounds; int _zoom; bool _block; - - static Downloader *downloader; }; #endif // ONLINEMAP_H diff --git a/src/map/projection.h b/src/map/projection.h index 7241be77..6e5d5b3a 100644 --- a/src/map/projection.h +++ b/src/map/projection.h @@ -77,9 +77,14 @@ public: 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; Coordinates xy2ll(const QPointF &p) const; + const LinearUnits &units() const {return _units;} + private: const GCS * _gcs; CT *_ct; diff --git a/src/map/tileloader.cpp b/src/map/tileloader.cpp new file mode 100644 index 00000000..f3f547c6 --- /dev/null +++ b/src/map/tileloader.cpp @@ -0,0 +1,106 @@ +#include +#include +#include +#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 &list) +{ + QList 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 &list) +{ + QList 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; +} diff --git a/src/map/tileloader.h b/src/map/tileloader.h new file mode 100644 index 00000000..9906326c --- /dev/null +++ b/src/map/tileloader.h @@ -0,0 +1,33 @@ +#ifndef TILELOADER_H +#define TILELOADER_H + +#include +#include "tile.h" +#include "downloader.h" + +class TileLoader +{ +public: + TileLoader() {} + TileLoader(const QString &url, const QString &dir); + + void loadTilesAsync(QList &list); + void loadTilesSync(QList &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 diff --git a/src/map/wmts.cpp b/src/map/wmts.cpp new file mode 100644 index 00000000..77c26a0a --- /dev/null +++ b/src/map/wmts.cpp @@ -0,0 +1,169 @@ +#include +#include +#include +#include +#include +#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 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; +} diff --git a/src/map/wmts.h b/src/map/wmts.h new file mode 100644 index 00000000..e0172a9d --- /dev/null +++ b/src/map/wmts.h @@ -0,0 +1,50 @@ +#ifndef WMTS_H +#define WMTS_H + +#include +#include +#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 &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 _zooms; + Projection _projection; + + QString _errorString; + + static Downloader *_downloader; +}; + +#endif // WMTS_H diff --git a/src/map/wmtsmap.cpp b/src/map/wmtsmap.cpp new file mode 100644 index 00000000..1b030de0 --- /dev/null +++ b/src/map/wmtsmap.cpp @@ -0,0 +1,203 @@ +#include +#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 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 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)); +} diff --git a/src/map/wmtsmap.h b/src/map/wmtsmap.h new file mode 100644 index 00000000..8be82205 --- /dev/null +++ b/src/map/wmtsmap.h @@ -0,0 +1,69 @@ +#ifndef WMTSMAP_H +#define WMTSMAP_H + +#include +#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(*this).ll2xy(c);} + Coordinates xy2ll(const QPointF &p) + {return static_cast(*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 _zooms; + Projection _projection; + QTransform _transform, _inverted; + int _zoom; + bool _block; + + bool _valid; + QString _errorString; +}; + +#endif // WMTSMAP_H