mirror of
https://github.com/tumic0/GPXSee.git
synced 2024-11-24 03:35:53 +01:00
Initial WMS support (no multi-layer support for now)
This commit is contained in:
parent
8821536419
commit
bf3589812a
10
gpxsee.pro
10
gpxsee.pro
@ -119,7 +119,10 @@ HEADERS += src/config.h \
|
||||
src/map/mapsource.h \
|
||||
src/map/tileloader.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 \
|
||||
src/common/coordinates.cpp \
|
||||
src/common/rectc.cpp \
|
||||
@ -208,7 +211,10 @@ SOURCES += src/main.cpp \
|
||||
src/map/mapsource.cpp \
|
||||
src/map/tileloader.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
|
||||
TRANSLATIONS = lang/gpxsee_cs.ts \
|
||||
lang/gpxsee_sv.ts \
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <QNetworkProxyFactory>
|
||||
#include <QLibraryInfo>
|
||||
#include "map/wmts.h"
|
||||
#include "map/wms.h"
|
||||
#include "map/tileloader.h"
|
||||
#include "map/downloader.h"
|
||||
#include "map/ellipsoid.h"
|
||||
@ -40,6 +41,7 @@ App::App(int &argc, char **argv) : QApplication(argc, argv),
|
||||
Downloader *dl = new Downloader(this);
|
||||
TileLoader::setDownloader(dl);
|
||||
WMTS::setDownloader(dl);
|
||||
WMS::setDownloader(dl);
|
||||
OPENGL_SET_SAMPLES(4);
|
||||
loadDatums();
|
||||
loadPCSs();
|
||||
|
@ -179,7 +179,7 @@ QList<PathItem *> MapView::loadData(const Data &data)
|
||||
if (_tracks.empty() && _routes.empty() && _waypoints.empty())
|
||||
return paths;
|
||||
|
||||
if (mapZoom() != zoom)
|
||||
if (fitMapZoom() != zoom)
|
||||
rescale();
|
||||
else
|
||||
updatePOIVisibility();
|
||||
@ -189,7 +189,7 @@ QList<PathItem *> MapView::loadData(const Data &data)
|
||||
return paths;
|
||||
}
|
||||
|
||||
int MapView::mapZoom() const
|
||||
int MapView::fitMapZoom() const
|
||||
{
|
||||
RectC br = _tr | _rr | _wr;
|
||||
|
||||
@ -389,7 +389,10 @@ void MapView::setCoordinatesFormat(CoordinatesFormat format)
|
||||
void MapView::clearMapCache()
|
||||
{
|
||||
_map->clearCache();
|
||||
resetCachedContent();
|
||||
|
||||
fitMapZoom();
|
||||
rescale();
|
||||
centerOn(contentCenter());
|
||||
}
|
||||
|
||||
void MapView::digitalZoom(int zoom)
|
||||
@ -767,7 +770,7 @@ void MapView::resizeEvent(QResizeEvent *event)
|
||||
QGraphicsView::resizeEvent(event);
|
||||
|
||||
int zoom = _map->zoom();
|
||||
if (mapZoom() != zoom)
|
||||
if (fitMapZoom() != zoom)
|
||||
rescale();
|
||||
|
||||
centerOn(contentCenter());
|
||||
|
@ -80,7 +80,7 @@ private:
|
||||
void loadPOI();
|
||||
void clearPOI();
|
||||
|
||||
int mapZoom() const;
|
||||
int fitMapZoom() const;
|
||||
QPointF contentCenter() const;
|
||||
void rescale();
|
||||
void centerOn(const QPointF &pos);
|
||||
|
@ -16,6 +16,9 @@ public:
|
||||
|
||||
bool isValid() const {return size() >= 0;}
|
||||
|
||||
void setMin(int min) {_min = min;}
|
||||
void setMax(int max) {_max = max;}
|
||||
|
||||
private:
|
||||
int _min, _max;
|
||||
};
|
||||
@ -32,6 +35,9 @@ public:
|
||||
|
||||
bool isValid() const {return size() >= 0;}
|
||||
|
||||
void setMin(qreal min) {_min = min;}
|
||||
void setMax(qreal max) {_max = max;}
|
||||
|
||||
void resize(qreal size);
|
||||
|
||||
private:
|
||||
|
46
src/map/crs.cpp
Normal file
46
src/map/crs.cpp
Normal 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
11
src/map/crs.h
Normal file
@ -0,0 +1,11 @@
|
||||
#ifndef CRS_H
|
||||
#define CRS_H
|
||||
|
||||
#include "projection.h"
|
||||
|
||||
namespace CRS
|
||||
{
|
||||
Projection projection(const QString &crs);
|
||||
}
|
||||
|
||||
#endif // CRS_H
|
@ -2,6 +2,7 @@
|
||||
#include <QXmlStreamReader>
|
||||
#include "onlinemap.h"
|
||||
#include "wmtsmap.h"
|
||||
#include "wmsmap.h"
|
||||
#include "mapsource.h"
|
||||
|
||||
#define ZOOM_MAX 19
|
||||
@ -11,9 +12,10 @@
|
||||
#define BOUNDS_RIGHT 180
|
||||
#define BOUNDS_BOTTOM -85.0511
|
||||
|
||||
MapSource::TMSConfig::TMSConfig()
|
||||
: zooms(ZOOM_MIN, ZOOM_MAX), bounds(Coordinates(BOUNDS_LEFT, BOUNDS_TOP),
|
||||
Coordinates(BOUNDS_RIGHT, BOUNDS_BOTTOM)) {}
|
||||
|
||||
MapSource::Config::Config() : type(TMS), zooms(ZOOM_MIN, ZOOM_MAX),
|
||||
bounds(Coordinates(BOUNDS_LEFT, BOUNDS_TOP), Coordinates(BOUNDS_RIGHT,
|
||||
BOUNDS_BOTTOM)), format("image/png"), rest(false), yx(false) {}
|
||||
|
||||
|
||||
Range MapSource::zooms(QXmlStreamReader &reader)
|
||||
@ -23,11 +25,7 @@ Range MapSource::zooms(QXmlStreamReader &reader)
|
||||
bool res;
|
||||
|
||||
if (attr.hasAttribute("min")) {
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
|
||||
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)) {
|
||||
reader.raiseError("Invalid minimal zoom level");
|
||||
return Range();
|
||||
@ -36,11 +34,7 @@ Range MapSource::zooms(QXmlStreamReader &reader)
|
||||
min = ZOOM_MIN;
|
||||
|
||||
if (attr.hasAttribute("max")) {
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
|
||||
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)) {
|
||||
reader.raiseError("Invalid maximal zoom level");
|
||||
return Range();
|
||||
@ -63,11 +57,7 @@ RectC MapSource::bounds(QXmlStreamReader &reader)
|
||||
bool res;
|
||||
|
||||
if (attr.hasAttribute("top")) {
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
|
||||
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)) {
|
||||
reader.raiseError("Invalid bounds top value");
|
||||
return RectC();
|
||||
@ -76,11 +66,7 @@ RectC MapSource::bounds(QXmlStreamReader &reader)
|
||||
top = BOUNDS_TOP;
|
||||
|
||||
if (attr.hasAttribute("bottom")) {
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
|
||||
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)) {
|
||||
reader.raiseError("Invalid bounds bottom value");
|
||||
return RectC();
|
||||
@ -89,11 +75,7 @@ RectC MapSource::bounds(QXmlStreamReader &reader)
|
||||
bottom = BOUNDS_BOTTOM;
|
||||
|
||||
if (attr.hasAttribute("left")) {
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
|
||||
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)) {
|
||||
reader.raiseError("Invalid bounds left value");
|
||||
return RectC();
|
||||
@ -102,11 +84,7 @@ RectC MapSource::bounds(QXmlStreamReader &reader)
|
||||
left = BOUNDS_LEFT;
|
||||
|
||||
if (attr.hasAttribute("right")) {
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
|
||||
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)) {
|
||||
reader.raiseError("Invalid bounds right value");
|
||||
return RectC();
|
||||
@ -128,38 +106,44 @@ RectC MapSource::bounds(QXmlStreamReader &reader)
|
||||
|
||||
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()) {
|
||||
if (reader.name() == "name")
|
||||
config.name = reader.readElementText();
|
||||
else if (reader.name() == "url") {
|
||||
config.wmts.rest = (reader.attributes().value("type") == "REST")
|
||||
config.rest = (reader.attributes().value("type") == "REST")
|
||||
? true : false;
|
||||
config.url = reader.readElementText();
|
||||
} else if (reader.name() == "zoom") {
|
||||
config.tms.zooms = zooms(reader);
|
||||
config.zooms = zooms(reader);
|
||||
reader.skipCurrentElement();
|
||||
} else if (reader.name() == "bounds") {
|
||||
config.tms.bounds = bounds(reader);
|
||||
config.bounds = bounds(reader);
|
||||
reader.skipCurrentElement();
|
||||
} else if (reader.name() == "format") {
|
||||
config.wmts.format = reader.readElementText();
|
||||
config.format = reader.readElementText();
|
||||
} else if (reader.name() == "layer")
|
||||
config.wmts.layer = reader.readElementText();
|
||||
config.layer = reader.readElementText();
|
||||
else if (reader.name() == "style")
|
||||
config.wmts.style = reader.readElementText();
|
||||
config.style = reader.readElementText();
|
||||
else if (reader.name() == "set") {
|
||||
config.wmts.yx = (reader.attributes().value("axis") == "yx")
|
||||
config.yx = (reader.attributes().value("axis") == "yx")
|
||||
? true : false;
|
||||
config.wmts.set = reader.readElementText();
|
||||
config.set = reader.readElementText();
|
||||
} else if (reader.name() == "dimension") {
|
||||
QXmlStreamAttributes attr = reader.attributes();
|
||||
if (!attr.hasAttribute("id"))
|
||||
reader.raiseError("Missing dimension id");
|
||||
else
|
||||
config.wmts.dimensions.append(QPair<QString, QString>(
|
||||
config.dimensions.append(QPair<QString, QString>(
|
||||
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
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
@ -170,6 +154,8 @@ Map *MapSource::loadFile(const QString &path)
|
||||
QFile file(path);
|
||||
QXmlStreamReader reader;
|
||||
Config config;
|
||||
Map *m;
|
||||
|
||||
|
||||
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
||||
_errorString = file.errorString();
|
||||
@ -197,33 +183,43 @@ Map *MapSource::loadFile(const QString &path)
|
||||
_errorString = "Missing URL definition";
|
||||
return 0;
|
||||
}
|
||||
if (config.type == WMTS) {
|
||||
if (config.wmts.layer.isEmpty()) {
|
||||
if (config.type == WMTS || config.type == WMS) {
|
||||
if (config.layer.isEmpty()) {
|
||||
_errorString = "Missing layer definition";
|
||||
return 0;
|
||||
}
|
||||
if (config.wmts.style.isEmpty()) {
|
||||
if (config.style.isEmpty()) {
|
||||
_errorString = "Missing style definiton";
|
||||
return 0;
|
||||
}
|
||||
if (config.wmts.set.isEmpty()) {
|
||||
_errorString = "Missing set definiton";
|
||||
return 0;
|
||||
}
|
||||
if (config.wmts.format.isEmpty()) {
|
||||
if (config.format.isEmpty()) {
|
||||
_errorString = "Missing format definition";
|
||||
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)
|
||||
m = new WMTSMap(config.name, WMTS::Setup(config.url, config.wmts.layer,
|
||||
config.wmts.set, config.wmts.style, config.wmts.format,
|
||||
config.wmts.rest, config.wmts.yx, config.wmts.dimensions));
|
||||
m = new WMTSMap(config.name, WMTS::Setup(config.url, config.layer,
|
||||
config.set, config.style, config.format, config.rest, config.yx,
|
||||
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
|
||||
m = new OnlineMap(config.name, config.url, config.tms.zooms,
|
||||
config.tms.bounds);
|
||||
m = new OnlineMap(config.name, config.url, config.zooms, config.bounds);
|
||||
|
||||
if (!m->isValid()) {
|
||||
_errorString = m->errorString();
|
||||
delete m;
|
||||
|
@ -17,36 +17,26 @@ public:
|
||||
private:
|
||||
enum Type {
|
||||
TMS,
|
||||
WMTS
|
||||
WMTS,
|
||||
WMS
|
||||
};
|
||||
|
||||
struct TMSConfig {
|
||||
struct Config {
|
||||
Type type;
|
||||
QString name;
|
||||
QString url;
|
||||
Range zooms;
|
||||
RectC bounds;
|
||||
|
||||
TMSConfig();
|
||||
};
|
||||
|
||||
struct WMTSConfig {
|
||||
QString layer;
|
||||
QString style;
|
||||
QString set;
|
||||
QString format;
|
||||
QString crs;
|
||||
bool rest;
|
||||
bool yx;
|
||||
QList<QPair<QString, QString> > dimensions;
|
||||
|
||||
WMTSConfig() : format("image/png"), rest(false), yx(false) {}
|
||||
};
|
||||
|
||||
struct Config {
|
||||
QString name;
|
||||
QString url;
|
||||
Type type;
|
||||
WMTSConfig wmts;
|
||||
TMSConfig tms;
|
||||
|
||||
Config() : type(TMS) {}
|
||||
Config();
|
||||
};
|
||||
|
||||
RectC bounds(QXmlStreamReader &reader);
|
||||
|
@ -8,23 +8,26 @@
|
||||
class Tile
|
||||
{
|
||||
public:
|
||||
Tile(const QPoint &xy, const QVariant &zoom)
|
||||
{_xy = xy; _zoom = zoom;}
|
||||
Tile(const QPoint &xy, const QVariant &zoom, const QRectF &bbox = QRectF())
|
||||
{_xy = xy; _zoom = zoom; _bbox = bbox;}
|
||||
|
||||
QVariant zoom() const {return _zoom;}
|
||||
const QPoint& xy() const {return _xy;}
|
||||
const QVariant &zoom() const {return _zoom;}
|
||||
const QPoint &xy() const {return _xy;}
|
||||
const QRectF &bbox() const {return _bbox;}
|
||||
QPixmap& pixmap() {return _pixmap;}
|
||||
|
||||
private:
|
||||
QVariant _zoom;
|
||||
QPoint _xy;
|
||||
QRectF _bbox;
|
||||
QPixmap _pixmap;
|
||||
};
|
||||
|
||||
#ifndef QT_NO_DEBUG
|
||||
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();
|
||||
}
|
||||
#endif // QT_NO_DEBUG
|
||||
|
@ -85,9 +85,18 @@ QString TileLoader::tileUrl(const Tile &tile) const
|
||||
{
|
||||
QString url(_url);
|
||||
|
||||
url.replace("$z", tile.zoom().toString());
|
||||
url.replace("$x", QString::number(tile.xy().x()));
|
||||
url.replace("$y", QString::number(tile.xy().y()));
|
||||
if (!tile.bbox().isNull()) {
|
||||
QString bbox = QString("%1,%2,%3,%4").arg(
|
||||
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;
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ public:
|
||||
{_downloader = downloader;}
|
||||
|
||||
private:
|
||||
void fillTile(Tile &tile);
|
||||
QString tileUrl(const Tile &tile) const;
|
||||
QString tileFile(const Tile &tile) const;
|
||||
|
||||
|
215
src/map/wms.cpp
Normal file
215
src/map/wms.cpp
Normal 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
86
src/map/wms.h
Normal 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
230
src/map/wmsmap.cpp
Normal 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
73
src/map/wmsmap.h
Normal 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
|
118
src/map/wmts.cpp
118
src/map/wmts.cpp
@ -8,57 +8,12 @@
|
||||
#include <QXmlStreamReader>
|
||||
#include "downloader.h"
|
||||
#include "pcs.h"
|
||||
#include "crs.h"
|
||||
#include "wmts.h"
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
TileMatrix matrix;
|
||||
@ -104,12 +59,12 @@ void WMTS::tileMatrixSet(QXmlStreamReader &reader, CTX &ctx)
|
||||
else if (reader.name() == "SupportedCRS")
|
||||
crs = reader.readElementText();
|
||||
else if (reader.name() == "TileMatrix")
|
||||
matrixes.insert(tileMatrix(reader, ctx.setup.yx));
|
||||
matrixes.insert(tileMatrix(reader, ctx.setup.yx()));
|
||||
else
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
|
||||
if (id == ctx.setup.set) {
|
||||
if (id == ctx.setup.set()) {
|
||||
ctx.crs = crs;
|
||||
_matrixes = matrixes;
|
||||
}
|
||||
@ -168,7 +123,7 @@ void WMTS::tileMatrixSetLink(QXmlStreamReader &reader, CTX &ctx)
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
|
||||
if (id == ctx.setup.set) {
|
||||
if (id == ctx.setup.set()) {
|
||||
ctx.set = true;
|
||||
_limits = limits;
|
||||
}
|
||||
@ -232,14 +187,14 @@ void WMTS::layer(QXmlStreamReader &reader, CTX &ctx)
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
|
||||
if (id == ctx.setup.layer) {
|
||||
if (id == ctx.setup.layer()) {
|
||||
ctx.layer = true;
|
||||
_bounds = bounds;
|
||||
if (ctx.setup.rest)
|
||||
if (ctx.setup.rest())
|
||||
_tileUrl = tpl;
|
||||
if (styles.contains(ctx.setup.style))
|
||||
if (styles.contains(ctx.setup.style()))
|
||||
ctx.style = true;
|
||||
if (formats.contains(ctx.setup.format))
|
||||
if (formats.contains(ctx.setup.format()))
|
||||
ctx.format = true;
|
||||
}
|
||||
}
|
||||
@ -291,26 +246,27 @@ bool WMTS::parseCapabilities(const QString &path, const Setup &setup)
|
||||
}
|
||||
|
||||
if (!ctx.layer) {
|
||||
_errorString = ctx.setup.layer + ": layer not provided";
|
||||
_errorString = ctx.setup.layer() + ": layer not provided";
|
||||
return false;
|
||||
}
|
||||
if (!ctx.style) {
|
||||
_errorString = ctx.setup.style + ": style not provided";
|
||||
_errorString = ctx.setup.style() + ": style not provided";
|
||||
return false;
|
||||
}
|
||||
if (!ctx.setup.rest && !ctx.format) {
|
||||
_errorString = ctx.setup.format + ": format not provided";
|
||||
if (!ctx.setup.rest() && !ctx.format) {
|
||||
_errorString = ctx.setup.format() + ": format not provided";
|
||||
return false;
|
||||
}
|
||||
if (!ctx.set) {
|
||||
_errorString = ctx.setup.set + ": set not provided";
|
||||
_errorString = ctx.setup.set() + ": set not provided";
|
||||
return false;
|
||||
}
|
||||
if (ctx.crs.isNull()) {
|
||||
_errorString = "Missing CRS definition";
|
||||
return false;
|
||||
}
|
||||
if (!createProjection(ctx.crs)) {
|
||||
_projection = CRS::projection(ctx.crs);
|
||||
if (_projection.isNull()) {
|
||||
_errorString = ctx.crs + ": unknown CRS";
|
||||
return false;
|
||||
}
|
||||
@ -318,7 +274,7 @@ bool WMTS::parseCapabilities(const QString &path, const Setup &setup)
|
||||
_errorString = "No usable tile matrix found";
|
||||
return false;
|
||||
}
|
||||
if (ctx.setup.rest && _tileUrl.isNull()) {
|
||||
if (ctx.setup.rest() && _tileUrl.isNull()) {
|
||||
_errorString = "Missing tile URL template";
|
||||
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")
|
||||
.arg(setup.url);
|
||||
.arg(setup.url());
|
||||
|
||||
if (!QFileInfo(file).exists())
|
||||
if (!getCapabilities(capaUrl, file))
|
||||
return false;
|
||||
return;
|
||||
if (!parseCapabilities(file, setup))
|
||||
return false;
|
||||
return;
|
||||
|
||||
if (!setup.rest) {
|
||||
if (!setup.rest()) {
|
||||
_tileUrl = QString("%1?service=WMTS&Version=1.0.0&request=GetTile"
|
||||
"&Format=%2&Layer=%3&Style=%4&TileMatrixSet=%5&TileMatrix=$z"
|
||||
"&TileRow=$y&TileCol=$x").arg(setup.url).arg(setup.format)
|
||||
.arg(setup.layer).arg(setup.style).arg(setup.set);
|
||||
for (int i = 0; i < setup.dimensions.size(); i++) {
|
||||
const QPair<QString, QString> &dim = setup.dimensions.at(i);
|
||||
"&TileRow=$y&TileCol=$x").arg(setup.url()).arg(setup.format())
|
||||
.arg(setup.layer()).arg(setup.style()).arg(setup.set());
|
||||
for (int i = 0; i < setup.dimensions().size(); i++) {
|
||||
const QPair<QString, QString> &dim = setup.dimensions().at(i);
|
||||
_tileUrl.append(QString("&%1=%2").arg(dim.first).arg(dim.second));
|
||||
}
|
||||
} else {
|
||||
_tileUrl.replace("{Style}", setup.style, Qt::CaseInsensitive);
|
||||
_tileUrl.replace("{TileMatrixSet}", setup.set, Qt::CaseInsensitive);
|
||||
_tileUrl.replace("{Style}", setup.style(), Qt::CaseInsensitive);
|
||||
_tileUrl.replace("{TileMatrixSet}", setup.set(), Qt::CaseInsensitive);
|
||||
_tileUrl.replace("{TileMatrix}", "$z", Qt::CaseInsensitive);
|
||||
_tileUrl.replace("{TileRow}", "$y", Qt::CaseInsensitive);
|
||||
_tileUrl.replace("{TileCol}", "$x", Qt::CaseInsensitive);
|
||||
for (int i = 0; i < setup.dimensions.size(); i++) {
|
||||
const QPair<QString, QString> &dim = setup.dimensions.at(i);
|
||||
for (int i = 0; i < setup.dimensions().size(); i++) {
|
||||
const QPair<QString, QString> &dim = setup.dimensions().at(i);
|
||||
_tileUrl.replace(QString("{%1}").arg(dim.first), dim.second,
|
||||
Qt::CaseInsensitive);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
_valid = true;
|
||||
}
|
||||
|
||||
QList<WMTS::Zoom> WMTS::zooms() const
|
||||
@ -405,17 +361,17 @@ QList<WMTS::Zoom> WMTS::zooms() const
|
||||
#ifndef QT_NO_DEBUG
|
||||
QDebug operator<<(QDebug dbg, const WMTS::Setup &setup)
|
||||
{
|
||||
dbg.nospace() << "Setup(" << setup.url << ", " << setup.layer << ", "
|
||||
<< setup.set << ", " << setup.style << ", " << setup.format << ", "
|
||||
<< setup.rest << ")";
|
||||
dbg.nospace() << "Setup(" << setup.url() << ", " << setup.layer() << ", "
|
||||
<< setup.set() << ", " << setup.style() << ", " << setup.format() << ", "
|
||||
<< setup.rest() << ")";
|
||||
return dbg.space();
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug dbg, const WMTS::Zoom &zoom)
|
||||
{
|
||||
dbg.nospace() << "Zoom(" << zoom.id << ", " << zoom.scaleDenominator << ", "
|
||||
<< zoom.topLeft << ", " << zoom.tile << ", " << zoom.matrix << ", "
|
||||
<< zoom.limits << ")";
|
||||
dbg.nospace() << "Zoom(" << zoom.id() << ", " << zoom.scaleDenominator()
|
||||
<< ", " << zoom.topLeft() << ", " << zoom.tile() << ", " << zoom.matrix()
|
||||
<< ", " << zoom.limits() << ")";
|
||||
return dbg.space();
|
||||
}
|
||||
#endif // QT_NO_DEBUG
|
||||
|
@ -15,49 +15,73 @@ class QXmlStreamReader;
|
||||
class WMTS
|
||||
{
|
||||
public:
|
||||
struct Setup {
|
||||
QString url;
|
||||
QString layer;
|
||||
QString set;
|
||||
QString style;
|
||||
QString format;
|
||||
bool rest;
|
||||
bool yx;
|
||||
QList<QPair<QString, QString> > dimensions;
|
||||
|
||||
class Setup
|
||||
{
|
||||
public:
|
||||
Setup(const QString &url, const QString &layer, const QString &set,
|
||||
const QString &style, const QString &format, bool rest, bool yx,
|
||||
const QList<QPair<QString, QString> > &dimensions) :
|
||||
url(url), layer(layer), set(set), style(style), format(format),
|
||||
rest(rest), yx(yx), dimensions(dimensions) {}
|
||||
_url(url), _layer(layer), _set(set), _style(style), _format(format),
|
||||
_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 {
|
||||
QString id;
|
||||
qreal scaleDenominator;
|
||||
QPointF topLeft;
|
||||
QSize tile;
|
||||
QSize matrix;
|
||||
QRect limits;
|
||||
|
||||
Zoom() {}
|
||||
class Zoom
|
||||
{
|
||||
public:
|
||||
Zoom(const QString &id, qreal scaleDenominator, const QPointF &topLeft,
|
||||
const QSize &tile, const QSize &matrix, const QRect &limits) :
|
||||
id(id), scaleDenominator(scaleDenominator), topLeft(topLeft),
|
||||
tile(tile), matrix(matrix), limits(limits) {}
|
||||
_id(id), _scaleDenominator(scaleDenominator), _topLeft(topLeft),
|
||||
_tile(tile), _matrix(matrix), _limits(limits) {}
|
||||
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;}
|
||||
QList<Zoom> zooms() const;
|
||||
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)
|
||||
{_downloader = downloader;}
|
||||
|
||||
@ -113,7 +137,6 @@ private:
|
||||
void capabilities(QXmlStreamReader &reader, CTX &ctx);
|
||||
bool parseCapabilities(const QString &path, const Setup &setup);
|
||||
bool getCapabilities(const QString &url, const QString &file);
|
||||
bool createProjection(const QString &crs);
|
||||
|
||||
QSet<TileMatrix> _matrixes;
|
||||
QSet<MatrixLimits> _limits;
|
||||
@ -121,6 +144,7 @@ private:
|
||||
Projection _projection;
|
||||
QString _tileUrl;
|
||||
|
||||
bool _valid;
|
||||
QString _errorString;
|
||||
|
||||
static Downloader *_downloader;
|
||||
|
@ -10,30 +10,50 @@
|
||||
|
||||
#define CAPABILITIES_FILE "capabilities.xml"
|
||||
|
||||
WMTSMap::WMTSMap(const QString &name, const WMTS::Setup &setup, QObject *parent)
|
||||
: Map(parent), _name(name), _setup(setup), _zoom(0), _valid(false)
|
||||
bool WMTSMap::loadWMTS()
|
||||
{
|
||||
QString dir(TILES_DIR + "/" + _name);
|
||||
QString file = dir + "/" + CAPABILITIES_FILE;
|
||||
QString file = tilesDir() + "/" + 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";
|
||||
return;
|
||||
}
|
||||
|
||||
WMTS wmts;
|
||||
if (!wmts.load(file, _setup)) {
|
||||
_errorString = wmts.errorString();
|
||||
return;
|
||||
}
|
||||
_bounds = wmts.bounds();
|
||||
_zooms = wmts.zooms();
|
||||
_projection = wmts.projection();
|
||||
_tileLoader = TileLoader(wmts.tileUrl(), dir);
|
||||
updateTransform();
|
||||
_valid = loadWMTS();
|
||||
}
|
||||
|
||||
_block = false;
|
||||
_valid = true;
|
||||
void WMTSMap::clearCache()
|
||||
{
|
||||
_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
|
||||
@ -46,17 +66,17 @@ void WMTSMap::updateTransform()
|
||||
const WMTS::Zoom &z = _zooms.at(_zoom);
|
||||
ReferencePoint tl, br;
|
||||
|
||||
qreal pixelSpan = sd2res(z.scaleDenominator);
|
||||
qreal pixelSpan = sd2res(z.scaleDenominator());
|
||||
if (_projection.isGeographic())
|
||||
pixelSpan /= deg2rad(WGS84_RADIUS);
|
||||
QPointF tileSpan(z.tile.width() * pixelSpan, z.tile.height() * pixelSpan);
|
||||
QPointF bottomRight(z.topLeft.x() + tileSpan.x() * z.matrix.width(),
|
||||
z.topLeft.y() - tileSpan.y() * z.matrix.height());
|
||||
QPointF tileSpan(z.tile().width() * pixelSpan, z.tile().height() * pixelSpan);
|
||||
QPointF bottomRight(z.topLeft().x() + tileSpan.x() * z.matrix().width(),
|
||||
z.topLeft().y() - tileSpan.y() * z.matrix().height());
|
||||
|
||||
tl.xy = QPoint(0, 0);
|
||||
tl.pp = z.topLeft;
|
||||
br.xy = QPoint(z.tile.width() * z.matrix.width(),
|
||||
z.tile.height() * z.matrix.height());
|
||||
tl.pp = z.topLeft();
|
||||
br.xy = QPoint(z.tile().width() * z.matrix().width(),
|
||||
z.tile().height() * z.matrix().height());
|
||||
br.pp = bottomRight;
|
||||
|
||||
QList<ReferencePoint> points;
|
||||
@ -76,26 +96,6 @@ void WMTSMap::unload()
|
||||
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()
|
||||
{
|
||||
emit loaded();
|
||||
@ -106,11 +106,12 @@ QRectF WMTSMap::bounds() const
|
||||
const WMTS::Zoom &z = _zooms.at(_zoom);
|
||||
QRectF tileBounds, bounds;
|
||||
|
||||
tileBounds = (z.limits.isNull()) ?
|
||||
QRectF(QPointF(0, 0), QSize(z.tile.width() * z.matrix.width(),
|
||||
z.tile.height() * z.matrix.height())) : QRectF(QPointF(z.limits.left()
|
||||
* z.tile.width(), z.limits.top() * z.tile.height()), QSize(z.tile.width()
|
||||
* z.limits.width(), z.tile.height() * z.limits.height()));
|
||||
tileBounds = (z.limits().isNull()) ?
|
||||
QRectF(QPointF(0, 0), QSize(z.tile().width() * z.matrix().width(),
|
||||
z.tile().height() * z.matrix().height()))
|
||||
: QRectF(QPointF(z.limits().left() * z.tile().width(), z.limits().top()
|
||||
* z.tile().height()), QSize(z.tile().width() * z.limits().width(),
|
||||
z.tile().height() * z.limits().height()));
|
||||
|
||||
bounds = _bounds.isValid() ? QRectF(ll2xy(_bounds.topLeft()),
|
||||
ll2xy(_bounds.bottomRight())) : QRectF();
|
||||
@ -130,7 +131,7 @@ int WMTSMap::zoomFit(const QSize &size, const RectC &br)
|
||||
|
||||
_zoom = 0;
|
||||
for (int i = 0; i < _zooms.size(); i++) {
|
||||
if (sd2res(_zooms.at(i).scaleDenominator) < resolution)
|
||||
if (sd2res(_zooms.at(i).scaleDenominator()) < resolution)
|
||||
break;
|
||||
_zoom = i;
|
||||
}
|
||||
@ -169,15 +170,15 @@ int WMTSMap::zoomOut()
|
||||
void WMTSMap::draw(QPainter *painter, const QRectF &rect)
|
||||
{
|
||||
const WMTS::Zoom &z = _zooms.at(_zoom);
|
||||
QPoint tl = QPoint((int)floor(rect.left() / (qreal)z.tile.width()),
|
||||
(int)floor(rect.top() / (qreal)z.tile.height()));
|
||||
QPoint br = QPoint((int)ceil(rect.right() / (qreal)z.tile.width()),
|
||||
(int)ceil(rect.bottom() / (qreal)z.tile.height()));
|
||||
QPoint tl = QPoint((int)floor(rect.left() / (qreal)z.tile().width()),
|
||||
(int)floor(rect.top() / (qreal)z.tile().height()));
|
||||
QPoint br = QPoint((int)ceil(rect.right() / (qreal)z.tile().width()),
|
||||
(int)ceil(rect.bottom() / (qreal)z.tile().height()));
|
||||
|
||||
QList<Tile> tiles;
|
||||
for (int i = tl.x(); i < br.x(); i++)
|
||||
for (int j = tl.y(); j < br.y(); j++)
|
||||
tiles.append(Tile(QPoint(i, j), z.id));
|
||||
tiles.append(Tile(QPoint(i, j), z.id()));
|
||||
|
||||
if (_block)
|
||||
_tileLoader.loadTilesSync(tiles);
|
||||
@ -186,9 +187,9 @@ void WMTSMap::draw(QPainter *painter, const QRectF &rect)
|
||||
|
||||
for (int i = 0; i < tiles.count(); i++) {
|
||||
Tile &t = tiles[i];
|
||||
QPoint tp(t.xy().x() * z.tile.width(), t.xy().y() * z.tile.height());
|
||||
QPoint tp(t.xy().x() * z.tile().width(), t.xy().y() * z.tile().height());
|
||||
if (t.pixmap().isNull())
|
||||
painter->fillRect(QRect(tp, z.tile), _backgroundColor);
|
||||
painter->fillRect(QRect(tp, z.tile()), _backgroundColor);
|
||||
else
|
||||
painter->drawPixmap(tp, t.pixmap());
|
||||
}
|
||||
|
@ -45,7 +45,9 @@ private slots:
|
||||
void emitLoaded();
|
||||
|
||||
private:
|
||||
bool loadWMTS();
|
||||
qreal sd2res(qreal scaleDenominator) const;
|
||||
QString tilesDir() const;
|
||||
void updateTransform();
|
||||
|
||||
QPointF ll2xy(const Coordinates &c) const;
|
||||
|
Loading…
Reference in New Issue
Block a user