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

Asynchronous WMS/WMTS map loading

(also fixes crash on OS X)
This commit is contained in:
Martin Tůma 2020-03-17 21:06:51 +01:00
parent 9ce6e16b60
commit 82c0c1f8a7
19 changed files with 520 additions and 445 deletions

View File

@ -20,6 +20,7 @@ equals(QT_MAJOR_VERSION, 5) : lessThan(QT_MINOR_VERSION, 4) {QT += opengl}
INCLUDEPATH += ./src INCLUDEPATH += ./src
HEADERS += src/common/config.h \ HEADERS += src/common/config.h \
src/GUI/graphicsscene.h \ src/GUI/graphicsscene.h \
src/GUI/mapaction.h \
src/GUI/popup.h \ src/GUI/popup.h \
src/common/garmin.h \ src/common/garmin.h \
src/common/staticassert.h \ src/common/staticassert.h \

View File

@ -51,6 +51,7 @@
#include "graphtab.h" #include "graphtab.h"
#include "graphitem.h" #include "graphitem.h"
#include "pathitem.h" #include "pathitem.h"
#include "mapaction.h"
#include "gui.h" #include "gui.h"
@ -58,7 +59,6 @@
GUI::GUI() GUI::GUI()
{ {
loadMaps();
loadPOIs(); loadPOIs();
createMapView(); createMapView();
@ -106,24 +106,13 @@ GUI::GUI()
updateStatusBarInfo(); updateStatusBarInfo();
} }
void GUI::loadMaps()
{
_ml = new MapList(this);
QString mapDir(ProgramPaths::mapDir());
if (!mapDir.isNull() && !_ml->loadDir(mapDir))
qWarning("%s", qPrintable(_ml->errorPath() + ": " + _ml->errorString()));
_map = new EmptyMap(this);
}
void GUI::loadPOIs() void GUI::loadPOIs()
{ {
_poi = new POI(this); _poi = new POI(this);
QString poiDir(ProgramPaths::poiDir());
if (!poiDir.isNull() && !_poi->loadDir(poiDir)) QString poiDir(ProgramPaths::poiDir());
qWarning("%s", qPrintable(_poi->errorString())); if (!poiDir.isNull())
_poi->loadDir(poiDir);
} }
void GUI::createBrowser() void GUI::createBrowser()
@ -134,40 +123,56 @@ void GUI::createBrowser()
void GUI::createMapActions() void GUI::createMapActions()
{ {
_mapsSignalMapper = new QSignalMapper(this);
_mapsActionGroup = new QActionGroup(this); _mapsActionGroup = new QActionGroup(this);
_mapsActionGroup->setExclusive(true); _mapsActionGroup->setExclusive(true);
for (int i = 0; i < _ml->maps().count(); i++) QString mapDir(ProgramPaths::mapDir());
createMapAction(_ml->maps().at(i)); if (mapDir.isNull())
return;
connect(_mapsSignalMapper, SIGNAL(mapped(int)), this, QString unused;
SLOT(mapChanged(int))); QList<Map*> maps(MapList::loadMaps(mapDir, unused));
for (int i = 0; i < maps.count(); i++) {
MapAction *a = createMapAction(maps.at(i));
connect(a, SIGNAL(loaded()), this, SLOT(mapInitialized()));
}
} }
QAction *GUI::createMapAction(const Map *map) MapAction *GUI::createMapAction(Map *map)
{ {
QAction *a = new QAction(map->name(), this); MapAction *a = new MapAction(map);
a->setMenuRole(QAction::NoRole); a->setMenuRole(QAction::NoRole);
a->setCheckable(true); a->setCheckable(true);
a->setActionGroup(_mapsActionGroup); a->setActionGroup(_mapsActionGroup);
connect(a, SIGNAL(triggered()), this, SLOT(mapChanged()));
_mapActions.append(a);
_mapsSignalMapper->setMapping(a, _mapActions.size() - 1);
connect(a, SIGNAL(triggered()), _mapsSignalMapper, SLOT(map()));
return a; return a;
} }
void GUI::mapInitialized()
{
MapAction *action = static_cast<MapAction*>(QObject::sender());
Map *map = action->data().value<Map*>();
if (map->isValid()) {
if (!_mapsActionGroup->checkedAction())
action->trigger();
_showMapAction->setEnabled(true);
_clearMapCacheAction->setEnabled(true);
} else {
qWarning(qPrintable(map->name() + ": " + map->errorString()));
action->deleteLater();
}
}
void GUI::createPOIFilesActions() void GUI::createPOIFilesActions()
{ {
_poiFilesSignalMapper = new QSignalMapper(this); _poiFilesSignalMapper = new QSignalMapper(this);
connect(_poiFilesSignalMapper, SIGNAL(mapped(int)), this,
SLOT(poiFileChecked(int)));
for (int i = 0; i < _poi->files().count(); i++) for (int i = 0; i < _poi->files().count(); i++)
createPOIFileAction(_poi->files().at(i)); createPOIFileAction(_poi->files().at(i));
connect(_poiFilesSignalMapper, SIGNAL(mapped(int)), this,
SLOT(poiFileChecked(int)));
} }
QAction *GUI::createPOIFileAction(const QString &fileName) QAction *GUI::createPOIFileAction(const QString &fileName)
@ -281,8 +286,10 @@ void GUI::createActions()
createPOIFilesActions(); createPOIFilesActions();
// Map actions // Map actions
createMapActions();
_showMapAction = new QAction(QIcon(SHOW_MAP_ICON), tr("Show map"), _showMapAction = new QAction(QIcon(SHOW_MAP_ICON), tr("Show map"),
this); this);
_showMapAction->setEnabled(false);
_showMapAction->setMenuRole(QAction::NoRole); _showMapAction->setMenuRole(QAction::NoRole);
_showMapAction->setCheckable(true); _showMapAction->setCheckable(true);
_showMapAction->setShortcut(SHOW_MAP_SHORTCUT); _showMapAction->setShortcut(SHOW_MAP_SHORTCUT);
@ -294,10 +301,10 @@ void GUI::createActions()
_loadMapAction->setMenuRole(QAction::NoRole); _loadMapAction->setMenuRole(QAction::NoRole);
connect(_loadMapAction, SIGNAL(triggered()), this, SLOT(loadMap())); connect(_loadMapAction, SIGNAL(triggered()), this, SLOT(loadMap()));
_clearMapCacheAction = new QAction(tr("Clear tile cache"), this); _clearMapCacheAction = new QAction(tr("Clear tile cache"), this);
_clearMapCacheAction->setEnabled(false);
_clearMapCacheAction->setMenuRole(QAction::NoRole); _clearMapCacheAction->setMenuRole(QAction::NoRole);
connect(_clearMapCacheAction, SIGNAL(triggered()), _mapView, connect(_clearMapCacheAction, SIGNAL(triggered()), _mapView,
SLOT(clearMapCache())); SLOT(clearMapCache()));
createMapActions();
_nextMapAction = new QAction(tr("Next map"), this); _nextMapAction = new QAction(tr("Next map"), this);
_nextMapAction->setMenuRole(QAction::NoRole); _nextMapAction->setMenuRole(QAction::NoRole);
_nextMapAction->setShortcut(NEXT_MAP_SHORTCUT); _nextMapAction->setShortcut(NEXT_MAP_SHORTCUT);
@ -308,10 +315,6 @@ void GUI::createActions()
_prevMapAction->setShortcut(PREV_MAP_SHORTCUT); _prevMapAction->setShortcut(PREV_MAP_SHORTCUT);
connect(_prevMapAction, SIGNAL(triggered()), this, SLOT(prevMap())); connect(_prevMapAction, SIGNAL(triggered()), this, SLOT(prevMap()));
addAction(_prevMapAction); addAction(_prevMapAction);
if (_ml->maps().isEmpty()) {
_showMapAction->setEnabled(false);
_clearMapCacheAction->setEnabled(false);
}
_showCoordinatesAction = new QAction(tr("Show cursor coordinates"), this); _showCoordinatesAction = new QAction(tr("Show cursor coordinates"), this);
_showCoordinatesAction->setMenuRole(QAction::NoRole); _showCoordinatesAction->setMenuRole(QAction::NoRole);
_showCoordinatesAction->setCheckable(true); _showCoordinatesAction->setCheckable(true);
@ -506,7 +509,7 @@ void GUI::createMenus()
#endif // Q_OS_MAC #endif // Q_OS_MAC
_mapMenu = menuBar()->addMenu(tr("&Map")); _mapMenu = menuBar()->addMenu(tr("&Map"));
_mapMenu->addActions(_mapActions); _mapMenu->addActions(_mapsActionGroup->actions());
_mapsEnd = _mapMenu->addSeparator(); _mapsEnd = _mapMenu->addSeparator();
_mapMenu->addAction(_loadMapAction); _mapMenu->addAction(_loadMapAction);
_mapMenu->addAction(_clearMapCacheAction); _mapMenu->addAction(_clearMapCacheAction);
@ -608,6 +611,7 @@ void GUI::createToolBars()
void GUI::createMapView() void GUI::createMapView()
{ {
_map = new EmptyMap(this);
_mapView = new MapView(_map, _poi, this); _mapView = new MapView(_map, _poi, this);
_mapView->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, _mapView->setSizePolicy(QSizePolicy(QSizePolicy::Ignored,
QSizePolicy::Expanding)); QSizePolicy::Expanding));
@ -1322,25 +1326,49 @@ void GUI::loadMap()
bool GUI::loadMap(const QString &fileName) bool GUI::loadMap(const QString &fileName)
{ {
// On OS X fileName may be a directory!
if (fileName.isEmpty()) if (fileName.isEmpty())
return false; return false;
QFileInfo fi(fileName); QString error;
bool res = fi.isDir() ? _ml->loadDir(fileName) : _ml->loadFile(fileName); QList<Map*> maps = MapList::loadMaps(fileName, error);
if (maps.isEmpty()) {
error = tr("Error loading map:") + "\n\n"
+ fileName + "\n\n" + error;
QMessageBox::critical(this, APP_NAME, error);
return false;
}
if (res) { for (int i = 0; i < maps.size(); i++) {
QAction *a = createMapAction(_ml->maps().last()); Map *map = maps.at(i);
MapAction *a = createMapAction(map);
_mapMenu->insertAction(_mapsEnd, a); _mapMenu->insertAction(_mapsEnd, a);
if (map->isReady()) {
a->trigger();
_showMapAction->setEnabled(true); _showMapAction->setEnabled(true);
_clearMapCacheAction->setEnabled(true); _clearMapCacheAction->setEnabled(true);
a->trigger(); } else
connect(a, SIGNAL(loaded()), this, SLOT(mapLoaded()));
}
return true; return true;
}
void GUI::mapLoaded()
{
MapAction *action = static_cast<MapAction*>(QObject::sender());
Map *map = action->data().value<Map*>();
if (map->isValid()) {
action->trigger();
_showMapAction->setEnabled(true);
_clearMapCacheAction->setEnabled(true);
} else { } else {
QString error = tr("Error loading map:") + "\n\n" QString error = tr("Error loading map:") + "\n\n"
+ fileName + "\n\n" + _ml->errorString(); + map->name() + "\n\n" + map->errorString();
QMessageBox::critical(this, APP_NAME, error); QMessageBox::critical(this, APP_NAME, error);
action->deleteLater();
return false;
} }
} }
@ -1382,31 +1410,42 @@ void GUI::updateWindowTitle()
setWindowTitle(APP_NAME); setWindowTitle(APP_NAME);
} }
void GUI::mapChanged(int index) void GUI::mapChanged()
{ {
_map = _ml->maps().at(index); _map = _mapsActionGroup->checkedAction()->data().value<Map*>();
_mapView->setMap(_map); _mapView->setMap(_map);
} }
void GUI::nextMap() void GUI::nextMap()
{ {
if (_ml->maps().count() < 2) QAction *checked = _mapsActionGroup->checkedAction();
if (!checked)
return; return;
int next = (_ml->maps().indexOf(_map) + 1) % _ml->maps().count(); QList<QAction*> maps = _mapsActionGroup->actions();
_mapActions.at(next)->setChecked(true); for (int i = 1; i < maps.size(); i++) {
mapChanged(next); int next = (maps.indexOf(checked) + i) % maps.count();
if (maps.at(next)->isEnabled()) {
maps.at(next)->trigger();
break;
}
}
} }
void GUI::prevMap() void GUI::prevMap()
{ {
if (_ml->maps().count() < 2) QAction *checked = _mapsActionGroup->checkedAction();
if (!checked)
return; return;
int prev = (_ml->maps().indexOf(_map) + _ml->maps().count() - 1) QList<QAction*> maps = _mapsActionGroup->actions();
% _ml->maps().count(); for (int i = 1; i < maps.size(); i++) {
_mapActions.at(prev)->setChecked(true); int prev = (maps.indexOf(checked) + maps.count() - i) % maps.count();
mapChanged(prev); if (maps.at(prev)->isEnabled()) {
maps.at(prev)->trigger();
break;
}
}
} }
void GUI::poiFileChecked(int index) void GUI::poiFileChecked(int index)
@ -1899,9 +1938,11 @@ void GUI::readSettings()
_showMapAction->setChecked(true); _showMapAction->setChecked(true);
else else
_mapView->showMap(false); _mapView->showMap(false);
if (_ml->maps().count()) { QAction *ma = mapAction(settings.value(CURRENT_MAP_SETTING).toString());
int index = mapIndex(settings.value(CURRENT_MAP_SETTING).toString()); if (ma) {
_mapActions.at(index)->trigger(); ma->trigger();
_showMapAction->setEnabled(true);
_clearMapCacheAction->setEnabled(true);
} }
if (settings.value(SHOW_COORDINATES_SETTING, SHOW_COORDINATES_DEFAULT) if (settings.value(SHOW_COORDINATES_SETTING, SHOW_COORDINATES_DEFAULT)
.toBool()) { .toBool()) {
@ -2175,11 +2216,23 @@ void GUI::readSettings()
settings.endGroup(); settings.endGroup();
} }
int GUI::mapIndex(const QString &name) QAction *GUI::mapAction(const QString &name)
{ {
for (int i = 0; i < _ml->maps().count(); i++) QList<QAction *> maps = _mapsActionGroup->actions();
if (_ml->maps().at(i)->name() == name)
return i; // Last map
for (int i = 0; i < maps.count(); i++) {
Map *map = maps.at(i)->data().value<Map*>();
if (map->name() == name && map->isReady())
return maps.at(i);
}
// Any usable map
for (int i = 0; i < maps.count(); i++) {
Map *map = maps.at(i)->data().value<Map*>();
if (map->isReady())
return maps.at(i);
}
return 0; return 0;
} }

