214 lines
7.0 KiB
C++
Executable File
214 lines
7.0 KiB
C++
Executable File
/**
|
|
* @RDE_HEADER@
|
|
* Tile Map Service Layer for Esri
|
|
* $Id$
|
|
* $HeadURL$
|
|
* $Author$
|
|
* License: BSD 2-Clause
|
|
*/
|
|
|
|
|
|
#include "tms_layer.h"
|
|
|
|
#include <QCoreApplication>
|
|
#include <QDomDocument>
|
|
#include <QNetworkAccessManager>
|
|
|
|
|
|
namespace EsriExtension
|
|
{
|
|
|
|
TMSLayer::TMSLayer(const QString &url, QObject *parent)
|
|
: EsriRuntimeQt::TiledMapServiceLayer(url, parent)
|
|
, m_nam(nullptr)
|
|
{
|
|
connect(this, SIGNAL(requestTile(int,int,int)),
|
|
this, SLOT(onRequestTMSTile(int,int,int)));
|
|
}
|
|
|
|
TMSLayer::~TMSLayer()
|
|
{
|
|
delete m_nam;
|
|
}
|
|
|
|
void TMSLayer::initialize()
|
|
{
|
|
if (!m_nam)
|
|
{
|
|
m_nam = new QNetworkAccessManager(this);
|
|
connect(m_nam, SIGNAL(finished(QNetworkReply*)),
|
|
this, SLOT(replyBaseXmlFinished(QNetworkReply*)));
|
|
}
|
|
|
|
QNetworkReply *reply = m_nam->get(QNetworkRequest(QUrl(url())));
|
|
connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
|
|
this, SLOT(replyBaseXmlError(QNetworkReply::NetworkError)));
|
|
}
|
|
|
|
void TMSLayer::replyBaseXmlFinished(QNetworkReply *reply)
|
|
{
|
|
if (!reply || reply->error() != QNetworkReply::NoError)
|
|
{
|
|
cleanupNetworkReply(reply);
|
|
return;
|
|
}
|
|
QDomDocument doc(QLatin1String("TMSLayerDoc"));
|
|
doc.setContent(reply);
|
|
const QDomElement domRoot = doc.documentElement();
|
|
const QDomElement domSRS = domRoot.firstChildElement(QLatin1String("SRS"));
|
|
const QDomElement domBoundingBox = domRoot.firstChildElement(QLatin1String("BoundingBox"));
|
|
const QDomElement domOrigin = domRoot.firstChildElement(QLatin1String("Origin"));
|
|
const QDomElement domTileFormat = domRoot.firstChildElement(QLatin1String("TileFormat"));
|
|
const QDomElement domTileSets = domRoot.firstChildElement(QLatin1String("TileSets"));
|
|
|
|
if (domSRS.isNull() ||
|
|
domBoundingBox.isNull() ||
|
|
domOrigin.isNull() ||
|
|
domTileFormat.isNull() ||
|
|
domTileSets.isNull())
|
|
{
|
|
cleanupNetworkReply(reply);
|
|
const QString errStr(QString(QLatin1String("Got invalid xml from tms server (url: %1)")).arg(url()));
|
|
layerCreateError(errStr);
|
|
qWarning("%s", qPrintable(errStr));
|
|
return;
|
|
}
|
|
|
|
// optional
|
|
if (domTileFormat.hasAttribute(QLatin1String("extension")))
|
|
m_extension = domTileFormat.attribute(QLatin1String("extension"));
|
|
|
|
//LODs:
|
|
const double dpi = 90.7; // according to spec
|
|
const double ipf = 12.0; // inch per foot
|
|
const double dpu = dpi * ipf; // dots per unit
|
|
|
|
// We expect a SRS string in the format 'EPSG:<number>' here ...
|
|
const EsriRuntimeQt::SpatialReference sr(domSRS.text().split(QLatin1Char(':')).at(1).toInt());
|
|
// Setting the image format is not needed (the image reader correctly guesses the format)
|
|
// Setting the spatial reference is also not needed - if not set, the default one
|
|
// from the layer is used.
|
|
EsriRuntimeQt::TileInfo tileInfo;
|
|
|
|
tileInfo.setCols(domTileFormat.attribute(QLatin1String("width")).toInt());
|
|
tileInfo.setRows(domTileFormat.attribute(QLatin1String("height")).toInt());
|
|
|
|
tileInfo.setDpi(dpi);
|
|
|
|
EsriRuntimeQt::LOD lod;
|
|
QList<EsriRuntimeQt::LOD> lods;
|
|
const double minX = domBoundingBox.attribute(QLatin1String("minx")).toDouble();
|
|
const double minY = domBoundingBox.attribute(QLatin1String("miny")).toDouble();
|
|
const double maxX = domBoundingBox.attribute(QLatin1String("maxx")).toDouble();
|
|
const double maxY = domBoundingBox.attribute(QLatin1String("maxy")).toDouble();
|
|
|
|
// Since ArcGIS calculates from upper left (instead lower left as tms defines),
|
|
// we need to make sure all our zoom levels have the same upper y value
|
|
// TODO: use from zoom level
|
|
const double utm32MaxYcalc = 16384 * 256 * 3; // 3 - Anzahl kacheln lowest zoom level
|
|
const double utm32MinX = 166021.44309607695; // min utm 32 boundary - calc from proj4 or smth. like this
|
|
|
|
const QString tsAttr(QLatin1String("TileSet"));
|
|
QDomElement tileSetSibling = domTileSets.firstChildElement(tsAttr);
|
|
for (; !tileSetSibling.isNull(); tileSetSibling = tileSetSibling.nextSiblingElement(tsAttr))
|
|
{
|
|
if (!tileSetSibling.hasAttribute(QLatin1String("order")))
|
|
{
|
|
qWarning("Could not find 'order' attribute for LOD - skipping lod");
|
|
continue;
|
|
}
|
|
const int lodLevel = tileSetSibling.attribute(QLatin1String("order")).toInt();
|
|
|
|
lod.setLevel(lodLevel);
|
|
lod.setResolution(tileSetSibling.attribute(QLatin1String("units-per-pixel")).toDouble());
|
|
lod.setScale(lod.resolution() / dpu);
|
|
|
|
if (!tileSetSibling.hasAttribute(QLatin1String("href")))
|
|
{
|
|
qWarning("Could not find 'href' attribute for LOD - skipping lod");
|
|
continue;
|
|
}
|
|
|
|
lods.append(lod);
|
|
m_tileUrls.insert(lodLevel, tileSetSibling.attribute(QLatin1String("href")));
|
|
|
|
// Since ArcGIS calculates from upper left (instead lower left as tms defines),
|
|
// we need to make sure all our zoom levels have the same upper y value
|
|
m_tilesYAxis.insert(lodLevel, qRound(utm32MaxYcalc / (lod.resolution() * tileInfo.cols())));
|
|
//qWarning("LOD: %d, %lf %lf, %lf", lodLevel, lod.resolution(), lod.scale(),
|
|
// lod.resolution() * 256 * m_tilesYAxis.value(lodLevel));
|
|
|
|
//qWarning("Y Tile count for level %d is %d - %lf", lodLevel, m_tilesYAxis.value(lodLevel),
|
|
// utm32MaxYcalc / (lod.resolution() * tileInfo.cols()));
|
|
}
|
|
if (lods.isEmpty())
|
|
{
|
|
const QString errStr(QLatin1String("Could not find any layer in the xml file!"));
|
|
layerCreateError(errStr);
|
|
qWarning("%s", qPrintable(errStr));
|
|
cleanupNetworkReply(reply);
|
|
return;
|
|
}
|
|
tileInfo.setLODs(lods);
|
|
|
|
const EsriRuntimeQt::Envelope envelope(minX, minY, maxX, maxY, sr);
|
|
// TMS service origin is bottom left, arcgis upper left
|
|
tileInfo.setOrigin({utm32MinX, utm32MaxYcalc, sr});
|
|
|
|
setDefaultSpatialReference(sr);
|
|
setFullExtent(envelope);
|
|
setInitialExtent(envelope);
|
|
setTileInfo(tileInfo);
|
|
|
|
// TODO: setMin/Max scale ?
|
|
|
|
EsriRuntimeQt::TiledMapServiceLayer::initialize();
|
|
|
|
// clean up
|
|
cleanupNetworkReply(reply);
|
|
}
|
|
|
|
void TMSLayer::replyBaseXmlError(QNetworkReply::NetworkError error)
|
|
{
|
|
const QString errStr(QString(QLatin1String("Error retrieving xml from %1, error: %2")).arg(url()).arg(error));
|
|
emit layerCreateError(errStr);
|
|
qWarning("%s", qPrintable(errStr));
|
|
}
|
|
|
|
void TMSLayer::onRequestTMSTile(int level, int col, int row)
|
|
{
|
|
if (!m_tilesYAxis.contains(level))
|
|
{
|
|
qWarning("Invalud zoom level (%d)!", level);
|
|
return;
|
|
}
|
|
// TMS starts counting from bottom left, ArcGIS from upper left
|
|
// -> we need to mirror the y-axis
|
|
int reqRow = m_tilesYAxis.value(level) - row - 1;
|
|
//qWarning("onRequestTMSTile: %d, %d, %d (%d)", level, col, reqRow, row);
|
|
QString requestUrl(m_tileUrls.value(level) +
|
|
QLatin1Char('/') + QString::number(col) +
|
|
QLatin1Char('/') + QString::number(reqRow));
|
|
if (!m_extension.isEmpty())
|
|
requestUrl += QLatin1Char('.') + m_extension;
|
|
//qWarning("RequestUrl: %s", qPrintable(requestUrl));
|
|
setTile(level, col, row, QUrl(requestUrl));
|
|
}
|
|
|
|
void TMSLayer::cleanupNetworkReply(QNetworkReply *reply)
|
|
{
|
|
if (reply)
|
|
{
|
|
reply->deleteLater();
|
|
reply->disconnect(this);
|
|
}
|
|
if (m_nam)
|
|
{
|
|
m_nam->disconnect(this);
|
|
m_nam->deleteLater();
|
|
m_nam = nullptr;
|
|
}
|
|
}
|
|
|
|
} // namespace EsriExtension
|