Skip to content

Commit

Permalink
[FEATURE] load MVT layers from style URL only (#58663)
Browse files Browse the repository at this point in the history
with support for multiple sources
  • Loading branch information
3nids authored Sep 12, 2024
1 parent 84e48f9 commit ea3d907
Show file tree
Hide file tree
Showing 13 changed files with 10,269 additions and 80 deletions.
3 changes: 3 additions & 0 deletions src/app/qgisapp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
#include "maptools/qgsappmaptools.h"
#include "qgsexpressioncontextutils.h"
#include "qgsauxiliarystorage.h"
#include "qgsvectortileutils.h"

#include "qgsbrowserwidget.h"
#include "annotations/qgsannotationitempropertieswidget.h"
Expand Down Expand Up @@ -2517,6 +2518,8 @@ QList< QgsMapLayer * > QgisApp::handleDropUriList( const QgsMimeDataUtils::UriLi
{
QgsTemporaryCursorOverride busyCursor( Qt::WaitCursor );

QgsVectorTileUtils::updateUriSources( uri );

const QgsVectorTileLayer::LayerOptions options( QgsProject::instance()->transformContext() );
QgsVectorTileLayer *layer = new QgsVectorTileLayer( uri, u.name, options );
bool ok = false;
Expand Down
1 change: 1 addition & 0 deletions src/core/vectortile/qgsvectortilemvtdecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "qgspolygon.h"

#include <QPointer>
#include <QNetworkRequest>


QgsVectorTileMVTDecoder::QgsVectorTileMVTDecoder( const QgsVectorTileMatrixSet &structure )
Expand Down
1 change: 1 addition & 0 deletions src/core/vectortile/qgsvectortilemvtdecoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class QgsFeature;
#include "qgsvectortilerenderer.h"
#include "qgsvectortilematrixset.h"


class QgsVectorTileRawData;

/**
Expand Down
140 changes: 139 additions & 1 deletion src/core/vectortile/qgsvectortileutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#include <math.h>

#include <QPolygon>
#include <QJsonDocument>
#include <QJsonArray>

#include "qgscoordinatetransform.h"
#include "qgsgeometrycollection.h"
Expand All @@ -32,10 +34,146 @@
#include "qgsvectortilelayer.h"
#include "qgsvectortilerenderer.h"
#include "qgsmapboxglstyleconverter.h"
#include "qgsnetworkaccessmanager.h"
#include "qgssetrequestinitiator_p.h"
#include "qgsblockingnetworkrequest.h"
#include "qgsjsonutils.h"
#include "qgsvectortileconnection.h"


void QgsVectorTileUtils::updateUriSources( QString &uri, bool forceUpdate )
{
QgsVectorTileProviderConnection::Data data = QgsVectorTileProviderConnection::decodedUri( uri );
if ( forceUpdate || ( data.url.isEmpty() && !data.styleUrl.isEmpty() ) )
{
const QMap<QString, QString> sources = QgsVectorTileUtils::parseStyleSourceUrl( data.styleUrl, data.httpHeaders, data.authCfg );
QMap<QString, QString>::const_iterator it = sources.constBegin();
int i = 0;
for ( ; it != sources.constEnd(); ++it )
{
i += 1;
QString urlKey = QStringLiteral( "url" );
QString nameKey = QStringLiteral( "urlName" );
if ( i > 1 )
{
urlKey.append( QString( "_%1" ).arg( i ) );
nameKey.append( QString( "_%1" ).arg( i ) );
}
uri.append( QString( "&%1=%2" ).arg( nameKey, it.key() ) );
uri.append( QString( "&%1=%2" ).arg( urlKey, it.value() ) );
}
}
}

QMap<QString, QString> QgsVectorTileUtils::parseStyleSourceUrl( const QString &styleUrl, const QgsHttpHeaders &headers, const QString &authCfg )
{
QNetworkRequest nr;
nr.setUrl( QUrl( styleUrl ) );
headers.updateNetworkRequest( nr );

QgsBlockingNetworkRequest req;
req.setAuthCfg( authCfg );
QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr, false );
if ( errCode != QgsBlockingNetworkRequest::NoError )
{
QgsDebugError( QStringLiteral( "Request failed: " ) + styleUrl );
return QMap<QString, QString>();
}
QgsNetworkReplyContent reply = req.reply();

QJsonParseError err;
const QJsonDocument doc = QJsonDocument::fromJson( reply.content(), &err );
if ( doc.isNull() )
{
QgsDebugError( QStringLiteral( "Could not load style: %1" ).arg( err.errorString() ) );
}
else if ( !doc.isObject() )
{
QgsDebugError( QStringLiteral( "Could not read style, JSON object is expected" ) );
}
else
{
QMap<QString, QString> sources;
QJsonObject sourcesData = doc.object().value( QStringLiteral( "sources" ) ).toObject();
if ( sourcesData.count() == 0 )
{
QgsDebugError( QStringLiteral( "Could not read sources in the style" ) );
}
else
{
QJsonObject::const_iterator it = sourcesData.constBegin();
for ( ; it != sourcesData.constEnd(); ++it )
{
const QString sourceName = it.key();
const QJsonObject sourceData = it.value().toObject();
if ( sourceData.value( QStringLiteral( "type" ) ).toString() != QStringLiteral( "vector" ) )
{
// raster layers are handled separately
// ideally we should handle the sources here also, the same way than for vector
continue;
}
QVariantList tiles;
if ( sourceData.contains( QStringLiteral( "tiles" ) ) )
{
tiles = sourceData["tiles"].toArray().toVariantList();
}
else if ( sourceData.contains( QStringLiteral( "url" ) ) )
{
tiles = parseStyleSourceContentUrl( sourceData.value( QStringLiteral( "url" ) ).toString(), headers, authCfg );
}
else
{
QgsDebugError( QStringLiteral( "Could not read source %1" ).arg( sourceName ) );
}
if ( tiles.count() == 0 )
{
QgsDebugError( QStringLiteral( "Could not read source %1, not tiles found" ).arg( sourceName ) );
}
else
{
// take a random one from the list
// we might want to save the alternatives for a fallback later
sources.insert( sourceName, tiles[rand() % tiles.count()].toString() );
}
}
return sources;
}
}
return QMap<QString, QString>();
}

QVariantList QgsVectorTileUtils::parseStyleSourceContentUrl( const QString &sourceUrl, const QgsHttpHeaders &headers, const QString &authCfg )
{
QNetworkRequest nr;
nr.setUrl( QUrl( sourceUrl ) );
headers.updateNetworkRequest( nr );

QgsBlockingNetworkRequest req;
req.setAuthCfg( authCfg );
QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr, false );
if ( errCode != QgsBlockingNetworkRequest::NoError )
{
QgsDebugError( QStringLiteral( "Request failed: " ) + sourceUrl );
return QVariantList();
}
QgsNetworkReplyContent reply = req.reply();

QJsonParseError err;
const QJsonDocument doc = QJsonDocument::fromJson( reply.content(), &err );
if ( doc.isNull() )
{
QgsDebugError( QStringLiteral( "Could not load style: %1" ).arg( err.errorString() ) );
}
else if ( !doc.isObject() )
{
QgsDebugError( QStringLiteral( "Could not read style, JSON object is expected" ) );
}
else
{
return doc.object().value( QStringLiteral( "tiles" ) ).toArray().toVariantList();
}
return QVariantList();
}



QPolygon QgsVectorTileUtils::tilePolygon( QgsTileXYZ id, const QgsCoordinateTransform &ct, const QgsTileMatrix &tm, const QgsMapToPixel &mtp )
Expand Down
20 changes: 20 additions & 0 deletions src/core/vectortile/qgsvectortileutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
#include <QSet>
#include <QVariantMap>

#include "qgshttpheaders.h"


class QPointF;
class QPolygon;

Expand All @@ -48,6 +51,13 @@ class CORE_EXPORT QgsVectorTileUtils
{
public:

/**
* Parses the style URL to update the source URLs in the \a uri.
* If \a forceUpdate is TRUE, any existing source will be updated.
* \since QGIS 3.40
*/
static void updateUriSources( QString &uri SIP_OUT, bool forceUpdate = false );

//! Orders tile requests according to the distance from view center (given in tile matrix coords)
static void sortTilesByDistanceFromCenter( QVector<QgsTileXYZ> &tiles, QPointF center );

Expand Down Expand Up @@ -92,6 +102,16 @@ class CORE_EXPORT QgsVectorTileUtils
* \param styleUrl optional the style url
*/
static void loadSprites( const QVariantMap &styleDefinition, QgsMapBoxGlStyleConversionContext &context, const QString &styleUrl = QString() );

private:
//! Parses the style URL to get a list of named source URLs.
static QMap<QString, QString> parseStyleSourceUrl( const QString &styleUrl, const QgsHttpHeaders &headers = QgsHttpHeaders(), const QString &authCfg = QString() );

//! Returns the tiles URLs of a source
static QVariantList parseStyleSourceContentUrl( const QString &sourceUrl, const QgsHttpHeaders &headers = QgsHttpHeaders(), const QString &authCfg = QString() );

friend class TestQgsVectorTileUtils;

};

#endif // QGSVECTORTILEUTILS_H
8 changes: 7 additions & 1 deletion src/gui/vectortile/qgsvectortileconnectiondialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,22 @@
#include "qgsvectortileconnection.h"
#include "qgsgui.h"
#include "qgshelp.h"
#include "qgssettingsenumflageditorwidgetwrapper.h"

#include <QMessageBox>
#include <QPushButton>

///@cond PRIVATE


QgsVectorTileConnectionDialog::QgsVectorTileConnectionDialog( QWidget *parent )
: QDialog( parent )
{
setupUi( this );
QgsGui::enableAutoGeometryRestore( this );

mEditUrl->setPlaceholderText( tr( "URL(s) can be determined from the style." ) );

// Behavior for min and max zoom checkbox
connect( mCheckBoxZMin, &QCheckBox::toggled, mSpinZMin, &QSpinBox::setEnabled );
connect( mCheckBoxZMax, &QCheckBox::toggled, mSpinZMax, &QSpinBox::setEnabled );
Expand All @@ -41,6 +45,7 @@ QgsVectorTileConnectionDialog::QgsVectorTileConnectionDialog( QWidget *parent )
} );
connect( mEditName, &QLineEdit::textChanged, this, &QgsVectorTileConnectionDialog::updateOkButtonState );
connect( mEditUrl, &QLineEdit::textChanged, this, &QgsVectorTileConnectionDialog::updateOkButtonState );
connect( mEditStyleUrl, &QLineEdit::textChanged, this, &QgsVectorTileConnectionDialog::updateOkButtonState );
}