View File

@ -29,6 +29,7 @@ class Map;
class MapList; class MapList;
class POI; class POI;
class QScreen; class QScreen;
class MapAction;
class GUI : public QMainWindow class GUI : public QMainWindow
{ {
@ -64,7 +65,7 @@ private slots:
void prevMap(); void prevMap();
void openOptions(); void openOptions();
void mapChanged(int); void mapChanged();
void graphChanged(int); void graphChanged(int);
void poiFileChecked(int); void poiFileChecked(int);
@ -88,16 +89,18 @@ private slots:
void screenChanged(QScreen *screen); void screenChanged(QScreen *screen);
void logicalDotsPerInchChanged(qreal dpi); void logicalDotsPerInchChanged(qreal dpi);
void mapLoaded();
void mapInitialized();
private: private:
typedef QPair<QDate, QDate> DateRange; typedef QPair<QDate, QDate> DateRange;
void loadMaps();
void loadPOIs(); void loadPOIs();
void closeFiles(); void closeFiles();
void plot(QPrinter *printer); void plot(QPrinter *printer);
QAction *createPOIFileAction(const QString &fileName); QAction *createPOIFileAction(const QString &fileName);
QAction *createMapAction(const Map *map); MapAction *createMapAction(Map *map);
void createPOIFilesActions(); void createPOIFilesActions();
void createMapActions(); void createMapActions();
void createActions(); void createActions();
@ -127,7 +130,7 @@ private:
qreal distance() const; qreal distance() const;
qreal time() const; qreal time() const;
qreal movingTime() const; qreal movingTime() const;
int mapIndex(const QString &name); QAction *mapAction(const QString &name);
void readSettings(); void readSettings();
void writeSettings(); void writeSettings();
@ -196,11 +199,9 @@ private:
QAction *_showCoordinatesAction; QAction *_showCoordinatesAction;
QAction *_openOptionsAction; QAction *_openOptionsAction;
QAction *_mapsEnd; QAction *_mapsEnd;
QList<QAction*> _mapActions;
QList<QAction*> _poiFilesActions;
QList<QAction*> _poiFilesActions;
QSignalMapper *_poiFilesSignalMapper; QSignalMapper *_poiFilesSignalMapper;
QSignalMapper *_mapsSignalMapper;
QLabel *_fileNameLabel; QLabel *_fileNameLabel;
QLabel *_distanceLabel; QLabel *_distanceLabel;

32
src/GUI/mapaction.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef MAPACTION_H
#define MAPACTION_H
#include <QAction>
#include "map/map.h"
class MapAction : public QAction
{
Q_OBJECT
public:
MapAction(Map *map, QObject *parent = 0) : QAction(map->name(), parent)
{
map->setParent(this);
setData(QVariant::fromValue(map));
setEnabled(map->isValid());
connect(map, SIGNAL(mapLoaded()), this, SLOT(mapLoaded()));
}
signals:
void loaded();
private slots:
void mapLoaded()
{
Map *map = data().value<Map*>();
setEnabled(map->isValid());
emit loaded();
}
};
#endif // MAPACTION_H

View File

@ -54,7 +54,7 @@ MapView::MapView(Map *map, POI *poi, QWidget *parent)
_map = map; _map = map;
_map->load(); _map->load();
_map->setProjection(_projection); _map->setProjection(_projection);
connect(_map, SIGNAL(loaded()), this, SLOT(reloadMap())); connect(_map, SIGNAL(tilesLoaded()), this, SLOT(reloadMap()));
_poi = poi; _poi = poi;
connect(_poi, SIGNAL(pointsChanged()), this, SLOT(updatePOI())); connect(_poi, SIGNAL(pointsChanged()), this, SLOT(updatePOI()));
@ -316,7 +316,7 @@ void MapView::setMap(Map *map)
RectC cr(_map->xy2ll(vr.topLeft()), _map->xy2ll(vr.bottomRight())); RectC cr(_map->xy2ll(vr.topLeft()), _map->xy2ll(vr.bottomRight()));
_map->unload(); _map->unload();
disconnect(_map, SIGNAL(loaded()), this, SLOT(reloadMap())); disconnect(_map, SIGNAL(tilesLoaded()), this, SLOT(reloadMap()));
_map = map; _map = map;
_map->load(); _map->load();
@ -324,7 +324,7 @@ void MapView::setMap(Map *map)
#ifdef ENABLE_HIDPI #ifdef ENABLE_HIDPI
_map->setDevicePixelRatio(_deviceRatio, _mapRatio); _map->setDevicePixelRatio(_deviceRatio, _mapRatio);
#endif // ENABLE_HIDPI #endif // ENABLE_HIDPI
connect(_map, SIGNAL(loaded()), this, SLOT(reloadMap())); connect(_map, SIGNAL(tilesLoaded()), this, SLOT(reloadMap()));
digitalZoom(0); digitalZoom(0);

View File

@ -17,7 +17,7 @@ POI::POI(QObject *parent) : QObject(parent)
_useDEM = false; _useDEM = false;
} }
bool POI::loadFile(const QString &path, bool dir) bool POI::loadFile(const QString &path)
{ {
Data data(path, true); Data data(path, true);
FileIndex index; FileIndex index;
@ -26,16 +26,8 @@ bool POI::loadFile(const QString &path, bool dir)
index.start = _data.size(); index.start = _data.size();
if (!data.isValid()) { if (!data.isValid()) {
if (dir) {
if (data.errorLine())
_errorString += QString("%1:%2: %3\n").arg(path)
.arg(data.errorLine()).arg(data.errorString());
else
_errorString += path + ": " + data.errorString() + "\n";
} else {
_errorString = data.errorString(); _errorString = data.errorString();
_errorLine = data.errorLine(); _errorLine = data.errorLine();
}
return false; return false;
} }
@ -59,37 +51,22 @@ bool POI::loadFile(const QString &path, bool dir)
return true; return true;
} }
bool POI::loadFile(const QString &path) void POI::loadDir(const QString &path)
{
_errorString.clear();
_errorLine = 0;
return loadFile(path, false);
}
bool POI::loadDir(const QString &path)
{ {
QDir md(path); QDir md(path);
md.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); md.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
QFileInfoList fl = md.entryInfoList(); QFileInfoList fl = md.entryInfoList();
bool ret = true;
_errorString.clear();
_errorLine = 0;
for (int i = 0; i < fl.size(); i++) { for (int i = 0; i < fl.size(); i++) {
const QFileInfo &fi = fl.at(i); const QFileInfo &fi = fl.at(i);
if (fi.isDir()) { if (fi.isDir())
if (!loadDir(fi.absoluteFilePath())) loadDir(fi.absoluteFilePath());
ret = false; else
} else { if (!loadFile(fi.absoluteFilePath()))
if (!loadFile(fi.absoluteFilePath(), true)) qWarning(qPrintable(fi.absoluteFilePath() + ": "
ret = false; + _errorString));
} }
}
return ret;
} }
static bool cb(size_t data, void* context) static bool cb(size_t data, void* context)

