2018-02-20 23:37:19 +01:00
|
|
|
#include <QXmlStreamReader>
|
|
|
|
#include <QFile>
|
|
|
|
#include <QFileInfo>
|
|
|
|
#include <QEventLoop>
|
|
|
|
#include <QTextStream>
|
2018-02-21 00:13:11 +01:00
|
|
|
#include <QStringList>
|
2018-02-24 10:34:27 +01:00
|
|
|
#include <QtAlgorithms>
|
2018-02-20 23:37:19 +01:00
|
|
|
#include "downloader.h"
|
|
|
|
#include "pcs.h"
|
|
|
|
#include "wmts.h"
|
|
|
|
|
|
|
|
|
|
|
|
Downloader *WMTS::_downloader = 0;
|
|
|
|
|
|
|
|
bool WMTS::createProjection(const QString &crs)
|
|
|
|
{
|
|
|
|
QStringList list(crs.split(':'));
|
|
|
|
QString authority, code;
|
2018-02-22 21:02:56 +01:00
|
|
|
bool res;
|
|
|
|
int epsg;
|
2018-02-20 23:37:19 +01:00
|
|
|
const PCS *pcs;
|
2018-02-22 21:02:56 +01:00
|
|
|
const GCS *gcs;
|
2018-02-20 23:37:19 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-02-24 11:20:29 +01:00
|
|
|
if (authority == "EPSG") {
|
|
|
|
epsg = code.toInt(&res);
|
|
|
|
if (!res)
|
|
|
|
return false;
|
2018-02-20 23:37:19 +01:00
|
|
|
|
2018-02-24 11:20:29 +01:00
|
|
|
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;
|
2018-02-22 21:02:56 +01:00
|
|
|
} else
|
|
|
|
return false;
|
2018-02-20 23:37:19 +01:00
|
|
|
}
|
|
|
|
|
2018-02-25 08:58:40 +01:00
|
|
|
WMTS::TileMatrix WMTS::tileMatrix(QXmlStreamReader &reader, bool yx)
|
2018-02-20 23:37:19 +01:00
|
|
|
{
|
2018-02-24 16:44:30 +01:00
|
|
|
TileMatrix matrix;
|
2018-02-20 23:37:19 +01:00
|
|
|
|
|
|
|
while (reader.readNextStartElement()) {
|
2018-02-24 01:59:03 +01:00
|
|
|
if (reader.name() == "Identifier")
|
2018-02-24 16:44:30 +01:00
|
|
|
matrix.id = reader.readElementText();
|
2018-02-24 01:59:03 +01:00
|
|
|
else if (reader.name() == "ScaleDenominator")
|
2018-02-24 16:44:30 +01:00
|
|
|
matrix.scaleDenominator = reader.readElementText().toDouble();
|
2018-02-20 23:37:19 +01:00
|
|
|
else if (reader.name() == "TopLeftCorner") {
|
|
|
|
QString str = reader.readElementText();
|
2018-02-25 08:58:40 +01:00
|
|
|
QTextStream ts(&str);
|
|
|
|
if (yx)
|
|
|
|
ts >> matrix.topLeft.ry() >> matrix.topLeft.rx();
|
|
|
|
else
|
|
|
|
ts >> matrix.topLeft.rx() >> matrix.topLeft.ry();
|
2018-02-20 23:37:19 +01:00
|
|
|
} else if (reader.name() == "TileWidth")
|
2018-02-24 16:44:30 +01:00
|
|
|
matrix.tile.setWidth(reader.readElementText().toInt());
|
2018-02-20 23:37:19 +01:00
|
|
|
else if (reader.name() == "TileHeight")
|
2018-02-24 16:44:30 +01:00
|
|
|
matrix.tile.setHeight(reader.readElementText().toInt());
|
2018-02-20 23:37:19 +01:00
|
|
|
else if (reader.name() == "MatrixWidth")
|
2018-02-24 16:44:30 +01:00
|
|
|
matrix.matrix.setWidth(reader.readElementText().toInt());
|
2018-02-20 23:37:19 +01:00
|
|
|
else if (reader.name() == "MatrixHeight")
|
2018-02-24 16:44:30 +01:00
|
|
|
matrix.matrix.setHeight(reader.readElementText().toInt());
|
2018-02-20 23:37:19 +01:00
|
|
|
else
|
|
|
|
reader.skipCurrentElement();
|
|
|
|
}
|
|
|
|
|
2018-02-24 16:44:30 +01:00
|
|
|
if (!matrix.isValid())
|
|
|
|
reader.raiseError("Invalid TileMatrix definition");
|
|
|
|
|
|
|
|
return matrix;
|
2018-02-20 23:37:19 +01:00
|
|
|
}
|
|
|
|
|
2018-02-25 08:58:40 +01:00
|
|
|
void WMTS::tileMatrixSet(QXmlStreamReader &reader, const QString &set, bool yx)
|
2018-02-20 23:37:19 +01:00
|
|
|
{
|
2018-02-24 16:44:30 +01:00
|
|
|
QString id, crs;
|
|
|
|
QSet<TileMatrix> matrixes;
|
2018-02-20 23:37:19 +01:00
|
|
|
|
|
|
|
while (reader.readNextStartElement()) {
|
|
|
|
if (reader.name() == "Identifier")
|
|
|
|
id = reader.readElementText();
|
2018-02-24 16:44:30 +01:00
|
|
|
else if (reader.name() == "SupportedCRS")
|
|
|
|
crs = reader.readElementText();
|
|
|
|
else if (reader.name() == "TileMatrix")
|
2018-02-25 08:58:40 +01:00
|
|
|
matrixes.insert(tileMatrix(reader, yx));
|
2018-02-20 23:37:19 +01:00
|
|
|
else
|
|
|
|
reader.skipCurrentElement();
|
|
|
|
}
|
2018-02-24 16:44:30 +01:00
|
|
|
|
|
|
|
if (id == set) {
|
|
|
|
if (!createProjection(crs)) {
|
|
|
|
reader.raiseError("Invalid/unknown CRS");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_matrixes.unite(matrixes);
|
|
|
|
}
|
2018-02-20 23:37:19 +01:00
|
|
|
}
|
|
|
|
|
2018-02-24 16:44:30 +01:00
|
|
|
WMTS::MatrixLimits WMTS::tileMatrixLimits(QXmlStreamReader &reader)
|
2018-02-24 01:59:03 +01:00
|
|
|
{
|
2018-02-24 16:44:30 +01:00
|
|
|
MatrixLimits limits;
|
2018-02-24 01:59:03 +01:00
|
|
|
|
|
|
|
while (reader.readNextStartElement()) {
|
|
|
|
if (reader.name() == "TileMatrix")
|
2018-02-24 16:44:30 +01:00
|
|
|
limits.id = reader.readElementText();
|
2018-02-24 01:59:03 +01:00
|
|
|
else if (reader.name() == "MinTileRow")
|
2018-02-24 16:44:30 +01:00
|
|
|
limits.rect.setTop(reader.readElementText().toInt());
|
2018-02-24 01:59:03 +01:00
|
|
|
else if (reader.name() == "MaxTileRow")
|
2018-02-24 16:44:30 +01:00
|
|
|
limits.rect.setBottom(reader.readElementText().toInt());
|
2018-02-24 01:59:03 +01:00
|
|
|
else if (reader.name() == "MinTileCol")
|
2018-02-24 16:44:30 +01:00
|
|
|
limits.rect.setLeft(reader.readElementText().toInt());
|
2018-02-24 01:59:03 +01:00
|
|
|
else if (reader.name() == "MaxTileCol")
|
2018-02-24 16:44:30 +01:00
|
|
|
limits.rect.setRight(reader.readElementText().toInt());
|
2018-02-24 01:59:03 +01:00
|
|
|
else
|
|
|
|
reader.skipCurrentElement();
|
|
|
|
}
|
|
|
|
|
2018-02-24 16:44:30 +01:00
|
|
|
if (!limits.isValid())
|
|
|
|
reader.raiseError("Invalid TileMatrixLimits definition");
|
|
|
|
|
|
|
|
return limits;
|
2018-02-24 01:59:03 +01:00
|
|
|
}
|
|
|
|
|
2018-02-24 16:44:30 +01:00
|
|
|
QSet<WMTS::MatrixLimits> WMTS::tileMatrixSetLimits(QXmlStreamReader &reader)
|
2018-02-24 01:59:03 +01:00
|
|
|
{
|
2018-02-24 16:44:30 +01:00
|
|
|
QSet<MatrixLimits> limits;
|
|
|
|
|
2018-02-24 01:59:03 +01:00
|
|
|
while (reader.readNextStartElement()) {
|
|
|
|
if (reader.name() == "TileMatrixLimits")
|
2018-02-24 16:44:30 +01:00
|
|
|
limits.insert(tileMatrixLimits(reader));
|
2018-02-24 01:59:03 +01:00
|
|
|
else
|
|
|
|
reader.skipCurrentElement();
|
|
|
|
}
|
2018-02-24 16:44:30 +01:00
|
|
|
|
|
|
|
return limits;
|
2018-02-24 01:59:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void WMTS::tileMatrixSetLink(QXmlStreamReader &reader, const QString &set)
|
|
|
|
{
|
|
|
|
QString id;
|
2018-02-24 16:44:30 +01:00
|
|
|
QSet<MatrixLimits> limits;
|
2018-02-24 01:59:03 +01:00
|
|
|
|
|
|
|
while (reader.readNextStartElement()) {
|
|
|
|
if (reader.name() == "TileMatrixSet")
|
|
|
|
id = reader.readElementText();
|
2018-02-24 16:44:30 +01:00
|
|
|
else if (reader.name() == "TileMatrixSetLimits")
|
|
|
|
limits = tileMatrixSetLimits(reader);
|
2018-02-24 01:59:03 +01:00
|
|
|
else
|
|
|
|
reader.skipCurrentElement();
|
|
|
|
}
|
2018-02-24 16:44:30 +01:00
|
|
|
|
|
|
|
if (id == set)
|
|
|
|
_limits.unite(limits);
|
|
|
|
}
|
|
|
|
|
|
|
|
RectC WMTS::wgs84BoundingBox(QXmlStreamReader &reader)
|
|
|
|
{
|
|
|
|
Coordinates topLeft, bottomRight;
|
|
|
|
|
|
|
|
while (reader.readNextStartElement()) {
|
|
|
|
if (reader.name() == "LowerCorner") {
|
|
|
|
QString str = reader.readElementText();
|
|
|
|
QTextStream(&str) >> topLeft.rlon() >> bottomRight.rlat();
|
|
|
|
} else if (reader.name() == "UpperCorner") {
|
|
|
|
QString str = reader.readElementText();
|
|
|
|
QTextStream(&str) >> bottomRight.rlon() >> topLeft.rlat();
|
|
|
|
} else
|
|
|
|
reader.skipCurrentElement();
|
|
|
|
}
|
|
|
|
|
|
|
|
return RectC(topLeft, bottomRight);
|
2018-02-24 01:59:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void WMTS::layer(QXmlStreamReader &reader, const QString &layer,
|
|
|
|
const QString &set)
|
|
|
|
{
|
|
|
|
QString id;
|
2018-02-24 16:44:30 +01:00
|
|
|
RectC bounds;
|
2018-02-25 02:31:01 +01:00
|
|
|
QString tpl;
|
2018-02-24 01:59:03 +01:00
|
|
|
|
|
|
|
while (reader.readNextStartElement()) {
|
|
|
|
if (reader.name() == "Identifier")
|
|
|
|
id = reader.readElementText();
|
2018-02-24 16:44:30 +01:00
|
|
|
else if (reader.name() == "TileMatrixSetLink")
|
2018-02-24 01:59:03 +01:00
|
|
|
tileMatrixSetLink(reader, set);
|
2018-02-24 16:44:30 +01:00
|
|
|
else if (reader.name() == "WGS84BoundingBox")
|
|
|
|
bounds = wgs84BoundingBox(reader);
|
2018-02-25 02:31:01 +01:00
|
|
|
else if (reader.name() == "ResourceURL") {
|
|
|
|
const QXmlStreamAttributes &attr = reader.attributes();
|
|
|
|
if (attr.value("resourceType") == "tile")
|
|
|
|
tpl = attr.value("template").toString();
|
|
|
|
reader.skipCurrentElement();
|
|
|
|
} else
|
2018-02-24 01:59:03 +01:00
|
|
|
reader.skipCurrentElement();
|
|
|
|
}
|
2018-02-24 16:44:30 +01:00
|
|
|
|
2018-02-25 02:31:01 +01:00
|
|
|
if (id == layer) {
|
2018-02-24 16:44:30 +01:00
|
|
|
_bounds = bounds;
|
2018-02-25 02:31:01 +01:00
|
|
|
_tileUrl = tpl;
|
|
|
|
}
|
2018-02-24 01:59:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void WMTS::contents(QXmlStreamReader &reader, const QString &layer,
|
2018-02-25 08:58:40 +01:00
|
|
|
const QString &set, bool yx)
|
2018-02-20 23:37:19 +01:00
|
|
|
{
|
|
|
|
while (reader.readNextStartElement()) {
|
|
|
|
if (reader.name() == "TileMatrixSet")
|
2018-02-25 08:58:40 +01:00
|
|
|
tileMatrixSet(reader, set, yx);
|
2018-02-24 01:59:03 +01:00
|
|
|
else if (reader.name() == "Layer")
|
|
|
|
WMTS::layer(reader, layer, set);
|
2018-02-20 23:37:19 +01:00
|
|
|
else
|
|
|
|
reader.skipCurrentElement();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-24 01:59:03 +01:00
|
|
|
void WMTS::capabilities(QXmlStreamReader &reader, const QString &layer,
|
2018-02-25 08:58:40 +01:00
|
|
|
const QString &set, bool yx)
|
2018-02-20 23:37:19 +01:00
|
|
|
{
|
|
|
|
while (reader.readNextStartElement()) {
|
|
|
|
if (reader.name() == "Contents")
|
2018-02-25 08:58:40 +01:00
|
|
|
contents(reader, layer, set, yx);
|
2018-02-20 23:37:19 +01:00
|
|
|
else
|
|
|
|
reader.skipCurrentElement();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-24 01:59:03 +01:00
|
|
|
bool WMTS::parseCapabilities(const QString &path, const QString &layer,
|
2018-02-25 08:58:40 +01:00
|
|
|
const QString &set, bool yx)
|
2018-02-20 23:37:19 +01:00
|
|
|
{
|
|
|
|
QFile file(path);
|
|
|
|
QXmlStreamReader reader;
|
|
|
|
|
|
|
|
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
|
|
|
_errorString = file.errorString();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
reader.setDevice(&file);
|
|
|
|
|
|
|
|
if (reader.readNextStartElement()) {
|
|
|
|
if (reader.name() == "Capabilities")
|
2018-02-25 08:58:40 +01:00
|
|
|
capabilities(reader, layer, set, yx);
|
2018-02-20 23:37:19 +01:00
|
|
|
else
|
|
|
|
reader.raiseError("Not a Capabilities XML file");
|
|
|
|
}
|
|
|
|
|
|
|
|
_errorString = reader.error() ? QString("%1:%2: %3").arg(path)
|
|
|
|
.arg(reader.lineNumber()).arg(reader.errorString()) : QString();
|
|
|
|
|
|
|
|
return reader.error() ? false : true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool WMTS::getCapabilities(const QString &url, const QString &file)
|
|
|
|
{
|
|
|
|
QList<Download> dl;
|
|
|
|
|
2018-02-25 02:31:01 +01:00
|
|
|
dl.append(Download(url, file));
|
2018-02-20 23:37:19 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-25 02:31:01 +01:00
|
|
|
bool WMTS::load(const QString &file, const WMTS::Setup &setup)
|
2018-02-20 23:37:19 +01:00
|
|
|
{
|
2018-02-25 02:31:01 +01:00
|
|
|
QString capaUrl = setup.rest ? setup.url :
|
|
|
|
QString("%1?service=WMTS&Version=1.0.0&request=GetCapabilities")
|
|
|
|
.arg(setup.url);
|
2018-02-24 11:40:54 +01:00
|
|
|
|
2018-02-20 23:37:19 +01:00
|
|
|
if (!QFileInfo(file).exists())
|
2018-02-25 02:31:01 +01:00
|
|
|
if (!getCapabilities(capaUrl, file))
|
2018-02-20 23:37:19 +01:00
|
|
|
return false;
|
2018-02-25 08:58:40 +01:00
|
|
|
if (!parseCapabilities(file, setup.layer, setup.set, setup.yx))
|
2018-02-20 23:37:19 +01:00
|
|
|
return false;
|
|
|
|
|
2018-02-25 02:31:01 +01:00
|
|
|
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);
|
|
|
|
else {
|
|
|
|
_tileUrl.replace("{Style}", setup.style);
|
|
|
|
_tileUrl.replace("{TileMatrixSet}", setup.set);
|
|
|
|
_tileUrl.replace("{TileMatrix}", "$z");
|
|
|
|
_tileUrl.replace("{TileRow}", "$y");
|
|
|
|
_tileUrl.replace("{TileCol}", "$x");
|
|
|
|
}
|
|
|
|
|
2018-02-24 16:44:30 +01:00
|
|
|
if (_matrixes.isEmpty()) {
|
|
|
|
_errorString = "No usable tile matrix found";
|
2018-02-20 23:37:19 +01:00
|
|
|
return false;
|
|
|
|
}
|
2018-02-24 16:44:30 +01:00
|
|
|
if (_projection.isNull()) {
|
|
|
|
_errorString = "Missing CRS definition";
|
2018-02-20 23:37:19 +01:00
|
|
|
return false;
|
|
|
|
}
|
2018-02-25 02:31:01 +01:00
|
|
|
if (_tileUrl.isNull()) {
|
|
|
|
_errorString = "Missing tile URL";
|
|
|
|
return false;
|
|
|
|
}
|
2018-02-20 23:37:19 +01:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2018-02-24 01:59:03 +01:00
|
|
|
|
2018-02-24 10:34:27 +01:00
|
|
|
QList<WMTS::Zoom> WMTS::zooms() const
|
|
|
|
{
|
2018-02-24 16:44:30 +01:00
|
|
|
QList<Zoom> zooms;
|
|
|
|
QSet<TileMatrix>::const_iterator mi;
|
|
|
|
QSet<MatrixLimits>::const_iterator li;
|
|
|
|
|
|
|
|
for (mi = _matrixes.constBegin(); mi != _matrixes.constEnd(); ++mi) {
|
|
|
|
if ((li = _limits.find(MatrixLimits(mi->id))) == _limits.constEnd())
|
|
|
|
zooms.append(Zoom(mi->id, mi->scaleDenominator, mi->topLeft,
|
|
|
|
mi->tile, mi->matrix, QRect()));
|
|
|
|
else
|
|
|
|
zooms.append(Zoom(Zoom(mi->id, mi->scaleDenominator, mi->topLeft,
|
|
|
|
mi->tile, mi->matrix, li->rect)));
|
|
|
|
}
|
|
|
|
|
|
|
|
qSort(zooms);
|
|
|
|
|
|
|
|
return zooms;
|
2018-02-24 10:34:27 +01:00
|
|
|
}
|
|
|
|
|
2018-02-24 01:59:03 +01:00
|
|
|
#ifndef QT_NO_DEBUG
|
2018-02-25 02:31:01 +01:00
|
|
|
QDebug operator<<(QDebug dbg, const WMTS::Setup &setup)
|
|
|
|
{
|
|
|
|
dbg.nospace() << "Setup(" << setup.url << ", " << setup.layer << ", "
|
|
|
|
<< setup.set << ", " << setup.style << ", " << setup.format << ", "
|
|
|
|
<< setup.rest << ")";
|
|
|
|
return dbg.space();
|
|
|
|
}
|
|
|
|
|
2018-02-24 01:59:03 +01:00
|
|
|
QDebug operator<<(QDebug dbg, const WMTS::Zoom &zoom)
|
|
|
|
{
|
2018-02-24 16:44:30 +01:00
|
|
|
dbg.nospace() << "Zoom(" << zoom.id << ", " << zoom.scaleDenominator << ", "
|
|
|
|
<< zoom.topLeft << ", " << zoom.tile << ", " << zoom.matrix << ", "
|
|
|
|
<< zoom.limits << ")";
|
2018-02-24 01:59:03 +01:00
|
|
|
return dbg.space();
|
|
|
|
}
|
|
|
|
#endif // QT_NO_DEBUG
|