1
0
mirror of https://github.com/tumic0/GPXSee.git synced 2024-11-27 21:24:47 +01:00

Initial WMS support (no multi-layer support for now)

This commit is contained in:
Martin Tůma 2018-03-30 10:25:05 +02:00
parent 8821536419
commit bf3589812a
20 changed files with 910 additions and 252 deletions

View File

@ -119,7 +119,10 @@ HEADERS += src/config.h \
src/map/mapsource.h \ src/map/mapsource.h \
src/map/tileloader.h \ src/map/tileloader.h \
src/map/wmtsmap.h \ src/map/wmtsmap.h \
src/map/wmts.h src/map/wmts.h \
src/map/wmsmap.h \
src/map/wms.h \
src/map/crs.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 \
@ -208,7 +211,10 @@ SOURCES += src/main.cpp \
src/map/mapsource.cpp \ src/map/mapsource.cpp \
src/map/tileloader.cpp \ src/map/tileloader.cpp \
src/map/wmtsmap.cpp \ src/map/wmtsmap.cpp \
src/map/wmts.cpp src/map/wmts.cpp \
src/map/wmsmap.cpp \
src/map/wms.cpp \
src/map/crs.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

@ -5,6 +5,7 @@
#include <QNetworkProxyFactory> #include <QNetworkProxyFactory>
#include <QLibraryInfo> #include <QLibraryInfo>
#include "map/wmts.h" #include "map/wmts.h"
#include "map/wms.h"
#include "map/tileloader.h" #include "map/tileloader.h"
#include "map/downloader.h" #include "map/downloader.h"
#include "map/ellipsoid.h" #include "map/ellipsoid.h"
@ -40,6 +41,7 @@ App::App(int &argc, char **argv) : QApplication(argc, argv),
Downloader *dl = new Downloader(this); Downloader *dl = new Downloader(this);
TileLoader::setDownloader(dl); TileLoader::setDownloader(dl);
WMTS::setDownloader(dl); WMTS::setDownloader(dl);
WMS::setDownloader(dl);
OPENGL_SET_SAMPLES(4); OPENGL_SET_SAMPLES(4);
loadDatums(); loadDatums();
loadPCSs(); loadPCSs();

View File

@ -179,7 +179,7 @@ QList<PathItem *> MapView::loadData(const Data &data)
if (_tracks.empty() && _routes.empty() && _waypoints.empty()) if (_tracks.empty() && _routes.empty() && _waypoints.empty())
return paths; return paths;
if (mapZoom() != zoom) if (fitMapZoom() != zoom)
rescale(); rescale();
else else
updatePOIVisibility(); updatePOIVisibility();
@ -189,7 +189,7 @@ QList<PathItem *> MapView::loadData(const Data &data)
return paths; return paths;
} }
int MapView::mapZoom() const int MapView::fitMapZoom() const
{ {
RectC br = _tr | _rr | _wr; RectC br = _tr | _rr | _wr;
@ -389,7 +389,10 @@ void MapView::setCoordinatesFormat(CoordinatesFormat format)
void MapView::clearMapCache() void MapView::clearMapCache()
{ {
_map->clearCache(); _map->clearCache();
resetCachedContent();
fitMapZoom();
rescale();
centerOn(contentCenter());
} }
void MapView::digitalZoom(int zoom) void MapView::digitalZoom(int zoom)
@ -767,7 +770,7 @@ void MapView::resizeEvent(QResizeEvent *event)
QGraphicsView::resizeEvent(event); QGraphicsView::resizeEvent(event);
int zoom = _map->zoom(); int zoom = _map->zoom();
if (mapZoom() != zoom) if (fitMapZoom() != zoom)
rescale(); rescale();
centerOn(contentCenter()); centerOn(contentCenter());

View File

@ -80,7 +80,7 @@ private:
void loadPOI(); void loadPOI();
void clearPOI(); void clearPOI();
int mapZoom() const; int fitMapZoom() const;
QPointF contentCenter() const; QPointF contentCenter() const;
void rescale(); void rescale();
void centerOn(const QPointF &pos); void centerOn(const QPointF &pos);

View File

@ -16,6 +16,9 @@ public:
bool isValid() const {return size() >= 0;} bool isValid() const {return size() >= 0;}
void setMin(int min) {_min = min;}
void setMax(int max) {_max = max;}
private: private:
int _min, _max; int _min, _max;
}; };
@ -32,6 +35,9 @@ public:
bool isValid() const {return size() >= 0;} bool isValid() const {return size() >= 0;}
void setMin(qreal min) {_min = min;}
void setMax(qreal max) {_max = max;}
void resize(qreal size); void resize(qreal size);
private: private:

46
src/map/crs.cpp Normal file
View File

@ -0,0 +1,46 @@
#include <QStringList>
#include "pcs.h"
#include "crs.h"
Projection CRS::projection(const QString &crs)
{
QStringList list(crs.split(':'));
QString authority, code;
bool res;
int epsg;
const PCS *pcs;
const GCS *gcs;
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 Projection();
}
if (authority == "EPSG") {
epsg = code.toInt(&res);
if (!res)
return Projection();
if ((pcs = PCS::pcs(epsg)))
return Projection(pcs->gcs(), pcs->method(), pcs->setup(),
pcs->units());
else if ((gcs = GCS::gcs(epsg)))
return Projection(gcs);
else
return Projection();
} else if (authority == "OGC") {
if (code == "CRS84")
return Projection(GCS::gcs(4326));
else
return Projection();
} else
return Projection();
}

11
src/map/crs.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef CRS_H
#define CRS_H
#include "projection.h"
namespace CRS
{
Projection projection(const QString &crs);
}
#endif // CRS_H

View File