View File

@ -20,7 +20,7 @@ public:
POI(QObject *parent = 0); POI(QObject *parent = 0);
bool loadFile(const QString &path); bool loadFile(const QString &path);
bool loadDir(const QString &path); void loadDir(const QString &path);
const QString &errorString() const {return _errorString;} const QString &errorString() const {return _errorString;}
int errorLine() const {return _errorLine;} int errorLine() const {return _errorLine;}

View File

@ -50,12 +50,15 @@ public:
virtual void setProjection(const Projection &) {} virtual void setProjection(const Projection &) {}
virtual bool isValid() const {return true;} virtual bool isValid() const {return true;}
virtual bool isReady() const {return true;}
virtual QString errorString() const {return QString();} virtual QString errorString() const {return QString();}
signals: signals:
void loaded(); void tilesLoaded();
void mapLoaded();
}; };
Q_DECLARE_METATYPE(Map*)
Q_DECLARE_OPERATORS_FOR_FLAGS(Map::Flags) Q_DECLARE_OPERATORS_FOR_FLAGS(Map::Flags)
#endif // MAP_H #endif // MAP_H

View File

@ -1,5 +1,6 @@
#include <QFileInfo> #include <QFileInfo>
#include <QDir> #include <QDir>
#include <QApplication>
#include "atlas.h" #include "atlas.h"
#include "ozimap.h" #include "ozimap.h"
#include "jnxmap.h" #include "jnxmap.h"
@ -12,31 +13,8 @@
#include "maplist.h" #include "maplist.h"
bool MapList::loadMap(Map *map, const QString &path) Map *MapList::loadFile(const QString &path, QString &errorString,
{ bool *terminate)
if (map && map->isValid()) {
_maps.append(map);
return true;
} else {
_errorPath = path;
_errorString = (map) ? map->errorString() : "Unknown file format";
return false;
}
}
Map *MapList::loadSource(const QString &path)
{
Map *map = MapSource::loadMap(path, _errorString);
if (!map)
_errorPath = path;
else
map->setParent(this);
return map;
}
bool MapList::loadFile(const QString &path, bool *terminate)
{ {
QFileInfo fi(path); QFileInfo fi(path);
QString suffix = fi.suffix().toLower(); QString suffix = fi.suffix().toLower();
@ -45,75 +23,94 @@ bool MapList::loadFile(const QString &path, bool *terminate)
if (Atlas::isAtlas(path)) { if (Atlas::isAtlas(path)) {
if (terminate) if (terminate)
*terminate = true; *terminate = true;
map = new Atlas(path, this); map = new Atlas(path);
} else if (suffix == "xml") { } else if (suffix == "xml") {
if (MapSource::isMap(path) && !(map = loadSource(path))) if (MapSource::isMap(path)) {
return false; if (!(map = MapSource::loadMap(path, errorString)))
else if (GMAP::isGMAP(path)) { return 0;
} else if (GMAP::isGMAP(path)) {
if (terminate) if (terminate)
*terminate = true; *terminate = true;
map = new IMGMap(path, this); map = new IMGMap(path);
} }
} else if (suffix == "jnx") } else if (suffix == "jnx")
map = new JNXMap(path, this); map = new JNXMap(path);
else if (suffix == "tif" || suffix == "tiff") else if (suffix == "tif" || suffix == "tiff")
map = new GeoTIFFMap(path, this); map = new GeoTIFFMap(path);
else if (suffix == "mbtiles") else if (suffix == "mbtiles")
map = new MBTilesMap(path, this); map = new MBTilesMap(path);
else if (suffix == "rmap" || suffix == "rtmap") else if (suffix == "rmap" || suffix == "rtmap")
map = new RMap(path, this); map = new RMap(path);
else if (suffix == "img") else if (suffix == "img")
map = new IMGMap(path, this); map = new IMGMap(path);
else if (suffix == "map" || suffix == "tar") else if (suffix == "map" || suffix == "tar")
map = new OziMap(path, this); map = new OziMap(path);
if (!loadMap(map, path)) { if (map && map->isValid())
return map;
else {
errorString = (map) ? map->errorString() : "Unknown file format";
delete map; delete map;
return false; return 0;
} }
return true;
} }
bool MapList::loadDir(const QString &path) QList<Map*> MapList::loadDir(const QString &path, QString &errorString)
{ {
QDir md(path); QDir md(path);
md.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); md.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
md.setSorting(QDir::DirsLast); md.setSorting(QDir::DirsLast);
QFileInfoList ml = md.entryInfoList(); QFileInfoList ml = md.entryInfoList();
bool ret = true; QList<Map*> list;
for (int i = 0; i < ml.size(); i++) { for (int i = 0; i < ml.size(); i++) {
const QFileInfo &fi = ml.at(i); const QFileInfo &fi = ml.at(i);
QString suffix = fi.suffix().toLower(); QString suffix = fi.suffix().toLower();
bool terminate = false; bool terminate = false;
if (fi.isDir() && fi.fileName() != "set") { if (fi.isDir() && fi.fileName() != "set")
if (!loadDir(fi.absoluteFilePath())) list.append(loadDir(fi.absoluteFilePath(), errorString));
ret = false; else if (filter().contains("*." + suffix)) {
} else if (filter().contains("*." + suffix)) { Map *map = loadFile(fi.absoluteFilePath(), errorString, &terminate);
if (!loadFile(fi.absoluteFilePath(), &terminate)) if (map)
ret = false; list.append(map);
else
qWarning(qPrintable(path + ": " + errorString));
if (terminate) if (terminate)
break; break;
} }
} }
return ret; return list;
}
QList<Map*> MapList::loadMaps(const QString &path, QString &errorString)
{
if (QFileInfo(path).isDir())
return loadDir(path, errorString);
else {
QList<Map*> list;
Map *map = loadFile(path, errorString, 0);
if (map)
list.append(map);
return list;
}
} }
QString MapList::formats() QString MapList::formats()
{ {
return return
tr("Supported files") + " (" + filter().join(" ") + ");;" qApp->translate("MapList", "Supported files")
+ tr("Garmin IMG maps") + " (*.gmap *.gmapi *.img *.xml);;" + " (" + filter().join(" ") + ");;"
+ tr("Garmin JNX maps") + " (*.jnx);;" + qApp->translate("MapList", "Garmin IMG maps")
+ tr("OziExplorer maps") + " (*.map);;" + " (*.gmap *.gmapi *.img *.xml);;"
+ tr("MBTiles maps") + " (*.mbtiles);;" + qApp->translate("MapList", "Garmin JNX maps") + " (*.jnx);;"
+ tr("TrekBuddy maps/atlases") + " (*.tar *.tba);;" + qApp->translate("MapList", "OziExplorer maps") + " (*.map);;"
+ tr("GeoTIFF images") + " (*.tif *.tiff);;" + qApp->translate("MapList", "MBTiles maps") + " (*.mbtiles);;"
+ tr("TwoNav maps") + " (*.rmap *.rtmap);;" + qApp->translate("MapList", "TrekBuddy maps/atlases") + " (*.tar *.tba);;"
+ tr("Online map sources") + " (*.xml)"; + qApp->translate("MapList", "GeoTIFF images") + " (*.tif *.tiff);;"
+ qApp->translate("MapList", "TwoNav maps") + " (*.rmap *.rtmap);;"
+ qApp->translate("MapList", "Online map sources") + " (*.xml)";
} }
QStringList MapList::filter() QStringList MapList::filter()

