Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add UX to load MVT layers from style URL only #58663

Merged
merged 9 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion 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 @@ -294,7 +295,6 @@
#include "qgslayoutatlas.h"
#include "qgslayoutcustomdrophandler.h"
#include "qgslayoutdesignerdialog.h"
#include "qgslayoutitemguiregistry.h"
#include "qgslayoutmanager.h"
#include "qgslayoutqptdrophandler.h"
#include "qgslayoutimagedrophandler.h"
Expand Down Expand Up @@ -2517,6 +2517,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
142 changes: 141 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,148 @@
#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"


bool QgsVectorTileUtils::updateUriSources( QString &uri )
{
QgsVectorTileProviderConnection::Data data = QgsVectorTileProviderConnection::decodedUri( uri );
if ( 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() ) );
}
return i > 0;
}
return true;
}

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
19 changes: 19 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,12 @@ class CORE_EXPORT QgsVectorTileUtils
{
public:

/**
* Parses the style URL to update the source URLs in the \a uri.
* \since QGIS 3.40
*/
static bool updateUriSources( QString &uri SIP_OUT );
3nids marked this conversation as resolved.
Show resolved Hide resolved

//! 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 +101,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
Loading