Initial commit

This commit is contained in:
torsten committed 2026-08-13 18:33:10 +02:00
commit b7d90b9afe
16245 files changed
+3396666

No files matched your search

+213
View File
@@ -0,0 +1,213 @@
/**
* @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