View File

@ -1,36 +1,21 @@
#ifndef MAPLIST_H #ifndef MAPLIST_H
#define MAPLIST_H #define MAPLIST_H
#include <QObject>
#include <QString> #include <QString>
class Map; class Map;
class MapList : public QObject class MapList
{ {
Q_OBJECT
public: public:
MapList(QObject *parent = 0) : QObject(parent) {} static QList<Map*> loadMaps(const QString &path, QString &errorString);
bool loadFile(const QString &path, bool *terminate = 0);
bool loadDir(const QString &path);
const QList<Map*> &maps() const {return _maps;}
const QString &errorString() const {return _errorString;}
const QString &errorPath() const {return _errorPath;}
static QString formats(); static QString formats();
static QStringList filter(); static QStringList filter();
private: private:
Map *loadSource(const QString &path); static Map *loadFile(const QString &path, QString &errorString,
bool loadMap(Map *map, const QString &path); bool *terminate);
static QList<Map*> loadDir(const QString &path, QString &errorString);
QList<Map*> _maps;
QString _errorString;
QString _errorPath;
}; };
#endif // MAPLIST_H #endif // MAPLIST_H

View File

@ -21,7 +21,7 @@ OnlineMap::OnlineMap(const QString &name, const QString &url,
_tileLoader->setUrl(url); _tileLoader->setUrl(url);
_tileLoader->setAuthorization(authorization); _tileLoader->setAuthorization(authorization);
_tileLoader->setQuadTiles(quadTiles); _tileLoader->setQuadTiles(quadTiles);
connect(_tileLoader, SIGNAL(finished()), this, SIGNAL(loaded())); connect(_tileLoader, SIGNAL(finished()), this, SIGNAL(tilesLoaded()));
} }
QRectF OnlineMap::bounds() QRectF OnlineMap::bounds()

View File

@ -218,10 +218,10 @@ void WMS::capabilities(QXmlStreamReader &reader, CTX &ctx)
} }
} }
bool WMS::parseCapabilities(const QString &path, const Setup &setup) bool WMS::parseCapabilities()
{ {
QFile file(path); QFile file(_path);
CTX ctx(setup); CTX ctx(_setup);
QXmlStreamReader reader; QXmlStreamReader reader;
@ -244,7 +244,7 @@ bool WMS::parseCapabilities(const QString &path, const Setup &setup)
reader.raiseError("Not a WMS Capabilities XML file"); reader.raiseError("Not a WMS Capabilities XML file");
} }
if (reader.error()) { if (reader.error()) {
_errorString = QString("%1:%2: %3").arg(path).arg(reader.lineNumber()) _errorString = QString("%1:%2: %3").arg(_path).arg(reader.lineNumber())
.arg(reader.errorString()); .arg(reader.errorString());
return false; return false;
} }
@ -290,10 +290,10 @@ bool WMS::parseCapabilities(const QString &path, const Setup &setup)
return false; return false;
} }
_boundingBox = ctx.layers.first().boundingBox; _bbox = ctx.layers.first().boundingBox;
for (int i = 1; i < ctx.layers.size(); i++) for (int i = 1; i < ctx.layers.size(); i++)
_boundingBox &= ctx.layers.at(i).boundingBox; _bbox &= ctx.layers.at(i).boundingBox;
if (_boundingBox.isNull()) { if (_bbox.isNull()) {
_errorString = "Empty layers bounding box join"; _errorString = "Empty layers bounding box join";
return false; return false;
} }
@ -306,42 +306,57 @@ bool WMS::parseCapabilities(const QString &path, const Setup &setup)
return false; return false;
} }
_tileUrl = ctx.url.isEmpty() ? setup.url() : ctx.url; if (_version >= "1.3.0") {
if (_setup.coordinateSystem().axisOrder() == CoordinateSystem::Unknown)
_cs = _projection.coordinateSystem();
else
_cs = _setup.coordinateSystem();
} else
_cs = CoordinateSystem::XY;
_getMapUrl = ctx.url.isEmpty() ? _setup.url() : ctx.url;
return true; return true;
} }
bool WMS::getCapabilities(const QString &url, const QString &file, bool WMS::downloadCapabilities(const QString &url)
const Authorization &authorization)
{ {
Downloader d; if (!_downloader) {
QList<Download> dl; _downloader = new Downloader(this);
connect(_downloader, SIGNAL(finished()), this,
dl.append(Download(url, file)); SLOT(capabilitiesReady()));
QEventLoop wait;
QObject::connect(&d, SIGNAL(finished()), &wait, SLOT(quit()));
if (d.get(dl, authorization))
wait.exec();
if (!QFileInfo(file).exists()) {
_errorString = "Error downloading capabilities XML file";
return false;
} }
return true; QList<Download> dl;
dl.append(Download(url, _path));
return _downloader->get(dl, _setup.authorization());
} }
WMS::WMS(const QString &file, const WMS::Setup &setup) : _valid(false) void WMS::capabilitiesReady()
{ {
QString capaUrl = QString("%1%2service=WMS&request=GetCapabilities") if (!QFileInfo(_path).exists()) {
_errorString = "Error downloading capabilities XML file";
_valid = false;
} else {
_ready = true;
_valid = parseCapabilities();
}
emit downloadFinished();
}
WMS::WMS(const QString &file, const WMS::Setup &setup, QObject *parent)
: QObject(parent), _setup(setup), _path(file), _downloader(0), _valid(false),
_ready(false)
{
QString url = QString("%1%2service=WMS&request=GetCapabilities")
.arg(setup.url(), setup.url().contains('?') ? "&" : "?"); .arg(setup.url(), setup.url().contains('?') ? "&" : "?");
if (!QFileInfo(file).exists()) if (!QFileInfo(file).exists())
if (!getCapabilities(capaUrl, file, setup.authorization())) _valid = downloadCapabilities(url);
return; else {
if (!parseCapabilities(file, setup)) _ready = true;
return; _valid = parseCapabilities();
}
_valid = true;
} }

View File

@ -12,8 +12,10 @@
class QXmlStreamReader; class QXmlStreamReader;
class WMS class WMS : public QObject
{ {
Q_OBJECT
public: public:
class Setup class Setup
{ {
@ -48,17 +50,26 @@ public:
}; };
WMS(const QString &path, const Setup &setup); WMS(const QString &path, const Setup &setup, QObject *parent = 0);
const RectC &bbox() const {return _bbox;}
const Projection &projection() const {return _projection;} const Projection &projection() const {return _projection;}
CoordinateSystem cs() const {return _cs;}
const RangeF &scaleDenominator() const {return _scaleDenominator;} const RangeF &scaleDenominator() const {return _scaleDenominator;}
const RectC &boundingBox() const {return _boundingBox;}
const QString &version() const {return _version;} const QString &version() const {return _version;}
const QString &tileUrl() const {return _tileUrl;} const QString &getMapUrl() const {return _getMapUrl;}
const WMS::Setup &setup() const {return _setup;}
bool isReady() const {return _valid && _ready;}
bool isValid() const {return _valid;} bool isValid() const {return _valid;}
const QString &errorString() const {return _errorString;} const QString &errorString() const {return _errorString;}
signals:
void downloadFinished();
private slots:
void capabilitiesReady();
private: private:
struct Layer { struct Layer {
QString name; QString name;
@ -97,20 +108,21 @@ private:
RectC &pBoundingBox); RectC &pBoundingBox);
void capability(QXmlStreamReader &reader, CTX &ctx); void capability(QXmlStreamReader &reader, CTX &ctx);
void capabilities(QXmlStreamReader &reader, CTX &ctx); void capabilities(QXmlStreamReader &reader, CTX &ctx);
bool parseCapabilities(const QString &path, const Setup &setup); bool parseCapabilities();
bool getCapabilities(const QString &url, const QString &file, bool downloadCapabilities(const QString &url);
const Authorization &authorization);
WMS::Setup _setup;
QString _path;
Downloader *_downloader;
Projection _projection; Projection _projection;
RangeF _scaleDenominator; RangeF _scaleDenominator;
RectC _boundingBox; RectC _bbox;
QString _version; QString _version;
QString _tileUrl; QString _getMapUrl;
CoordinateSystem _cs;
bool _valid; bool _valid, _ready;
QString _errorString; QString _errorString;
static Downloader *_downloader;
}; };
#endif // WMS_H #endif // WMS_H