void QgsVectorTileConnectionDialog::setConnection( const QString &name, const QString &uri )
Expand Down Expand Up @@ -85,10 +90,11 @@ QString QgsVectorTileConnectionDialog::connectionName() const

void QgsVectorTileConnectionDialog::updateOkButtonState()
{
const bool enabled = !mEditName->text().isEmpty() && !mEditUrl->text().isEmpty();
const bool enabled = !mEditName->text().isEmpty() && ( !mEditUrl->text().isEmpty() || !mEditStyleUrl->text().isEmpty() );
buttonBox->button( QDialogButtonBox::Ok )->setEnabled( enabled );
}


void QgsVectorTileConnectionDialog::accept()
{
if ( mCheckBoxZMin->isChecked() && mCheckBoxZMax->isChecked() && mSpinZMax->value() < mSpinZMin->value() )
Expand Down
11 changes: 10 additions & 1 deletion src/gui/vectortile/qgsvectortileconnectiondialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,21 @@

#include "ui_qgsvectortileconnectiondialog.h"

#include "qgssettingsentryenumflag.h"

class QgsVectorTileConnectionDialog : public QDialog, public Ui::QgsVectorTileConnectionDialog
{
Q_OBJECT
public:
enum class LoadingMode : int
{
Url,
Style
};
Q_ENUM( LoadingMode )

static const QgsSettingsEntryEnumFlag<QgsVectorTileConnectionDialog::LoadingMode> *settingsLastLoadingMode;

explicit QgsVectorTileConnectionDialog( QWidget *parent = nullptr );

void setConnection( const QString &name, const QString &uri );
Expand All @@ -39,7 +49,6 @@ class QgsVectorTileConnectionDialog : public QDialog, public Ui::QgsVectorTileCo

private slots:
void updateOkButtonState();

};

///@endcond
Expand Down
Loading

0 comments on commit ea3d907

Please sign in to comment.