@ -2,6 +2,7 @@
#include <QXmlStreamReader> #include <QXmlStreamReader>
#include "onlinemap.h" #include "onlinemap.h"
#include "wmtsmap.h" #include "wmtsmap.h"
#include "wmsmap.h"
#include "mapsource.h" #include "mapsource.h"
#define ZOOM_MAX 19 #define ZOOM_MAX 19
@ -11,9 +12,10 @@
#define BOUNDS_RIGHT 180 #define BOUNDS_RIGHT 180
#define BOUNDS_BOTTOM -85.0511 #define BOUNDS_BOTTOM -85.0511
MapSource::TMSConfig::TMSConfig()
: zooms(ZOOM_MIN, ZOOM_MAX), bounds(Coordinates(BOUNDS_LEFT, BOUNDS_TOP), MapSource::Config::Config() : type(TMS), zooms(ZOOM_MIN, ZOOM_MAX),
Coordinates(BOUNDS_RIGHT, BOUNDS_BOTTOM)) {} bounds(Coordinates(BOUNDS_LEFT, BOUNDS_TOP), Coordinates(BOUNDS_RIGHT,
BOUNDS_BOTTOM)), format("image/png"), rest(false), yx(false) {}
Range MapSource::zooms(QXmlStreamReader &reader) Range MapSource::zooms(QXmlStreamReader &reader)
@ -23,11 +25,7 @@ Range MapSource::zooms(QXmlStreamReader &reader)
bool res; bool res;
if (attr.hasAttribute("min")) { if (attr.hasAttribute("min")) {
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
min = attr.value("min").toString().toInt(&res); min = attr.value("min").toString().toInt(&res);
#else // QT_VERSION < 5
min = attr.value("min").toInt(&res);
#endif // QT_VERSION < 5
if (!res || (min < ZOOM_MIN || min > ZOOM_MAX)) { if (!res || (min < ZOOM_MIN || min > ZOOM_MAX)) {
reader.raiseError("Invalid minimal zoom level"); reader.raiseError("Invalid minimal zoom level");
return Range(); return Range();
@ -36,11 +34,7 @@ Range MapSource::zooms(QXmlStreamReader &reader)
min = ZOOM_MIN; min = ZOOM_MIN;
if (attr.hasAttribute("max")) { if (attr.hasAttribute("max")) {
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
max = attr.value("max").toString().toInt(&res); max = attr.value("max").toString().toInt(&res);
#else // QT_VERSION < 5
max = attr.value("max").toInt(&res);
#endif // QT_VERSION < 5
if (!res || (max < ZOOM_MIN || max > ZOOM_MAX)) { if (!res || (max < ZOOM_MIN || max > ZOOM_MAX)) {
reader.raiseError("Invalid maximal zoom level"); reader.raiseError("Invalid maximal zoom level");
return Range(); return Range();
@ -63,11 +57,7 @@ RectC MapSource::bounds(QXmlStreamReader &reader)
bool res; bool res;
if (attr.hasAttribute("top")) { if (attr.hasAttribute("top")) {
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
top = attr.value("top").toString().toDouble(&res); top = attr.value("top").toString().toDouble(&res);
#else // QT_VERSION < 5
top = attr.value("top").toDouble(&res);
#endif // QT_VERSION < 5
if (!res || (top < BOUNDS_BOTTOM || top > BOUNDS_TOP)) { if (!res || (top < BOUNDS_BOTTOM || top > BOUNDS_TOP)) {
reader.raiseError("Invalid bounds top value"); reader.raiseError("Invalid bounds top value");
return RectC(); return RectC();
@ -76,11 +66,7 @@ RectC MapSource::bounds(QXmlStreamReader &reader)
top = BOUNDS_TOP; top = BOUNDS_TOP;
if (attr.hasAttribute("bottom")) { if (attr.hasAttribute("bottom")) {
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
bottom = attr.value("bottom").toString().toDouble(&res); bottom = attr.value("bottom").toString().toDouble(&res);
#else // QT_VERSION < 5
bottom = attr.value("bottom").toDouble(&res);
#endif // QT_VERSION < 5
if (!res || (bottom < BOUNDS_BOTTOM || bottom > BOUNDS_TOP)) { if (!res || (bottom < BOUNDS_BOTTOM || bottom > BOUNDS_TOP)) {
reader.raiseError("Invalid bounds bottom value"); reader.raiseError("Invalid bounds bottom value");
return RectC(); return RectC();
@ -89,11 +75,7 @@ RectC MapSource::bounds(QXmlStreamReader &reader)
bottom = BOUNDS_BOTTOM; bottom = BOUNDS_BOTTOM;
if (attr.hasAttribute("left")) { if (attr.hasAttribute("left")) {
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
left = attr.value("left").toString().toDouble(&res); left = attr.value("left").toString().toDouble(&res);
#else // QT_VERSION < 5
left = attr.value("left").toDouble(&res);
#endif // QT_VERSION < 5
if (!res || (left < BOUNDS_LEFT || left > BOUNDS_RIGHT)) { if (!res || (left < BOUNDS_LEFT || left > BOUNDS_RIGHT)) {
reader.raiseError("Invalid bounds left value"); reader.raiseError("Invalid bounds left value");
return RectC(); return RectC();
@ -102,11 +84,7 @@ RectC MapSource::bounds(QXmlStreamReader &reader)
left = BOUNDS_LEFT; left = BOUNDS_LEFT;
if (attr.hasAttribute("right")) { if (attr.hasAttribute("right")) {
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
right = attr.value("right").toString().toDouble(&res); right = attr.value("right").toString().toDouble(&res);
#else // QT_VERSION < 5
right = attr.value("right").toDouble(&res);
#endif // QT_VERSION < 5
if (!res || (right < BOUNDS_LEFT || right > BOUNDS_RIGHT)) { if (!res || (right < BOUNDS_LEFT || right > BOUNDS_RIGHT)) {
reader.raiseError("Invalid bounds right value"); reader.raiseError("Invalid bounds right value");
return RectC(); return RectC();
@ -128,38 +106,44 @@ RectC MapSource::bounds(QXmlStreamReader &reader)
void MapSource::map(QXmlStreamReader &reader, Config &config) void MapSource::map(QXmlStreamReader &reader, Config &config)
{ {
config.type = (reader.attributes().value("type") == "WMTS") ? WMTS : TMS; const QXmlStreamAttributes &attr = reader.attributes();
config.type = (attr.value("type") == "WMTS") ? WMTS
: (attr.value("type") == "WMS") ? WMS : TMS;
while (reader.readNextStartElement()) { while (reader.readNextStartElement()) {
if (reader.name() == "name") if (reader.name() == "name")
config.name = reader.readElementText(); config.name = reader.readElementText();
else if (reader.name() == "url") { else if (reader.name() == "url") {
config.wmts.rest = (reader.attributes().value("type") == "REST") config.rest = (reader.attributes().value("type") == "REST")
? true : false; ? true : false;
config.url = reader.readElementText(); config.url = reader.readElementText();
} else if (reader.name() == "zoom") { } else if (reader.name() == "zoom") {
config.tms.zooms = zooms(reader); config.zooms = zooms(reader);
reader.skipCurrentElement(); reader.skipCurrentElement();
} else if (reader.name() == "bounds") { } else if (reader.name() == "bounds") {
config.tms.bounds = bounds(reader); config.bounds = bounds(reader);
reader.skipCurrentElement(); reader.skipCurrentElement();
} else if (reader.name() == "format") { } else if (reader.name() == "format") {
config.wmts.format = reader.readElementText(); config.format = reader.readElementText();
} else if (reader.name() == "layer") } else if (reader.name() == "layer")
config.wmts.layer = reader.readElementText(); config.layer = reader.readElementText();
else if (reader.name() == "style") else if (reader.name() == "style")
config.wmts.style = reader.readElementText(); config.style = reader.readElementText();
else if (reader.name() == "set") { else if (reader.name() == "set") {
config.wmts.yx = (reader.attributes().value("axis") == "yx") config.yx = (reader.attributes().value("axis") == "yx")
? true : false; ? true : false;
config.wmts.set = reader.readElementText(); config.set = reader.readElementText();
} else if (reader.name() == "dimension") { } else if (reader.name() == "dimension") {
QXmlStreamAttributes attr = reader.attributes(); QXmlStreamAttributes attr = reader.attributes();
if (!attr.hasAttribute("id")) if (!attr.hasAttribute("id"))
reader.raiseError("Missing dimension id"); reader.raiseError("Missing dimension id");
else else
config.wmts.dimensions.append(QPair<QString, QString>( config.dimensions.append(QPair<QString, QString>(
attr.value("id").toString(), reader.readElementText())); attr.value("id").toString(), reader.readElementText()));
} else if (reader.name() == "crs") {
config.yx = (reader.attributes().value("axis") == "yx")
? true : false;
config.crs = reader.readElementText();
} else } else
reader.skipCurrentElement(); reader.skipCurrentElement();
} }
@ -170,6 +154,8 @@ Map *MapSource::loadFile(const QString &path)
QFile file(path); QFile file(path);
QXmlStreamReader reader; QXmlStreamReader reader;
Config config; Config config;
Map *m;
if (!file.open(QFile::ReadOnly | QFile::Text)) { if (!file.open(QFile::ReadOnly | QFile::Text)) {
_errorString = file.errorString(); _errorString = file.errorString();
@ -197,33 +183,43 @@ Map *MapSource::loadFile(const QString &path)
_errorString = "Missing URL definition"; _errorString = "Missing URL definition";
return 0; return 0;
} }
if (config.type == WMTS) { if (config.type == WMTS || config.type == WMS) {
if (config.wmts.layer.isEmpty()) { if (config.layer.isEmpty()) {
_errorString = "Missing layer definition"; _errorString = "Missing layer definition";
return 0; return 0;
} }
if (config.wmts.style.isEmpty()) { if (config.style.isEmpty()) {
_errorString = "Missing style definiton"; _errorString = "Missing style definiton";
return 0; return 0;
} }
if (config.wmts.set.isEmpty()) { if (config.format.isEmpty()) {
_errorString = "Missing set definiton";
return 0;
}
if (config.wmts.format.isEmpty()) {
_errorString = "Missing format definition"; _errorString = "Missing format definition";
return 0; return 0;
} }
} }
if (config.type == WMTS) {
if (config.set.isEmpty()) {
_errorString = "Missing set definiton";
return 0;
}
}
if (config.type == WMS) {
if (config.crs.isEmpty()) {
_errorString = "Missing CRS definiton";
return 0;
}
}
Map *m;
if (config.type == WMTS) if (config.type == WMTS)
m = new WMTSMap(config.name, WMTS::Setup(config.url, config.wmts.layer, m = new WMTSMap(config.name, WMTS::Setup(config.url, config.layer,
config.wmts.set, config.wmts.style, config.wmts.format, config.set, config.style, config.format, config.rest, config.yx,
config.wmts.rest, config.wmts.yx, config.wmts.dimensions)); config.dimensions));
else if (config.type == WMS)
m = new WMSMap(config.name, WMS::Setup(config.url, config.layer,
config.style, config.format, config.crs, config.yx));
else else
m = new OnlineMap(config.name, config.url, config.tms.zooms, m = new OnlineMap(config.name, config.url, config.zooms, config.bounds);
config.tms.bounds);
if (!m->isValid()) { if (!m->isValid()) {
_errorString = m->errorString(); _errorString = m->errorString();
delete m; delete m;

View File

@ -17,36 +17,26 @@ public:
private: private:
enum Type { enum Type {
TMS, TMS,
WMTS WMTS,
WMS
}; };
struct TMSConfig { struct Config {
Type type;
QString name;
QString url;
Range zooms; Range zooms;
RectC bounds; RectC bounds;
TMSConfig();
};
struct WMTSConfig {
QString layer; QString layer;
QString style; QString style;
QString set; QString set;
QString format; QString format;
QString crs;
bool rest; bool rest;
bool yx; bool yx;
QList<QPair<QString, QString> > dimensions; QList<QPair<QString, QString> > dimensions;
WMTSConfig() : format("image/png"), rest(false), yx(false) {} Config();
};
struct Config {
QString name;
QString url;
Type type;
WMTSConfig wmts;
TMSConfig tms;
Config() : type(TMS) {}
}; };
RectC bounds(QXmlStreamReader &reader); RectC bounds(QXmlStreamReader &reader);

View File

@ -8,23 +8,26 @@
class Tile class Tile
{ {
public: public:
Tile(const QPoint &xy, const QVariant &zoom) Tile(const QPoint &xy, const QVariant &zoom, const QRectF &bbox = QRectF())
{_xy = xy; _zoom = zoom;} {_xy = xy; _zoom = zoom; _bbox = bbox;}
QVariant zoom() const {return _zoom;} const QVariant &zoom() const {return _zoom;}
const QPoint& xy() const {return _xy;} const QPoint &xy() const {return _xy;}
const QRectF &bbox() const {return _bbox;}
QPixmap& pixmap() {return _pixmap;} QPixmap& pixmap() {return _pixmap;}
private: private:
QVariant _zoom; QVariant _zoom;
QPoint _xy; QPoint _xy;
QRectF _bbox;
QPixmap _pixmap; QPixmap _pixmap;
}; };
#ifndef QT_NO_DEBUG #ifndef QT_NO_DEBUG
inline QDebug operator<<(QDebug dbg, const Tile &tile) inline QDebug operator<<(QDebug dbg, const Tile &tile)
{ {
dbg.nospace() << "Tile(" << tile.zoom() << ", " << tile.xy() << ")"; dbg.nospace() << "Tile(" << tile.zoom() << ", " << tile.xy() << ", "
<< tile.bbox() << ")";
return dbg.space(); return dbg.space();
} }
#endif // QT_NO_DEBUG #endif // QT_NO_DEBUG

View File

@ -85,9 +85,18 @@ QString TileLoader::tileUrl(const Tile &tile) const
{ {
QString url(_url); QString url(_url);
url.replace("$z", tile.zoom().toString()); if (!tile.bbox().isNull()) {
url.replace("$x", QString::number(tile.xy().x())); QString bbox = QString("%1,%2,%3,%4").arg(
url.replace("$y", QString::number(tile.xy().y())); QString::number(tile.bbox().left(), 'f', 6),
QString::number(tile.bbox().bottom(), 'f', 6),
QString::number(tile.bbox().right(), 'f', 6),
QString::number(tile.bbox().top(), 'f', 6));
url.replace("$bbox", bbox);
} else {
url.replace("$z", tile.zoom().toString());
url.replace("$x", QString::number(tile.xy().x()));
url.replace("$y", QString::number(tile.xy().y()));
}
return url; return url;
} }

View File

@ -22,7 +22,6 @@ public:
{_downloader = downloader;} {_downloader = downloader;}
private: private:
void fillTile(Tile &tile);
QString tileUrl(const Tile &tile) const; QString tileUrl(const Tile &tile) const;
QString tileFile(const Tile &tile) const; QString tileFile(const Tile &tile) const;

215
src/map/wms.cpp Normal file
View File

@ -0,0 +1,215 @@
#include <QFileInfo>
#include <QEventLoop>
#include <QXmlStreamReader>
#include "downloader.h"
#include "crs.h"
#include "wms.h"
Downloader *WMS::_downloader = 0;
void WMS::getMap(QXmlStreamReader &reader, CTX &ctx)
{
while (reader.readNextStartElement()) {
if (reader.name() == "Format") {
if (reader.readElementText() == ctx.setup.format())
ctx.format = true;
} else
reader.skipCurrentElement();
}
}
void WMS::request(QXmlStreamReader &reader, CTX &ctx)
{
while (reader.readNextStartElement()) {
if (reader.name() == "GetMap")
getMap(reader, ctx);
else
reader.skipCurrentElement();
}
}
QString WMS::style(QXmlStreamReader &reader)
{
QString name;
while (reader.readNextStartElement()) {
if (reader.name() == "Name")
name = reader.readElementText();
else
reader.skipCurrentElement();
}
return name;
}
void WMS::layer(QXmlStreamReader &reader, CTX &ctx,
const QList<QString> &pCRSs, const QList<QString> &pStyles)
{
QString name;
QList<QString> CRSs(pCRSs);
QList<QString> styles(pStyles);
RangeF scaleDenominator(2132.729583849784, 559082264.0287178);
QRectF boundingBox;
while (reader.readNextStartElement()) {
if (reader.name() == "Name")
name = reader.readElementText();
else if (reader.name() == "CRS")
CRSs.append(reader.readElementText());
else if (reader.name() == "Style")
styles.append(style(reader));
else if (reader.name() == "MinScaleDenominator")
scaleDenominator.setMin(reader.readElementText().toDouble());
else if (reader.name() == "MaxScaleDenominator")
scaleDenominator.setMax(reader.readElementText().toDouble());
else if (reader.name() == "BoundingBox") {
QXmlStreamAttributes attr = reader.attributes();
if (attr.value("CRS") == ctx.setup.crs()) {
if (ctx.setup.yx())
boundingBox = QRectF(QPointF(
attr.value("miny").toString().toDouble(),
attr.value("maxx").toString().toDouble()),
QPointF(attr.value("maxy").toString().toDouble(),
attr.value("minx").toString().toDouble()));
else
boundingBox = QRectF(QPointF(
attr.value("minx").toString().toDouble(),
attr.value("maxy").toString().toDouble()),
QPointF(attr.value("maxx").toString().toDouble(),
attr.value("miny").toString().toDouble()));
}
reader.skipCurrentElement();
} else if (reader.name() == "Layer")
layer(reader, ctx, CRSs, styles);
else
reader.skipCurrentElement();
}
if (name == ctx.setup.layer()) {
ctx.scaleDenominator = scaleDenominator;
ctx.boundingBox = boundingBox;
ctx.layer = true;
ctx.style = styles.contains(ctx.setup.style());
ctx.crs = CRSs.contains(ctx.setup.crs());
}
}
void WMS::capability(QXmlStreamReader &reader, CTX &ctx)
{
QList<QString> CRSs;
QList<QString> styles;
while (reader.readNextStartElement()) {
if (reader.name() == "Layer")
layer(reader, ctx, CRSs, styles);
else if (reader.name() == "Request")
request(reader, ctx);
else
reader.skipCurrentElement();
}
}
void WMS::capabilities(QXmlStreamReader &reader, CTX &ctx)
{
while (reader.readNextStartElement()) {
if (reader.name() == "Capability")
capability(reader, ctx);
else
reader.skipCurrentElement();
}
}
bool WMS::parseCapabilities(const QString &path, const Setup &setup)
{
QFile file(path);
CTX ctx(setup);
QXmlStreamReader reader;
if (!file.open(QFile::ReadOnly | QFile::Text)) {
_errorString = file.errorString();
return false;
}
reader.setDevice(&file);
if (reader.readNextStartElement()) {
if (reader.name() == "WMS_Capabilities")
capabilities(reader, ctx);
else
reader.raiseError("Not a WMS_Capabilities XML file");
}
if (reader.error()) {
_errorString = QString("%1:%2: %3").arg(path).arg(reader.lineNumber())
.arg(reader.errorString());
return false;
}
if (!ctx.layer) {
_errorString = ctx.setup.layer() + ": layer not provided";
return false;
}
if (!ctx.style) {
_errorString = ctx.setup.style() + ": style not provided";
return false;
}
if (!ctx.format) {
_errorString = ctx.setup.format() + ": format not provided";
return false;
}
if (!ctx.crs) {
_errorString = ctx.setup.crs() + ": CRS not provided";
return false;
}
_projection = CRS::projection(ctx.setup.crs());
if (_projection.isNull()) {
_errorString = ctx.setup.crs() + ": unknown CRS";
return false;
}
if (!ctx.scaleDenominator.isValid()) {
_errorString = "Invalid scale denominator range";
return false;
}
if (ctx.boundingBox.isNull()) {
_errorString = "Missing bounding box";
return false;
}
_boundingBox = ctx.boundingBox;
_scaleDenominator = ctx.scaleDenominator;
return true;
}
bool WMS::getCapabilities(const QString &url, const QString &file)
{
QList<Download> dl;
dl.append(Download(url, 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;
}
}
WMS::WMS(const QString &file, const WMS::Setup &setup) : _valid(false)
{
QString capaUrl = QString("%1?service=WMS&request=GetCapabilities")
.arg(setup.url());
if (!QFileInfo(file).exists())
if (!getCapabilities(capaUrl, file))
return;
if (!parseCapabilities(file, setup))
return;
_valid = true;
}

86
src/map/wms.h Normal file
View File

@ -0,0 +1,86 @@
#ifndef WMS_H
#define WMS_H
#include <QString>
#include <QRectF>
#include "common/range.h"
#include "projection.h"
class QXmlStreamReader;
class Downloader;
class WMS
{
public:
class Setup
{
public:
Setup(const QString &url, const QString &layer, const QString &style,
const QString &format, const QString &crs, bool yx)
: _url(url), _layer(layer), _style(style), _format(format), _crs(crs),
_yx(yx) {}
const QString &url() const {return _url;}
const QString &layer() const {return _layer;}
const QString &style() const {return _style;}
const QString &format() const {return _format;}
const QString &crs() const {return _crs;}
bool yx() const {return _yx;}
private:
QString _url;
QString _layer;
QString _style;
QString _format;
QString _crs;
bool _yx;
};
WMS(const QString &path, const Setup &setup);
const Projection &projection() const {return _projection;}
const RangeF &scaleDenominator() const {return _scaleDenominator;}
const QRectF &boundingBox() const {return _boundingBox;}
bool isValid() const {return _valid;}
const QString &errorString() const {return _errorString;}
static void setDownloader(Downloader *downloader)
{_downloader = downloader;}
private:
struct CTX {
const Setup &setup;
RangeF scaleDenominator;
QRectF boundingBox;
bool layer;
bool style;
bool format;
bool crs;
CTX(const Setup &setup) : setup(setup), layer(false), style(false),
format(false), crs(false) {}
};
QString style(QXmlStreamReader &reader);
void getMap(QXmlStreamReader &reader, CTX &ctx);
void request(QXmlStreamReader &reader, CTX &ctx);
void layer(QXmlStreamReader &reader, CTX &ctx, const QList<QString> &pCRSs,
const QList<QString> &pStyles);
void capability(QXmlStreamReader &reader, CTX &ctx);
void capabilities(QXmlStreamReader &reader, CTX &ctx);
bool parseCapabilities(const QString &path, const Setup &setup);
bool getCapabilities(const QString &url, const QString &file);
Projection _projection;
RangeF _scaleDenominator;
QRectF _boundingBox;
bool _valid;
QString _errorString;
static Downloader *_downloader;
};
#endif // WMS_H

230
src/map/wmsmap.cpp Normal file
View File

@ -0,0 +1,230 @@
#include <QDir>
#include <QPainter>
#include "common/wgs84.h"
#include "common/rectc.h"
#include "config.h"
#include "downloader.h"
#include "wmsmap.h"
#define CAPABILITIES_FILE "capabilities.xml"
#define TILE_SIZE 256
qreal WMSMap::sd2res(qreal scaleDenominator) const
{
return scaleDenominator * 0.28e-3 * _projection.units().fromMeters(1.0);
}
QString WMSMap::tileUrl() const
{
return QString("%1?version=1.3.0&request=GetMap&CRS=%2&bbox=$bbox"
"&width=%3&height=%4&layers=%5&styles=%6&format=%7&transparent=true")
.arg(_setup.url(), _setup.crs(), QString::number(TILE_SIZE),
QString::number(TILE_SIZE), _setup.layer(), _setup.style(),
_setup.format());
}
QString WMSMap::tilesDir() const
{
return QString(TILES_DIR + "/" + _name);
}
void WMSMap::computeZooms(const RangeF &scaleDenominator)
{
_zooms.clear();
if (scaleDenominator.size() > 0) {
qreal ld = log2(scaleDenominator.max()) - log2(scaleDenominator.min());
int cld = ceil(ld);
qreal step = ld / (qreal)cld;
qreal lmax = log2(scaleDenominator.max());
for (int i = 0; i <= cld; i++)
_zooms.append(pow(2.0, lmax - i * step));
} else
_zooms.append(scaleDenominator.min());
}
void WMSMap::updateTransform()
{
qreal scaleDenominator = _zooms.at(_zoom);
ReferencePoint tl, br;
qreal pixelSpan = sd2res(scaleDenominator);
if (_projection.isGeographic())
pixelSpan /= deg2rad(WGS84_RADIUS);
tl.xy = QPoint(0, 0);
tl.pp = _boundingBox.topLeft();
br.xy = QPoint(_boundingBox.width() / pixelSpan, -_boundingBox.height()
/ pixelSpan);
br.pp = _boundingBox.bottomRight();
QList<ReferencePoint> points;
points << tl << br;
_transform = Transform(points);
}
bool WMSMap::loadWMS()
{
QString file = tilesDir() + "/" + CAPABILITIES_FILE;
WMS wms(file, _setup);
if (!wms.isValid()) {
_errorString = wms.errorString();
return false;
}
_projection = wms.projection();
_boundingBox = wms.boundingBox();
_tileLoader = TileLoader(tileUrl(), tilesDir());
computeZooms(wms.scaleDenominator());
updateTransform();
return true;
}
WMSMap::WMSMap(const QString &name, const WMS::Setup &setup, QObject *parent)
: Map(parent), _name(name), _setup(setup), _zoom(0), _block(false),
_valid(false)
{
if (!QDir().mkpath(tilesDir())) {
_errorString = "Error creating tiles dir";
return;
}
_valid = loadWMS();
}
void WMSMap::clearCache()
{
_tileLoader.clearCache();
_zoom = 0;
if (!loadWMS())
qWarning("%s: %s\n", qPrintable(_name), qPrintable(_errorString));
}
void WMSMap::load()
{
connect(TileLoader::downloader(), SIGNAL(finished()), this,
SLOT(emitLoaded()));
}
void WMSMap::unload()
{
disconnect(TileLoader::downloader(), SIGNAL(finished()), this,
SLOT(emitLoaded()));
}
void WMSMap::emitLoaded()
{
emit loaded();
}
QRectF WMSMap::bounds() const
{
qreal pixelSpan = sd2res(_zooms.at(_zoom));
if (_projection.isGeographic())
pixelSpan /= deg2rad(WGS84_RADIUS);
QSizeF size(_boundingBox.width() / pixelSpan, -_boundingBox.height()
/ pixelSpan);
return QRectF(QPointF(0, 0), size);
}
qreal WMSMap::resolution(const QRectF &rect) const
{
Coordinates tl = xy2ll((rect.topLeft()));
Coordinates br = xy2ll(rect.bottomRight());
qreal ds = tl.distanceTo(br);
qreal ps = QLineF(rect.topLeft(), rect.bottomRight()).length();
return ds/ps;
}
int WMSMap::zoomFit(const QSize &size, const RectC &br)
{
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()));
if (_projection.isGeographic())
resolution *= deg2rad(WGS84_RADIUS);
_zoom = 0;
for (int i = 0; i < _zooms.size(); i++) {
if (sd2res(_zooms.at(i)) < resolution)
break;
_zoom = i;
}
} else
_zoom = _zooms.size() - 1;
updateTransform();
return _zoom;
}
int WMSMap::zoomIn()
{
_zoom = qMin(_zoom + 1, _zooms.size() - 1);
updateTransform();
return _zoom;
}
int WMSMap::zoomOut()
{
_zoom = qMax(_zoom - 1, 0);
updateTransform();
return _zoom;
}
QPointF WMSMap::ll2xy(const Coordinates &c) const
{
return _transform.proj2img(_projection.ll2xy(c));
}
Coordinates WMSMap::xy2ll(const QPointF &p) const
{
return _projection.xy2ll(_transform.img2proj(p));
}
void WMSMap::draw(QPainter *painter, const QRectF &rect)
{
QPoint tl = QPoint((int)floor(rect.left() / (qreal)TILE_SIZE),
(int)floor(rect.top() / (qreal)TILE_SIZE));
QPoint br = QPoint((int)ceil(rect.right() / (qreal)TILE_SIZE),
(int)ceil(rect.bottom() / (qreal)TILE_SIZE));
QList<Tile> tiles;
for (int i = tl.x(); i < br.x(); i++) {
for (int j = tl.y(); j < br.y(); j++) {
QPointF ttl(_transform.img2proj(QPointF(i * TILE_SIZE,
j * TILE_SIZE)));
QPointF tbr(_transform.img2proj(QPointF(i * TILE_SIZE + TILE_SIZE
- 1, j * TILE_SIZE + TILE_SIZE - 1)));
QRectF bbox = _setup.yx()
? QRectF(QPointF(tbr.y(), tbr.x()), QPointF(ttl.y(), ttl.x()))
: QRectF(ttl, tbr);
tiles.append(Tile(QPoint(i, j), _zoom, bbox));
}
}
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() * TILE_SIZE, t.xy().y() * TILE_SIZE);
if (t.pixmap().isNull())
painter->fillRect(QRect(tp, QSize(TILE_SIZE, TILE_SIZE)),
_backgroundColor);
else
painter->drawPixmap(tp, t.pixmap());
}
}

73
src/map/wmsmap.h Normal file
View File

@ -0,0 +1,73 @@
#ifndef WMSMAP_H
#define WMSMAP_H
#include "transform.h"
#include "projection.h"
#include "map.h"
#include "wms.h"
#include "tileloader.h"
class WMSMap : public Map
{
Q_OBJECT
public:
WMSMap(const QString &name, const WMS::Setup &setup, QObject *parent = 0);
const QString &name() const {return _name;}
QRectF bounds() const;
qreal resolution(const QRectF &rect) const;
int zoom() const {return _zoom;}
int zoomFit(const QSize &size, const RectC &br);
int zoomIn();
int zoomOut();
QPointF ll2xy(const Coordinates &c)
{return static_cast<const WMSMap &>(*this).ll2xy(c);}
Coordinates xy2ll(const QPointF &p)
{return static_cast<const WMSMap &>(*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:
QString tileUrl() const;
qreal sd2res(qreal scaleDenominator) const;
QString tilesDir() const;
void computeZooms(const RangeF &scaleDenominator);
void updateTransform();
bool loadWMS();
QPointF ll2xy(const Coordinates &c) const;
Coordinates xy2ll(const QPointF &p) const;
QString _name;
WMS::Setup _setup;
TileLoader _tileLoader;
Projection _projection;
Transform _transform;
QVector<qreal> _zooms;
int _zoom;
QRectF _boundingBox;
bool _block;
bool _valid;
QString _errorString;
};
#endif // WMSMAP_H

View File

@ -8,57 +8,12 @@
#include <QXmlStreamReader> #include <QXmlStreamReader>
#include "downloader.h" #include "downloader.h"
#include "pcs.h" #include "pcs.h"
#include "crs.h"
#include "wmts.h" #include "wmts.h"
Downloader *WMTS::_downloader = 0; Downloader *WMTS::_downloader = 0;
bool WMTS::createProjection(const QString &crs)
{
QStringList list(crs.split(':'));
QString authority, code;
bool res;
int epsg;
const PCS *pcs;
const GCS *gcs;
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") {
epsg = code.toInt(&res);
if (!res)
return false;
if ((pcs = PCS::pcs(epsg))) {
_projection = Projection(pcs->gcs(), pcs->method(), pcs->setup(),
pcs->units());
return true;
} else if ((gcs = GCS::gcs(epsg))) {
_projection = Projection(gcs);
return true;
} else
return false;
} else if (authority == "OGC") {
if (code == "CRS84") {
_projection = Projection(GCS::gcs(4326));
return true;
} else
return false;
} else
return false;
}
WMTS::TileMatrix WMTS::tileMatrix(QXmlStreamReader &reader, bool yx) WMTS::TileMatrix WMTS::tileMatrix(QXmlStreamReader &reader, bool yx)
{ {
TileMatrix matrix; TileMatrix matrix;
@ -104,12 +59,12 @@ void WMTS::tileMatrixSet(QXmlStreamReader &reader, CTX &ctx)
else if (reader.name() == "SupportedCRS") else if (reader.name() == "SupportedCRS")
crs = reader.readElementText(); crs = reader.readElementText();
else if (reader.name() == "TileMatrix") else if (reader.name() == "TileMatrix")
matrixes.insert(tileMatrix(reader, ctx.setup.yx)); matrixes.insert(tileMatrix(reader, ctx.setup.yx()));
else else
reader.skipCurrentElement(); reader.skipCurrentElement();
} }
if (id == ctx.setup.set) { if (id == ctx.setup.set()) {
ctx.crs = crs; ctx.crs = crs;
_matrixes = matrixes; _matrixes = matrixes;
} }
@ -168,7 +123,7 @@ void WMTS::tileMatrixSetLink(QXmlStreamReader &reader, CTX &ctx)
reader.skipCurrentElement(); reader.skipCurrentElement();
} }
if (id == ctx.setup.set) { if (id == ctx.setup.set()) {
ctx.set = true; ctx.set = true;
_limits = limits; _limits = limits;
} }
@ -232,14 +187,14 @@ void WMTS::layer(QXmlStreamReader &reader, CTX &ctx)
reader.skipCurrentElement(); reader.skipCurrentElement();
} }
if (id == ctx.setup.layer) { if (id == ctx.setup.layer()) {
ctx.layer = true; ctx.layer = true;
_bounds = bounds; _bounds = bounds;
if (ctx.setup.rest) if (ctx.setup.rest())
_tileUrl = tpl; _tileUrl = tpl;
if (styles.contains(ctx.setup.style)) if (styles.contains(ctx.setup.style()))
ctx.style = true; ctx.style = true;
if (formats.contains(ctx.setup.format)) if (formats.contains(ctx.setup.format()))
ctx.format = true; ctx.format = true;
} }
} }
@ -291,26 +246,27 @@ bool WMTS::parseCapabilities(const QString &path, const Setup &setup)
} }
if (!ctx.layer) { if (!ctx.layer) {
_errorString = ctx.setup.layer + ": layer not provided"; _errorString = ctx.setup.layer() + ": layer not provided";
return false; return false;
} }
if (!ctx.style) { if (!ctx.style) {
_errorString = ctx.setup.style + ": style not provided"; _errorString = ctx.setup.style() + ": style not provided";
return false; return false;
} }
if (!ctx.setup.rest && !ctx.format) { if (!ctx.setup.rest() && !ctx.format) {
_errorString = ctx.setup.format + ": format not provided"; _errorString = ctx.setup.format() + ": format not provided";
return false; return false;
} }
if (!ctx.set) { if (!ctx.set) {
_errorString = ctx.setup.set + ": set not provided"; _errorString = ctx.setup.set() + ": set not provided";
return false; return false;
} }
if (ctx.crs.isNull()) { if (ctx.crs.isNull()) {
_errorString = "Missing CRS definition"; _errorString = "Missing CRS definition";
return false; return false;
} }
if (!createProjection(ctx.crs)) { _projection = CRS::projection(ctx.crs);
if (_projection.isNull()) {
_errorString = ctx.crs + ": unknown CRS"; _errorString = ctx.crs + ": unknown CRS";
return false; return false;
} }
@ -318,7 +274,7 @@ bool WMTS::parseCapabilities(const QString &path, const Setup &setup)
_errorString = "No usable tile matrix found"; _errorString = "No usable tile matrix found";
return false; return false;
} }
if (ctx.setup.rest && _tileUrl.isNull()) { if (ctx.setup.rest() && _tileUrl.isNull()) {
_errorString = "Missing tile URL template"; _errorString = "Missing tile URL template";
return false; return false;
} }
@ -345,41 +301,41 @@ bool WMTS::getCapabilities(const QString &url, const QString &file)
} }
} }
bool WMTS::load(const QString &file, const WMTS::Setup &setup) WMTS::WMTS(const QString &file, const WMTS::Setup &setup) : _valid(false)
{ {
QString capaUrl = setup.rest ? setup.url : QString capaUrl = setup.rest() ? setup.url() :
QString("%1?service=WMTS&Version=1.0.0&request=GetCapabilities") QString("%1?service=WMTS&Version=1.0.0&request=GetCapabilities")
.arg(setup.url); .arg(setup.url());
if (!QFileInfo(file).exists()) if (!QFileInfo(file).exists())
if (!getCapabilities(capaUrl, file)) if (!getCapabilities(capaUrl, file))
return false; return;
if (!parseCapabilities(file, setup)) if (!parseCapabilities(file, setup))
return false; return;
if (!setup.rest) { if (!setup.rest()) {
_tileUrl = QString("%1?service=WMTS&Version=1.0.0&request=GetTile" _tileUrl = QString("%1?service=WMTS&Version=1.0.0&request=GetTile"
"&Format=%2&Layer=%3&Style=%4&TileMatrixSet=%5&TileMatrix=$z" "&Format=%2&Layer=%3&Style=%4&TileMatrixSet=%5&TileMatrix=$z"
"&TileRow=$y&TileCol=$x").arg(setup.url).arg(setup.format) "&TileRow=$y&TileCol=$x").arg(setup.url()).arg(setup.format())
.arg(setup.layer).arg(setup.style).arg(setup.set); .arg(setup.layer()).arg(setup.style()).arg(setup.set());
for (int i = 0; i < setup.dimensions.size(); i++) { for (int i = 0; i < setup.dimensions().size(); i++) {
const QPair<QString, QString> &dim = setup.dimensions.at(i); const QPair<QString, QString> &dim = setup.dimensions().at(i);
_tileUrl.append(QString("&%1=%2").arg(dim.first).arg(dim.second)); _tileUrl.append(QString("&%1=%2").arg(dim.first).arg(dim.second));
} }
} else { } else {
_tileUrl.replace("{Style}", setup.style, Qt::CaseInsensitive); _tileUrl.replace("{Style}", setup.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 QPair<QString, QString> &dim = setup.dimensions.at(i); const QPair<QString, QString> &dim = setup.dimensions().at(i);
_tileUrl.replace(QString("{%1}").arg(dim.first), dim.second, _tileUrl.replace(QString("{%1}").arg(dim.first), dim.second,
Qt::CaseInsensitive); Qt::CaseInsensitive);
} }
} }
return true; _valid = true;
} }
QList<WMTS::Zoom> WMTS::zooms() const QList<WMTS::Zoom> WMTS::zooms() const
@ -405,17 +361,17 @@ QList<WMTS::Zoom> WMTS::zooms() const
#ifndef QT_NO_DEBUG #ifndef QT_NO_DEBUG
QDebug operator<<(QDebug dbg, const WMTS::Setup &setup) QDebug operator<<(QDebug dbg, const WMTS::Setup &setup)
{ {
dbg.nospace() << "Setup(" << setup.url << ", " << setup.layer << ", " dbg.nospace() << "Setup(" << setup.url() << ", " << setup.layer() << ", "
<< setup.set << ", " << setup.style << ", " << setup.format << ", " << setup.set() << ", " << setup.style() << ", " << setup.format() << ", "
<< setup.rest << ")"; << setup.rest() << ")";
return dbg.space(); return dbg.space();
} }
QDebug operator<<(QDebug dbg, const WMTS::Zoom &zoom) QDebug operator<<(QDebug dbg, const WMTS::Zoom &zoom)
{ {
dbg.nospace() << "Zoom(" << zoom.id << ", " << zoom.scaleDenominator << ", " dbg.nospace() << "Zoom(" << zoom.id() << ", " << zoom.scaleDenominator()
<< zoom.topLeft << ", " << zoom.tile << ", " << zoom.matrix << ", " << ", " << zoom.topLeft() << ", " << zoom.tile() << ", " << zoom.matrix()
<< zoom.limits << ")"; << ", " << zoom.limits() << ")";
return dbg.space(); return dbg.space();
} }
#endif // QT_NO_DEBUG #endif // QT_NO_DEBUG

View File

@ -15,49 +15,73 @@ class QXmlStreamReader;
class WMTS class WMTS
{ {
public: public:
struct Setup { class Setup
QString url; {
QString layer; public:
QString set;
QString style;
QString format;
bool rest;
bool yx;
QList<QPair<QString, QString> > dimensions;
Setup(const QString &url, const QString &layer, const QString &set, Setup(const QString &url, const QString &layer, const QString &set,
const QString &style, const QString &format, bool rest, bool yx, const QString &style, const QString &format, bool rest, bool yx,
const QList<QPair<QString, QString> > &dimensions) : const QList<QPair<QString, QString> > &dimensions) :
url(url), layer(layer), set(set), style(style), format(format), _url(url), _layer(layer), _set(set), _style(style), _format(format),
rest(rest), yx(yx), dimensions(dimensions) {} _rest(rest), _yx(yx), _dimensions(dimensions) {}
const QString &url() const {return _url;}
const QString &layer() const {return _layer;}
const QString &set() const {return _set;}
const QString &style() const {return _style;}
const QString &format() const {return _format;}
bool rest() const {return _rest;}
bool yx() const {return _yx;}
const QList<QPair<QString, QString> > &dimensions() const
{return _dimensions;}
private:
QString _url;
QString _layer;
QString _set;
QString _style;
QString _format;
bool _rest;
bool _yx;
QList<QPair<QString, QString> > _dimensions;
}; };
struct Zoom { class Zoom
QString id; {
qreal scaleDenominator; public:
QPointF topLeft;
QSize tile;
QSize matrix;
QRect limits;
Zoom() {}
Zoom(const QString &id, qreal scaleDenominator, const QPointF &topLeft, Zoom(const QString &id, qreal scaleDenominator, const QPointF &topLeft,
const QSize &tile, const QSize &matrix, const QRect &limits) : const QSize &tile, const QSize &matrix, const QRect &limits) :
id(id), scaleDenominator(scaleDenominator), topLeft(topLeft), _id(id), _scaleDenominator(scaleDenominator), _topLeft(topLeft),
tile(tile), matrix(matrix), limits(limits) {} _tile(tile), _matrix(matrix), _limits(limits) {}
bool operator<(const Zoom &other) const bool operator<(const Zoom &other) const
{return scaleDenominator > other.scaleDenominator;} {return _scaleDenominator > other._scaleDenominator;}
const QString &id() const {return _id;}
qreal scaleDenominator() const {return _scaleDenominator;}
const QPointF &topLeft() const {return _topLeft;}
const QSize &tile() const {return _tile;}
const QSize &matrix() const {return _matrix;}
const QRect &limits() const {return _limits;}
private:
QString _id;
qreal _scaleDenominator;
QPointF _topLeft;
QSize _tile;
QSize _matrix;
QRect _limits;
}; };
bool load(const QString &path, const Setup &setup);
const QString &errorString() const {return _errorString;} WMTS(const QString &path, const Setup &setup);
const RectC &bounds() const {return _bounds;} const RectC &bounds() const {return _bounds;}
QList<Zoom> zooms() const; QList<Zoom> zooms() const;
const Projection &projection() const {return _projection;} const Projection &projection() const {return _projection;}
QString tileUrl() const {return _tileUrl;} const QString &tileUrl() const {return _tileUrl;}
bool isValid() const {return _valid;}
const QString &errorString() const {return _errorString;}
static Downloader *downloader() {return _downloader;}
static void setDownloader(Downloader *downloader) static void setDownloader(Downloader *downloader)
{_downloader = downloader;} {_downloader = downloader;}
@ -113,7 +137,6 @@ private:
void capabilities(QXmlStreamReader &reader, CTX &ctx); void capabilities(QXmlStreamReader &reader, CTX &ctx);
bool parseCapabilities(const QString &path, const Setup &setup); bool parseCapabilities(const QString &path, const Setup &setup);
bool getCapabilities(const QString &url, const QString &file); bool getCapabilities(const QString &url, const QString &file);
bool createProjection(const QString &crs);
QSet<TileMatrix> _matrixes; QSet<TileMatrix> _matrixes;
QSet<MatrixLimits> _limits; QSet<MatrixLimits> _limits;
@ -121,6 +144,7 @@ private:
Projection _projection; Projection _projection;
QString _tileUrl; QString _tileUrl;
bool _valid;
QString _errorString; QString _errorString;
static Downloader *_downloader; static Downloader *_downloader;

View File

@ -10,30 +10,50 @@
#define CAPABILITIES_FILE "capabilities.xml" #define CAPABILITIES_FILE "capabilities.xml"
WMTSMap::WMTSMap(const QString &name, const WMTS::Setup &setup, QObject *parent) bool WMTSMap::loadWMTS()
: Map(parent), _name(name), _setup(setup), _zoom(0), _valid(false)
{ {
QString dir(TILES_DIR + "/" + _name); QString file = tilesDir() + "/" + CAPABILITIES_FILE;
QString file = dir + "/" + CAPABILITIES_FILE;
if (!QDir().mkpath(dir)) { WMTS wmts(file, _setup);
if (!wmts.isValid()) {
_errorString = wmts.errorString();
return false;
}
_bounds = wmts.bounds();
_zooms = wmts.zooms();
_projection = wmts.projection();
_tileLoader = TileLoader(wmts.tileUrl(), tilesDir());
updateTransform();
return true;
}
WMTSMap::WMTSMap(const QString &name, const WMTS::Setup &setup, QObject *parent)
: Map(parent), _name(name), _setup(setup), _zoom(0), _block(false),
_valid(false)
{
if (!QDir().mkpath(tilesDir())) {
_errorString = "Error creating tiles dir"; _errorString = "Error creating tiles dir";
return; return;
} }
WMTS wmts; _valid = loadWMTS();
if (!wmts.load(file, _setup)) { }
_errorString = wmts.errorString();
return;
}
_bounds = wmts.bounds();
_zooms = wmts.zooms();
_projection = wmts.projection();
_tileLoader = TileLoader(wmts.tileUrl(), dir);
updateTransform();
_block = false; void WMTSMap::clearCache()
_valid = true; {
_tileLoader.clearCache();
_zoom = 0;
if (!loadWMTS())
qWarning("%s: %s\n", qPrintable(_name), qPrintable(_errorString));
}
QString WMTSMap::tilesDir() const
{
return QString(TILES_DIR + "/" + _name);
} }
qreal WMTSMap::sd2res(qreal scaleDenominator) const qreal WMTSMap::sd2res(qreal scaleDenominator) const
@ -46,17 +66,17 @@ void WMTSMap::updateTransform()
const WMTS::Zoom &z = _zooms.at(_zoom); const WMTS::Zoom &z = _zooms.at(_zoom);
ReferencePoint tl, br; ReferencePoint tl, br;
qreal pixelSpan = sd2res(z.scaleDenominator); qreal pixelSpan = sd2res(z.scaleDenominator());
if (_projection.isGeographic()) if (_projection.isGeographic())
pixelSpan /= deg2rad(WGS84_RADIUS); pixelSpan /= deg2rad(WGS84_RADIUS);
QPointF tileSpan(z.tile.width() * pixelSpan, z.tile.height() * pixelSpan); QPointF tileSpan(z.tile().width() * pixelSpan, z.tile().height() * pixelSpan);
QPointF bottomRight(z.topLeft.x() + tileSpan.x() * z.matrix.width(), QPointF bottomRight(z.topLeft().x() + tileSpan.x() * z.matrix().width(),
z.topLeft.y() - tileSpan.y() * z.matrix.height()); z.topLeft().y() - tileSpan.y() * z.matrix().height());
tl.xy = QPoint(0, 0); tl.xy = QPoint(0, 0);
tl.pp = z.topLeft; tl.pp = z.topLeft();
br.xy = QPoint(z.tile.width() * z.matrix.width(), br.xy = QPoint(z.tile().width() * z.matrix().width(),
z.tile.height() * z.matrix.height()); z.tile().height() * z.matrix().height());
br.pp = bottomRight; br.pp = bottomRight;
QList<ReferencePoint> points; QList<ReferencePoint> points;
@ -76,26 +96,6 @@ void WMTSMap::unload()
SLOT(emitLoaded())); SLOT(emitLoaded()));
} }
void WMTSMap::clearCache()
{
QString dir(TILES_DIR + "/" + _name);
QString file = dir + "/" + CAPABILITIES_FILE;
_tileLoader.clearCache();
WMTS wmts;
if (!wmts.load(file, _setup))
return;
_bounds = wmts.bounds();
_zooms = wmts.zooms();
_projection = wmts.projection();
_tileLoader = TileLoader(wmts.tileUrl(), dir);
if (_zoom >= _zooms.size())
_zoom = _zooms.size() - 1;
updateTransform();
}
void WMTSMap::emitLoaded() void WMTSMap::emitLoaded()
{ {
emit loaded(); emit loaded();
@ -106,11 +106,12 @@ QRectF WMTSMap::bounds() const
const WMTS::Zoom &z = _zooms.at(_zoom); const WMTS::Zoom &z = _zooms.at(_zoom);
QRectF tileBounds, bounds; QRectF tileBounds, bounds;
tileBounds = (z.limits.isNull()) ? tileBounds = (z.limits().isNull()) ?
QRectF(QPointF(0, 0), QSize(z.tile.width() * z.matrix.width(), QRectF(QPointF(0, 0), QSize(z.tile().width() * z.matrix().width(),
z.tile.height() * z.matrix.height())) : QRectF(QPointF(z.limits.left() z.tile().height() * z.matrix().height()))
* z.tile.width(), z.limits.top() * z.tile.height()), QSize(z.tile.width() : QRectF(QPointF(z.limits().left() * z.tile().width(), z.limits().top()
* z.limits.width(), z.tile.height() * z.limits.height())); * z.tile().height()), QSize(z.tile().width() * z.limits().width(),
z.tile().height() * z.limits().height()));
bounds = _bounds.isValid() ? QRectF(ll2xy(_bounds.topLeft()), bounds = _bounds.isValid() ? QRectF(ll2xy(_bounds.topLeft()),
ll2xy(_bounds.bottomRight())) : QRectF(); ll2xy(_bounds.bottomRight())) : QRectF();
@ -130,7 +131,7 @@ int WMTSMap::zoomFit(const QSize &size, const RectC &br)
_zoom = 0; _zoom = 0;
for (int i = 0; i < _zooms.size(); i++) { for (int i = 0; i < _zooms.size(); i++) {
if (sd2res(_zooms.at(i).scaleDenominator) < resolution) if (sd2res(_zooms.at(i).scaleDenominator()) < resolution)
break; break;
_zoom = i; _zoom = i;
} }
@ -169,15 +170,15 @@ int WMTSMap::zoomOut()
void WMTSMap::draw(QPainter *painter, const QRectF &rect) void WMTSMap::draw(QPainter *painter, const QRectF &rect)
{ {
const WMTS::Zoom &z = _zooms.at(_zoom); const WMTS::Zoom &z = _zooms.at(_zoom);
QPoint tl = QPoint((int)floor(rect.left() / (qreal)z.tile.width()), QPoint tl = QPoint((int)floor(rect.left() / (qreal)z.tile().width()),
(int)floor(rect.top() / (qreal)z.tile.height())); (int)floor(rect.top() / (qreal)z.tile().height()));
QPoint br = QPoint((int)ceil(rect.right() / (qreal)z.tile.width()), QPoint br = QPoint((int)ceil(rect.right() / (qreal)z.tile().width()),
(int)ceil(rect.bottom() / (qreal)z.tile.height())); (int)ceil(rect.bottom() / (qreal)z.tile().height()));
QList<Tile> tiles; QList<Tile> tiles;
for (int i = tl.x(); i < br.x(); i++) for (int i = tl.x(); i < br.x(); i++)
for (int j = tl.y(); j < br.y(); j++) for (int j = tl.y(); j < br.y(); j++)
tiles.append(Tile(QPoint(i, j), z.id)); tiles.append(Tile(QPoint(i, j), z.id()));
if (_block) if (_block)
_tileLoader.loadTilesSync(tiles); _tileLoader.loadTilesSync(tiles);
@ -186,9 +187,9 @@ void WMTSMap::draw(QPainter *painter, const QRectF &rect)
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(t.xy().x() * z.tile.width(), t.xy().y() * z.tile.height()); QPoint tp(t.xy().x() * z.tile().width(), t.xy().y() * z.tile().height());
if (t.pixmap().isNull()) if (t.pixmap().isNull())
painter->fillRect(QRect(tp, z.tile), _backgroundColor); painter->fillRect(QRect(tp, z.tile()), _backgroundColor);
else else
painter->drawPixmap(tp, t.pixmap()); painter->drawPixmap(tp, t.pixmap());
} }

View File

@ -45,7 +45,9 @@ private slots:
void emitLoaded(); void emitLoaded();
private: private:
bool loadWMTS();
qreal sd2res(qreal scaleDenominator) const; qreal sd2res(qreal scaleDenominator) const;
QString tilesDir() const;
void updateTransform(); void updateTransform();
QPointF ll2xy(const Coordinates &c) const; QPointF ll2xy(const Coordinates &c) const;