View File

@ -14,109 +14,94 @@
double WMSMap::sd2res(double scaleDenominator) const double WMSMap::sd2res(double scaleDenominator) const
{ {
return scaleDenominator * 0.28e-3 * _projection.units().fromMeters(1.0); return scaleDenominator * _wms->projection().units().fromMeters(1.0)
* 0.28e-3;
} }
QString WMSMap::tileUrl(const QString &baseUrl, const QString &version) const QString WMSMap::tileUrl() const
{ {
QString url; const WMS::Setup &setup = _wms->setup();
url = QString("%1%2service=WMS&version=%3&request=GetMap&bbox=$bbox" QString url = QString("%1%2service=WMS&version=%3&request=GetMap&bbox=$bbox"
"&width=%4&height=%5&layers=%6&styles=%7&format=%8&transparent=true") "&width=%4&height=%5&layers=%6&styles=%7&format=%8&transparent=true")
.arg(baseUrl, baseUrl.contains('?') ? "&" : "?", version, .arg(_wms->getMapUrl(), _wms->getMapUrl().contains('?') ? "&" : "?",
QString::number(_tileSize), QString::number(_tileSize), _setup.layer(), _wms->version(), QString::number(_tileSize), QString::number(_tileSize),
_setup.style(), _setup.format()); setup.layer(), setup.style(), setup.format());
if (version >= "1.3.0") if (_wms->version() >= "1.3.0")
url.append(QString("&CRS=%1").arg(_setup.crs())); url.append(QString("&CRS=%1").arg(setup.crs()));
else else
url.append(QString("&SRS=%1").arg(_setup.crs())); url.append(QString("&SRS=%1").arg(setup.crs()));
for (int i = 0; i < _setup.dimensions().size(); i++) { for (int i = 0; i < setup.dimensions().size(); i++) {
const KV<QString, QString> &dim = _setup.dimensions().at(i); const KV<QString, QString> &dim = setup.dimensions().at(i);
url.append(QString("&%1=%2").arg(dim.key(), dim.value())); url.append(QString("&%1=%2").arg(dim.key(), dim.value()));
} }
return url; return url;
} }
QString WMSMap::tilesDir() const void WMSMap::computeZooms()
{
return QString(QDir(ProgramPaths::tilesDir()).filePath(_name));
}
void WMSMap::computeZooms(const RangeF &scaleDenominator)
{ {
_zooms.clear(); _zooms.clear();
if (scaleDenominator.size() > 0) { const RangeF &sd = _wms->scaleDenominator();
double ld = log2(scaleDenominator.max() - EPSILON) if (sd.size() > 0) {
- log2(scaleDenominator.min() + EPSILON); double ld = log2(sd.max() - EPSILON) - log2(sd.min() + EPSILON);
int cld = (int)ceil(ld); int cld = (int)ceil(ld);
double step = ld / (double)cld; double step = ld / (double)cld;
double lmax = log2(scaleDenominator.max() - EPSILON); double lmax = log2(sd.max() - EPSILON);
for (int i = 0; i <= cld; i++) for (int i = 0; i <= cld; i++)
_zooms.append(pow(2.0, lmax - i * step)); _zooms.append(pow(2.0, lmax - i * step));
} else } else
_zooms.append(scaleDenominator.min() + EPSILON); _zooms.append(sd.min() + EPSILON);
} }
void WMSMap::updateTransform() void WMSMap::updateTransform()
{ {
double pixelSpan = sd2res(_zooms.at(_zoom)); double pixelSpan = sd2res(_zooms.at(_zoom));
if (_projection.isGeographic()) if (_wms->projection().isGeographic())
pixelSpan /= deg2rad(WGS84_RADIUS); pixelSpan /= deg2rad(WGS84_RADIUS);
_transform = Transform(ReferencePoint(PointD(0, 0), _transform = Transform(ReferencePoint(PointD(0, 0),
_projection.ll2xy(_bbox.topLeft())), PointD(pixelSpan, pixelSpan)); _wms->projection().ll2xy(_wms->bbox().topLeft())),
} PointD(pixelSpan, pixelSpan));
bool WMSMap::loadWMS()
{
QString file = tilesDir() + "/" + CAPABILITIES_FILE;
WMS wms(file, _setup);
if (!wms.isValid()) {
_errorString = wms.errorString();
return false;
}
_projection = wms.projection();
_bbox = wms.boundingBox();
_bounds = RectD(_bbox, _projection);
_tileLoader->setUrl(tileUrl(wms.tileUrl(), wms.version()));
if (wms.version() >= "1.3.0") {
if (_setup.coordinateSystem().axisOrder() == CoordinateSystem::Unknown)
_cs = _projection.coordinateSystem();
else
_cs = _setup.coordinateSystem();
} else
_cs = CoordinateSystem::XY;
computeZooms(wms.scaleDenominator());
updateTransform();
return true;
} }
WMSMap::WMSMap(const QString &name, const WMS::Setup &setup, int tileSize, WMSMap::WMSMap(const QString &name, const WMS::Setup &setup, int tileSize,
QObject *parent) : Map(parent), _name(name), _setup(setup), _tileLoader(0), QObject *parent) : Map(parent), _name(name), _tileLoader(0), _zoom(0),
_zoom(0), _tileSize(tileSize), _mapRatio(1.0), _valid(false) _tileSize(tileSize), _mapRatio(1.0)
{ {
_tileLoader = new TileLoader(tilesDir(), this); QString tilesDir(QDir(ProgramPaths::tilesDir()).filePath(_name));
_tileLoader->setAuthorization(_setup.authorization());
connect(_tileLoader, SIGNAL(finished()), this, SIGNAL(loaded()));
_valid = loadWMS(); _tileLoader = new TileLoader(tilesDir, this);
_tileLoader->setAuthorization(setup.authorization());
connect(_tileLoader, SIGNAL(finished()), this, SIGNAL(tilesLoaded()));
_wms = new WMS(QDir(tilesDir).filePath(CAPABILITIES_FILE), setup, this);
connect(_wms, SIGNAL(downloadFinished()), this, SLOT(wmsReady()));
if (_wms->isReady())
init();
}
void WMSMap::init()
{
_tileLoader->setUrl(tileUrl());
_bounds = RectD(_wms->bbox(), _wms->projection());
computeZooms();
updateTransform();
}
void WMSMap::wmsReady()
{
if (_wms->isValid())
init();
emit mapLoaded();
} }
void WMSMap::clearCache() void WMSMap::clearCache()
{ {
_tileLoader->clearCache(); _tileLoader->clearCache();
_zoom = 0;
if (!loadWMS())
qWarning("%s: %s", qPrintable(_name), qPrintable(_errorString));
} }
QRectF WMSMap::bounds() QRectF WMSMap::bounds()
@ -128,10 +113,10 @@ QRectF WMSMap::bounds()
int WMSMap::zoomFit(const QSize &size, const RectC &rect) int WMSMap::zoomFit(const QSize &size, const RectC &rect)
{ {
if (rect.isValid()) { if (rect.isValid()) {
RectD prect(rect, _projection); RectD prect(rect, _wms->projection());
PointD sc(prect.width() / size.width(), prect.height() / size.height()); PointD sc(prect.width() / size.width(), prect.height() / size.height());
double resolution = qMax(qAbs(sc.x()), qAbs(sc.y())); double resolution = qMax(qAbs(sc.x()), qAbs(sc.y()));
if (_projection.isGeographic()) if (_wms->projection().isGeographic())
resolution *= deg2rad(WGS84_RADIUS); resolution *= deg2rad(WGS84_RADIUS);
_zoom = 0; _zoom = 0;
@ -169,12 +154,12 @@ int WMSMap::zoomOut()
QPointF WMSMap::ll2xy(const Coordinates &c) QPointF WMSMap::ll2xy(const Coordinates &c)
{ {
return _transform.proj2img(_projection.ll2xy(c)) / _mapRatio; return _transform.proj2img(_wms->projection().ll2xy(c)) / _mapRatio;
} }
Coordinates WMSMap::xy2ll(const QPointF &p) Coordinates WMSMap::xy2ll(const QPointF &p)
{ {
return _projection.xy2ll(_transform.img2proj(p * _mapRatio)); return _wms->projection().xy2ll(_transform.img2proj(p * _mapRatio));
} }
qreal WMSMap::tileSize() const qreal WMSMap::tileSize() const
@ -197,7 +182,7 @@ void WMSMap::draw(QPainter *painter, const QRectF &rect, Flags flags)
j * _tileSize))); j * _tileSize)));
PointD tbr(_transform.img2proj(QPointF(i * _tileSize + _tileSize, PointD tbr(_transform.img2proj(QPointF(i * _tileSize + _tileSize,
j * _tileSize + _tileSize))); j * _tileSize + _tileSize)));
RectD bbox = (_cs.axisOrder() == CoordinateSystem::YX) RectD bbox = (_wms->cs().axisOrder() == CoordinateSystem::YX)
? RectD(PointD(tbr.y(), tbr.x()), PointD(ttl.y(), ttl.x())) ? RectD(PointD(tbr.y(), tbr.x()), PointD(ttl.y(), ttl.x()))
: RectD(ttl, tbr); : RectD(ttl, tbr);

