/** * @RDE_HEADER@ * Tile Map Service Layer for Esri * $Id$ * $HeadURL$ * $Author$ * License: BSD 2-Clause */ #include "tms_layer.h" #include #include #include 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:' 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 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