View File

@ -36,34 +36,30 @@ public:
{_mapRatio = mapRatio;} {_mapRatio = mapRatio;}
void clearCache(); void clearCache();
bool isValid() const {return _valid;} bool isReady() const {return _wms->isReady();}
QString errorString() const {return _errorString;} bool isValid() const {return _wms->isValid();}
QString errorString() const {return _wms->errorString();}
private slots:
void wmsReady();
private: private:
QString tileUrl(const QString &baseUrl, const QString &version) const; QString tileUrl() const;
double sd2res(double scaleDenominator) const; double sd2res(double scaleDenominator) const;
QString tilesDir() const; void computeZooms();
void computeZooms(const RangeF &scaleDenominator);
void updateTransform(); void updateTransform();
bool loadWMS();
qreal tileSize() const; qreal tileSize() const;
void init();
QString _name; QString _name;
WMS *_wms;
WMS::Setup _setup;
TileLoader *_tileLoader; TileLoader *_tileLoader;
Projection _projection;
Transform _transform;
CoordinateSystem _cs;
QVector<double> _zooms;
RectC _bbox;
RectD _bounds; RectD _bounds;
Transform _transform;
QVector<double> _zooms;
int _zoom; int _zoom;
int _tileSize; int _tileSize;
qreal _mapRatio; qreal _mapRatio;
bool _valid;
QString _errorString;
}; };
#endif // WMSMAP_H #endif // WMSMAP_H

View File

@ -58,7 +58,7 @@ void WMTS::tileMatrixSet(QXmlStreamReader &reader, CTX &ctx)
{ {
while (reader.readNextStartElement()) { while (reader.readNextStartElement()) {
if (reader.name() == "Identifier") { if (reader.name() == "Identifier") {
if (reader.readElementText() != ctx.setup.set()) { if (reader.readElementText() != _setup.set()) {
skipParentElement(reader); skipParentElement(reader);
return; return;
} }
@ -114,7 +114,7 @@ void WMTS::tileMatrixSetLink(QXmlStreamReader &reader, CTX &ctx)
{ {
while (reader.readNextStartElement()) { while (reader.readNextStartElement()) {
if (reader.name() == "TileMatrixSet") { if (reader.name() == "TileMatrixSet") {
if (reader.readElementText() == ctx.setup.set()) if (reader.readElementText() == _setup.set())
ctx.hasSet = true; ctx.hasSet = true;
else { else {
skipParentElement(reader); skipParentElement(reader);
@ -163,7 +163,7 @@ void WMTS::layer(QXmlStreamReader &reader, CTX &ctx)
{ {
while (reader.readNextStartElement()) { while (reader.readNextStartElement()) {
if (reader.name() == "Identifier") { if (reader.name() == "Identifier") {
if (reader.readElementText() == ctx.setup.layer()) if (reader.readElementText() == _setup.layer())
ctx.hasLayer = true; ctx.hasLayer = true;
else { else {
skipParentElement(reader); skipParentElement(reader);
@ -172,10 +172,10 @@ void WMTS::layer(QXmlStreamReader &reader, CTX &ctx)
} else if (reader.name() == "TileMatrixSetLink") } else if (reader.name() == "TileMatrixSetLink")
tileMatrixSetLink(reader, ctx); tileMatrixSetLink(reader, ctx);
else if (reader.name() == "WGS84BoundingBox") else if (reader.name() == "WGS84BoundingBox")
_bounds = wgs84BoundingBox(reader); ctx.bbox = wgs84BoundingBox(reader);
else if (reader.name() == "ResourceURL") { else if (reader.name() == "ResourceURL") {
const QXmlStreamAttributes &attr = reader.attributes(); const QXmlStreamAttributes &attr = reader.attributes();
if (attr.value("resourceType") == "tile" && ctx.setup.rest()) if (attr.value("resourceType") == "tile" && _setup.rest())
_tileUrl = attr.value("template").toString(); _tileUrl = attr.value("template").toString();
reader.skipCurrentElement(); reader.skipCurrentElement();
} else if (reader.name() == "Style") { } else if (reader.name() == "Style") {
@ -184,11 +184,11 @@ void WMTS::layer(QXmlStreamReader &reader, CTX &ctx)
QString s = style(reader); QString s = style(reader);
if (isDefault) if (isDefault)
ctx.defaultStyle = s; ctx.defaultStyle = s;
if (s == ctx.setup.style()) if (s == _setup.style())
ctx.hasStyle = true; ctx.hasStyle = true;
} else if (reader.name() == "Format") { } else if (reader.name() == "Format") {
QString format(reader.readElementText()); QString format(reader.readElementText());
if (bareFormat(format) == bareFormat(ctx.setup.format())) if (bareFormat(format) == bareFormat(_setup.format()))
ctx.hasFormat = true; ctx.hasFormat = true;
} else } else
reader.skipCurrentElement(); reader.skipCurrentElement();
@ -232,9 +232,9 @@ void WMTS::createZooms(const CTX &ctx)
qSort(_zooms); qSort(_zooms);
} }
bool WMTS::parseCapabilities(const QString &path, CTX &ctx) bool WMTS::parseCapabilities(CTX &ctx)
{ {
QFile file(path); QFile file(_path);
QXmlStreamReader reader; QXmlStreamReader reader;
if (!file.open(QFile::ReadOnly | QFile::Text)) { if (!file.open(QFile::ReadOnly | QFile::Text)) {
@ -250,30 +250,30 @@ bool WMTS::parseCapabilities(const QString &path, CTX &ctx)
reader.raiseError("Not a Capabilities XML file"); reader.raiseError("Not a Capabilities XML file");
} }
if (reader.error()) { if (reader.error()) {
_errorString = QString("%1:%2: %3").arg(path).arg(reader.lineNumber()) _errorString = QString("%1:%2: %3").arg(_path).arg(reader.lineNumber())
.arg(reader.errorString()); .arg(reader.errorString());
return false; return false;
} }
if (!ctx.hasLayer) { if (!ctx.hasLayer) {
_errorString = ctx.setup.layer() + ": layer not provided"; _errorString = _setup.layer() + ": layer not provided";
return false; return false;
} }
if (!ctx.hasStyle && !ctx.setup.style().isEmpty()) { if (!ctx.hasStyle && !_setup.style().isEmpty()) {
_errorString = ctx.setup.style() + ": style not provided"; _errorString = _setup.style() + ": style not provided";
return false; return false;
} }
if (!ctx.hasStyle && ctx.setup.style().isEmpty() if (!ctx.hasStyle && _setup.style().isEmpty()
&& ctx.defaultStyle.isEmpty()) { && ctx.defaultStyle.isEmpty()) {
_errorString = "Default style not provided"; _errorString = "Default style not provided";
return false; return false;
} }
if (!ctx.setup.rest() && !ctx.hasFormat) { if (!_setup.rest() && !ctx.hasFormat) {
_errorString = ctx.setup.format() + ": format not provided"; _errorString = _setup.format() + ": format not provided";
return false; return false;
} }
if (!ctx.hasSet) { if (!ctx.hasSet) {
_errorString = ctx.setup.set() + ": set not provided"; _errorString = _setup.set() + ": set not provided";
return false; return false;
} }
if (ctx.crs.isNull()) { if (ctx.crs.isNull()) {
@ -290,74 +290,92 @@ bool WMTS::parseCapabilities(const QString &path, CTX &ctx)
_errorString = "No usable tile matrix found"; _errorString = "No usable tile matrix found";
return false; return false;
} }
if (ctx.setup.rest() && _tileUrl.isNull()) { if (_setup.rest() && _tileUrl.isNull()) {
_errorString = "Missing tile URL template"; _errorString = "Missing tile URL template";
return false; return false;
} }
_bbox = ctx.bbox;
_cs = (_setup.coordinateSystem().axisOrder() == CoordinateSystem::Unknown)
? _projection.coordinateSystem() : _setup.coordinateSystem();
return true; return true;
} }
bool WMTS::downloadCapabilities(const QString &url, const QString &file, bool WMTS::downloadCapabilities(const QString &url)
const Authorization &authorization)
{ {
Downloader d; if (!_downloader) {
QList<Download> dl; _downloader = new Downloader(this);
connect(_downloader, SIGNAL(finished()), this,
dl.append(Download(url, file)); SLOT(capabilitiesReady()));
QEventLoop wait;
QObject::connect(&d, SIGNAL(finished()), &wait, SLOT(quit()));
if (d.get(dl, authorization))
wait.exec();
if (!QFileInfo(file).exists()) {
_errorString = "Error downloading capabilities XML file";
return false;
} }
return true; QList<Download> dl;
dl.append(Download(url, _path));
return _downloader->get(dl, _setup.authorization());
} }
WMTS::WMTS(const QString &file, const WMTS::Setup &setup) : _valid(false) void WMTS::capabilitiesReady()
{ {
QUrl url(setup.rest() ? setup.url() : QString( if (!QFileInfo(_path).exists()) {
"%1%2service=WMTS&Version=1.0.0&request=GetCapabilities").arg(setup.url(), _errorString = "Error downloading capabilities XML file";
setup.url().contains('?') ? "&" : "?")); _valid = false;
} else {
_ready = true;
_valid = init();
}
if (!url.isLocalFile() && !QFileInfo(file).exists()) emit downloadFinished();
if (!downloadCapabilities(url.toString(), file, setup.authorization())) }
return;
CTX ctx(setup); bool WMTS::init()
if (!parseCapabilities(url.isLocalFile() ? url.toLocalFile() : file, ctx)) {
return; CTX ctx;
if (!parseCapabilities(ctx))
return false;
QString style = setup.style().isEmpty() ? ctx.defaultStyle : setup.style(); QString style = _setup.style().isEmpty() ? ctx.defaultStyle : _setup.style();
if (!setup.rest()) { if (!_setup.rest()) {
_tileUrl = QString("%1%2service=WMTS&Version=1.0.0&request=GetTile" _tileUrl = QString("%1%2service=WMTS&Version=1.0.0&request=GetTile"
"&Format=%3&Layer=%4&Style=%5&TileMatrixSet=%6&TileMatrix=$z" "&Format=%3&Layer=%4&Style=%5&TileMatrixSet=%6&TileMatrix=$z"
"&TileRow=$y&TileCol=$x").arg(setup.url(), "&TileRow=$y&TileCol=$x").arg(_setup.url(),
setup.url().contains('?') ? "&" : "?" , setup.format(), _setup.url().contains('?') ? "&" : "?" , _setup.format(),
setup.layer(), style, setup.set()); _setup.layer(), style, _setup.set());
for (int i = 0; i < setup.dimensions().size(); i++) { for (int i = 0; i < _setup.dimensions().size(); i++) {
const KV<QString, QString> &dim = setup.dimensions().at(i); const KV<QString, QString> &dim = _setup.dimensions().at(i);
_tileUrl.append(QString("&%1=%2").arg(dim.key(), dim.value())); _tileUrl.append(QString("&%1=%2").arg(dim.key(), dim.value()));
} }
} else { } else {
_tileUrl.replace("{Style}", style, Qt::CaseInsensitive); _tileUrl.replace("{Style}", style, Qt::CaseInsensitive);
_tileUrl.replace("{TileMatrixSet}", setup.set(), Qt::CaseInsensitive); _tileUrl.replace("{TileMatrixSet}", _setup.set(), Qt::CaseInsensitive);
_tileUrl.replace("{TileMatrix}", "$z", Qt::CaseInsensitive); _tileUrl.replace("{TileMatrix}", "$z", Qt::CaseInsensitive);
_tileUrl.replace("{TileRow}", "$y", Qt::CaseInsensitive); _tileUrl.replace("{TileRow}", "$y", Qt::CaseInsensitive);
_tileUrl.replace("{TileCol}", "$x", Qt::CaseInsensitive); _tileUrl.replace("{TileCol}", "$x", Qt::CaseInsensitive);
for (int i = 0; i < setup.dimensions().size(); i++) { for (int i = 0; i < _setup.dimensions().size(); i++) {
const KV<QString, QString> &dim = setup.dimensions().at(i); const KV<QString, QString> &dim = _setup.dimensions().at(i);
_tileUrl.replace(QString("{%1}").arg(dim.key()), dim.value(), _tileUrl.replace(QString("{%1}").arg(dim.key()), dim.value(),
Qt::CaseInsensitive); Qt::CaseInsensitive);
} }
} }
_valid = true; return true;
}
WMTS::WMTS(const QString &file, const WMTS::Setup &setup, QObject *parent)
: QObject(parent), _setup(setup), _downloader(0), _valid(false), _ready(false)
{
QUrl url(setup.rest() ? setup.url() : QString(
"%1%2service=WMTS&Version=1.0.0&request=GetCapabilities").arg(setup.url(),
setup.url().contains('?') ? "&" : "?"));
_path = url.isLocalFile() ? url.toLocalFile() : file;
if (!url.isLocalFile() && !QFileInfo(file).exists())
_valid = downloadCapabilities(url.toString());
else {
_ready = true;
_valid = init();
}
} }
#ifndef QT_NO_DEBUG #ifndef QT_NO_DEBUG

View File

@ -14,8 +14,10 @@
class QXmlStreamReader; class QXmlStreamReader;
class WMTS class WMTS : public QObject
{ {
Q_OBJECT
public: public:
class Setup class Setup
{ {
@ -79,16 +81,24 @@ public:
}; };
WMTS(const QString &path, const Setup &setup); WMTS(const QString &path, const Setup &setup, QObject *parent = 0);
const RectC &bounds() const {return _bounds;} const RectC &bbox() const {return _bbox;}
const QList<Zoom> &zooms() const {return _zooms;} const QList<Zoom> &zooms() const {return _zooms;}
const Projection &projection() const {return _projection;} const Projection &projection() const {return _projection;}
const QString &tileUrl() const {return _tileUrl;} const QString &tileUrl() const {return _tileUrl;}
CoordinateSystem cs() const {return _cs;}
bool isReady() const {return _valid && _ready;}
bool isValid() const {return _valid;} bool isValid() const {return _valid;}
const QString &errorString() const {return _errorString;} const QString &errorString() const {return _errorString;}
signals:
void downloadFinished();
private slots:
void capabilitiesReady();
private: private:
struct TileMatrix { struct TileMatrix {
QString id; QString id;
@ -118,18 +128,18 @@ private:
}; };
struct CTX { struct CTX {
const Setup &setup;
QSet<TileMatrix> matrixes; QSet<TileMatrix> matrixes;
QSet<MatrixLimits> limits; QSet<MatrixLimits> limits;
QString crs; QString crs;
QString defaultStyle; QString defaultStyle;
RectC bbox;
bool hasLayer; bool hasLayer;
bool hasStyle; bool hasStyle;
bool hasFormat; bool hasFormat;
bool hasSet; bool hasSet;
CTX(const Setup &setup) : setup(setup), hasLayer(false), hasStyle(false), CTX() : hasLayer(false), hasStyle(false), hasFormat(false), hasSet(false)
hasFormat(false), hasSet(false) {} {}
}; };
RectC wgs84BoundingBox(QXmlStreamReader &reader); RectC wgs84BoundingBox(QXmlStreamReader &reader);
@ -142,17 +152,21 @@ private:
void layer(QXmlStreamReader &reader, CTX &ctx); void layer(QXmlStreamReader &reader, CTX &ctx);
void contents(QXmlStreamReader &reader, CTX &ctx); void contents(QXmlStreamReader &reader, CTX &ctx);
void capabilities(QXmlStreamReader &reader, CTX &ctx); void capabilities(QXmlStreamReader &reader, CTX &ctx);
bool parseCapabilities(const QString &path, CTX &ctx); bool parseCapabilities(CTX &ctx);
bool downloadCapabilities(const QString &url, const QString &file, bool downloadCapabilities(const QString &url);
const Authorization &authorization);
void createZooms(const CTX &ctx); void createZooms(const CTX &ctx);
bool init();
WMTS::Setup _setup;
QString _path;
Downloader *_downloader;
RectC _bbox;
QList<Zoom> _zooms; QList<Zoom> _zooms;
RectC _bounds;
Projection _projection; Projection _projection;
QString _tileUrl; QString _tileUrl;
CoordinateSystem _cs;
bool _valid; bool _valid, _ready;
QString _errorString; QString _errorString;
friend uint qHash(const WMTS::TileMatrix &key); friend uint qHash(const WMTS::TileMatrix &key);

View File

@ -12,70 +12,57 @@
#define CAPABILITIES_FILE "capabilities.xml" #define CAPABILITIES_FILE "capabilities.xml"
bool WMTSMap::loadWMTS() WMTSMap::WMTSMap(const QString &name, const WMTS::Setup &setup, qreal tileRatio,
QObject *parent) : Map(parent), _name(name), _tileLoader(0), _zoom(0),
_mapRatio(1.0), _tileRatio(tileRatio)
{ {
QString file = tilesDir() + "/" + CAPABILITIES_FILE; QString tilesDir(QDir(ProgramPaths::tilesDir()).filePath(_name));
WMTS wmts(file, _setup); _tileLoader = new TileLoader(tilesDir, this);
if (!wmts.isValid()) { _tileLoader->setAuthorization(setup.authorization());
_errorString = wmts.errorString(); connect(_tileLoader, SIGNAL(finished()), this, SIGNAL(tilesLoaded()));
return false;
}
_zooms = wmts.zooms(); _wmts = new WMTS(QDir(tilesDir).filePath(CAPABILITIES_FILE), setup, this);
_projection = wmts.projection(); connect(_wmts, SIGNAL(downloadFinished()), this, SLOT(wmtsReady()));
_tileLoader->setUrl(wmts.tileUrl()); if (_wmts->isReady())
_bounds = RectD(wmts.bounds(), _projection); init();
if (_setup.coordinateSystem().axisOrder() == CoordinateSystem::Unknown)
_cs = _projection.coordinateSystem();
else
_cs = _setup.coordinateSystem();
updateTransform();
return true;
} }
WMTSMap::WMTSMap(const QString &name, const WMTS::Setup &setup, qreal tileRatio, void WMTSMap::init()
QObject *parent) : Map(parent), _name(name), _setup(setup), _tileLoader(0),
_zoom(0), _mapRatio(1.0), _tileRatio(tileRatio), _valid(false)
{ {
_tileLoader = new TileLoader(tilesDir(), this); _tileLoader->setUrl(_wmts->tileUrl());
_tileLoader->setAuthorization(_setup.authorization()); _bounds = RectD(_wmts->bbox(), _wmts->projection());
connect(_tileLoader, SIGNAL(finished()), this, SIGNAL(loaded())); updateTransform();
}
_valid = loadWMTS(); void WMTSMap::wmtsReady()
{
if (_wmts->isValid())
init();
emit mapLoaded();
} }
void WMTSMap::clearCache() void WMTSMap::clearCache()
{ {
_tileLoader->clearCache(); _tileLoader->clearCache();
_zoom = 0;
if (!loadWMTS())
qWarning("%s: %s", qPrintable(_name), qPrintable(_errorString));
}
QString WMTSMap::tilesDir() const
{
return QString(QDir(ProgramPaths::tilesDir()).filePath(_name));
} }
double WMTSMap::sd2res(double scaleDenominator) const double WMTSMap::sd2res(double scaleDenominator) const
{ {
return scaleDenominator * 0.28e-3 * _projection.units().fromMeters(1.0); return scaleDenominator * 0.28e-3
* _wmts->projection().units().fromMeters(1.0);
} }
void WMTSMap::updateTransform() void WMTSMap::updateTransform()
{ {
const WMTS::Zoom &z = _zooms.at(_zoom); const WMTS::Zoom &z = _wmts->zooms().at(_zoom);
PointD topLeft = (_cs.axisOrder() == CoordinateSystem::YX) PointD topLeft = (_wmts->cs().axisOrder() == CoordinateSystem::YX)
? PointD(z.topLeft().y(), z.topLeft().x()) : z.topLeft(); ? PointD(z.topLeft().y(), z.topLeft().x()) : z.topLeft();
double pixelSpan = sd2res(z.scaleDenominator()); double pixelSpan = sd2res(z.scaleDenominator());
if (_projection.isGeographic()) if (_wmts->projection().isGeographic())
pixelSpan /= deg2rad(WGS84_RADIUS); pixelSpan /= deg2rad(WGS84_RADIUS);
_transform = Transform(ReferencePoint(PointD(0, 0), topLeft), _transform = Transform(ReferencePoint(PointD(0, 0), topLeft),
PointD(pixelSpan, pixelSpan)); PointD(pixelSpan, pixelSpan));
@ -83,7 +70,7 @@ void WMTSMap::updateTransform()
QRectF WMTSMap::bounds() QRectF WMTSMap::bounds()
{ {
const WMTS::Zoom &z = _zooms.at(_zoom); const WMTS::Zoom &z = _wmts->zooms().at(_zoom);
QRectF tileBounds, bounds; QRectF tileBounds, bounds;
tileBounds = (z.limits().isNull()) ? tileBounds = (z.limits().isNull()) ?
@ -95,29 +82,29 @@ QRectF WMTSMap::bounds()
if (_bounds.isValid()) if (_bounds.isValid())
bounds = QRectF(_transform.proj2img(_bounds.topLeft()) bounds = QRectF(_transform.proj2img(_bounds.topLeft())
/ coordinatesRatio(), _transform.proj2img(_bounds.bottomRight()) / coordinatesRatio(), _transform.proj2img(
/ coordinatesRatio()); _bounds.bottomRight()) / coordinatesRatio());
return bounds.isValid() ? tileBounds.intersected(bounds) : tileBounds; return bounds.isValid() ? tileBounds.intersected(bounds) : tileBounds;
} }
int WMTSMap::zoomFit(const QSize &size, const RectC &rect) int WMTSMap::zoomFit(const QSize &size, const RectC &rect)
{ {
if (rect.isValid()) { if (rect.isValid()) {
RectD prect(rect, _projection); RectD prect(rect, _wmts->projection());
PointD sc(prect.width() / size.width(), prect.height() / size.height()); PointD sc(prect.width() / size.width(), prect.height() / size.height());
double resolution = qMax(qAbs(sc.x()), qAbs(sc.y())); double resolution = qMax(qAbs(sc.x()), qAbs(sc.y()));
if (_projection.isGeographic()) if (_wmts->projection().isGeographic())
resolution *= deg2rad(WGS84_RADIUS); resolution *= deg2rad(WGS84_RADIUS);
_zoom = 0; _zoom = 0;
for (int i = 0; i < _zooms.size(); i++) { for (int i = 0; i < _wmts->zooms().size(); i++) {
if (sd2res(_zooms.at(i).scaleDenominator()) < resolution if (sd2res(_wmts->zooms().at(i).scaleDenominator()) < resolution
/ coordinatesRatio()) / coordinatesRatio())
break; break;
_zoom = i; _zoom = i;
} }
} else } else
_zoom = _zooms.size() - 1; _zoom = _wmts->zooms().size() - 1;
updateTransform(); updateTransform();
return _zoom; return _zoom;
@ -131,7 +118,7 @@ void WMTSMap::setZoom(int zoom)
int WMTSMap::zoomIn() int WMTSMap::zoomIn()
{ {
_zoom = qMin(_zoom + 1, _zooms.size() - 1); _zoom = qMin(_zoom + 1, _wmts->zooms().size() - 1);
updateTransform(); updateTransform();
return _zoom; return _zoom;
} }
@ -161,7 +148,7 @@ QSizeF WMTSMap::tileSize(const WMTS::Zoom &zoom) const
void WMTSMap::draw(QPainter *painter, const QRectF &rect, Flags flags) void WMTSMap::draw(QPainter *painter, const QRectF &rect, Flags flags)
{ {
const WMTS::Zoom &z = _zooms.at(_zoom); const WMTS::Zoom &z = _wmts->zooms().at(_zoom);
QSizeF ts(tileSize(z)); QSizeF ts(tileSize(z));
QPoint tl = QPoint(qFloor(rect.left() / ts.width()), QPoint tl = QPoint(qFloor(rect.left() / ts.width()),
@ -194,10 +181,12 @@ void WMTSMap::draw(QPainter *painter, const QRectF &rect, Flags flags)
QPointF WMTSMap::ll2xy(const Coordinates &c) QPointF WMTSMap::ll2xy(const Coordinates &c)
{ {
return _transform.proj2img(_projection.ll2xy(c)) / coordinatesRatio(); return _transform.proj2img(_wmts->projection().ll2xy(c))
/ coordinatesRatio();
} }
Coordinates WMTSMap::xy2ll(const QPointF &p) Coordinates WMTSMap::xy2ll(const QPointF &p)
{ {
return _projection.xy2ll(_transform.img2proj(p * coordinatesRatio())); return _wmts->projection().xy2ll(_transform.img2proj(p
* coordinatesRatio()));
} }

View File

@ -36,31 +36,28 @@ public:
{_mapRatio = mapRatio;} {_mapRatio = mapRatio;}
void clearCache(); void clearCache();
bool isValid() const {return _valid;} bool isReady() const {return _wmts->isReady();}
QString errorString() const {return _errorString;} bool isValid() const {return _wmts->isValid();}
QString errorString() const {return _wmts->errorString();}
private slots:
void wmtsReady();
private: private:
bool loadWMTS();
double sd2res(double scaleDenominator) const; double sd2res(double scaleDenominator) const;
QString tilesDir() const;
void updateTransform(); void updateTransform();
QSizeF tileSize(const WMTS::Zoom &zoom) const; QSizeF tileSize(const WMTS::Zoom &zoom) const;
qreal coordinatesRatio() const; qreal coordinatesRatio() const;
qreal imageRatio() const; qreal imageRatio() const;
void init();
QString _name; QString _name;
WMTS::Setup _setup; WMTS *_wmts;
TileLoader *_tileLoader; TileLoader *_tileLoader;
RectD _bounds;
QList<WMTS::Zoom> _zooms;
Projection _projection;
Transform _transform; Transform _transform;
CoordinateSystem _cs; RectD _bounds;
int _zoom; int _zoom;
qreal _mapRatio, _tileRatio; qreal _mapRatio, _tileRatio;
bool _valid;
QString _errorString;
}; };
#endif // WMTSMAP_H #endif // WMTSMAP_H