diff --git a/python/core/auto_generated/qgsvectordataprovider.sip.in b/python/core/auto_generated/qgsvectordataprovider.sip.in index ff660449d50..688c4cfaf18 100644 --- a/python/core/auto_generated/qgsvectordataprovider.sip.in +++ b/python/core/auto_generated/qgsvectordataprovider.sip.in @@ -52,6 +52,7 @@ of feature and attribute information from a spatial datasource. WriteLayerMetadata, CancelSupport, CreateRenderer, + CreateLabeling, }; typedef QFlags Capabilities; @@ -518,6 +519,23 @@ Only providers which report the CreateRenderer capability will return a feature providers will return None. .. versionadded:: 3.2 +%End + + virtual QgsAbstractVectorLayerLabeling *createLabeling( const QVariantMap &configuration = QVariantMap() ) const /Factory/; +%Docstring +Creates labeling settings, using provider backend specific information. + +The ``configuration`` map can be used to pass provider-specific configuration maps to the provider to +allow customization of the returned labeling object. Support and format of ``configuration`` varies by provider. + +When called with an empty ``configuration`` map the provider's default labeling settings will be returned. + +This method returns a new labeling settings and the caller takes ownership of the returned object. + +Only providers which report the CreateLabeling capability will return labeling settings. Other +providers will return None. + +.. versionadded:: 3.6 %End static QVariant convertValue( QVariant::Type type, const QString &value ); diff --git a/src/core/qgsvectordataprovider.cpp b/src/core/qgsvectordataprovider.cpp index 3074e045c32..9259d6e8cb8 100644 --- a/src/core/qgsvectordataprovider.cpp +++ b/src/core/qgsvectordataprovider.cpp @@ -699,6 +699,11 @@ QgsFeatureRenderer *QgsVectorDataProvider::createRenderer( const QVariantMap & ) return nullptr; } +QgsAbstractVectorLayerLabeling *QgsVectorDataProvider::createLabeling( const QVariantMap & ) const +{ + return nullptr; +} + void QgsVectorDataProvider::pushError( const QString &msg ) const { QgsDebugMsg( msg ); diff --git a/src/core/qgsvectordataprovider.h b/src/core/qgsvectordataprovider.h index 42b208c7031..b3b13547370 100644 --- a/src/core/qgsvectordataprovider.h +++ b/src/core/qgsvectordataprovider.h @@ -41,6 +41,7 @@ class QgsFeatureIterator; class QgsTransaction; class QgsFeedback; class QgsFeatureRenderer; +class QgsAbstractVectorLayerLabeling; #include "qgsfeaturerequest.h" @@ -93,6 +94,7 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider, public QgsFeat WriteLayerMetadata = 1 << 22, //!< Provider can write layer metadata to the data store. Since QGIS 3.0. See QgsDataProvider::writeLayerMetadata() CancelSupport = 1 << 23, //!< Supports interruption of pending queries from a separated thread. Since QGIS 3.2 CreateRenderer = 1 << 24, //!< Provider can create feature renderers using backend-specific formatting information. Since QGIS 3.2. See QgsVectorDataProvider::createRenderer(). + CreateLabeling = 1 << 25, //!< Provider can set labeling settings using backend-specific formatting information. Since QGIS 3.6. See QgsVectorDataProvider::createLabeling(). }; Q_DECLARE_FLAGS( Capabilities, Capability ) @@ -520,6 +522,23 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider, public QgsFeat */ virtual QgsFeatureRenderer *createRenderer( const QVariantMap &configuration = QVariantMap() ) const SIP_FACTORY; + /** + * Creates labeling settings, using provider backend specific information. + * + * The \a configuration map can be used to pass provider-specific configuration maps to the provider to + * allow customization of the returned labeling object. Support and format of \a configuration varies by provider. + * + * When called with an empty \a configuration map the provider's default labeling settings will be returned. + * + * This method returns a new labeling settings and the caller takes ownership of the returned object. + * + * Only providers which report the CreateLabeling capability will return labeling settings. Other + * providers will return nullptr. + * + * \since QGIS 3.6 + */ + virtual QgsAbstractVectorLayerLabeling *createLabeling( const QVariantMap &configuration = QVariantMap() ) const SIP_FACTORY; + static QVariant convertValue( QVariant::Type type, const QString &value ); /** diff --git a/src/core/qgsvectorlayer.cpp b/src/core/qgsvectorlayer.cpp index 52e3ba37237..b7cce709b07 100644 --- a/src/core/qgsvectorlayer.cpp +++ b/src/core/qgsvectorlayer.cpp @@ -1531,6 +1531,16 @@ void QgsVectorLayer::setDataSource( const QString &dataSource, const QString &ba } setLegend( QgsMapLayerLegend::defaultVectorLegend( this ) ); + + if ( mDataProvider->capabilities() & QgsVectorDataProvider::CreateLabeling ) + { + std::unique_ptr< QgsAbstractVectorLayerLabeling > defaultLabeling( mDataProvider->createLabeling() ); + if ( defaultLabeling ) + { + setLabeling( defaultLabeling.release() ); + setLabelsEnabled( true ); + } + } } emit dataSourceChanged(); diff --git a/src/providers/arcgisrest/qgsafsprovider.cpp b/src/providers/arcgisrest/qgsafsprovider.cpp index 12dcf49eee6..38ca8e154ae 100644 --- a/src/providers/arcgisrest/qgsafsprovider.cpp +++ b/src/providers/arcgisrest/qgsafsprovider.cpp @@ -222,6 +222,7 @@ QgsAfsProvider::QgsAfsProvider( const QString &uri, const ProviderOptions &optio // renderer mRendererDataMap = layerData.value( QStringLiteral( "drawingInfo" ) ).toMap().value( QStringLiteral( "renderer" ) ).toMap(); + mLabelingDataList = layerData.value( QStringLiteral( "drawingInfo" ) ).toMap().value( QStringLiteral( "labelingInfo" ) ).toList(); mValid = true; } @@ -263,6 +264,10 @@ QgsVectorDataProvider::Capabilities QgsAfsProvider::capabilities() const { c = c | QgsVectorDataProvider::CreateRenderer; } + if ( !mLabelingDataList.empty() ) + { + c = c | QgsVectorDataProvider::CreateLabeling; + } return c; } @@ -307,6 +312,10 @@ QgsFeatureRenderer *QgsAfsProvider::createRenderer( const QVariantMap & ) const return QgsArcGisRestUtils::parseEsriRenderer( mRendererDataMap ); } +QgsAbstractVectorLayerLabeling *QgsAfsProvider::createLabeling( const QVariantMap & ) const +{ + return QgsArcGisRestUtils::parseEsriLabeling( mLabelingDataList ); +} #ifdef HAVE_GUI diff --git a/src/providers/arcgisrest/qgsafsprovider.h b/src/providers/arcgisrest/qgsafsprovider.h index 83501076711..87852e1f838 100644 --- a/src/providers/arcgisrest/qgsafsprovider.h +++ b/src/providers/arcgisrest/qgsafsprovider.h @@ -71,6 +71,7 @@ class QgsAfsProvider : public QgsVectorDataProvider QString dataComment() const override; void reloadData() override; QgsFeatureRenderer *createRenderer( const QVariantMap &configuration = QVariantMap() ) const override; + QgsAbstractVectorLayerLabeling *createLabeling( const QVariantMap &configuration = QVariantMap() ) const override; private: bool mValid = false; @@ -80,6 +81,7 @@ class QgsAfsProvider : public QgsVectorDataProvider QString mLayerDescription; QgsLayerMetadata mLayerMetadata; QVariantMap mRendererDataMap; + QVariantList mLabelingDataList; }; #endif // QGSAFSPROVIDER_H diff --git a/src/providers/arcgisrest/qgsarcgisrestutils.cpp b/src/providers/arcgisrest/qgsarcgisrestutils.cpp index bd14f987e02..f5bbbb308f6 100644 --- a/src/providers/arcgisrest/qgsarcgisrestutils.cpp +++ b/src/providers/arcgisrest/qgsarcgisrestutils.cpp @@ -28,6 +28,7 @@ #include "geometry/qgspolygon.h" #include "geometry/qgspoint.h" #include "qgsfeedback.h" +#include "qgspallabeling.h" #include "qgssymbol.h" #include "qgssymbollayer.h" #include "qgsauthmanager.h" @@ -36,8 +37,11 @@ #include "qgsfillsymbollayer.h" #include "qgsmarkersymbollayer.h" #include "qgsrenderer.h" +#include "qgsrulebasedlabeling.h" #include "qgssinglesymbolrenderer.h" #include "qgscategorizedsymbolrenderer.h" +#include "qgsvectorlayerlabeling.h" + #include #include #include @@ -765,6 +769,132 @@ std::unique_ptr QgsArcGisRestUtils::parseEsriPictureMarkerSymbo return symbol; } +QgsAbstractVectorLayerLabeling *QgsArcGisRestUtils::parseEsriLabeling( const QVariantList &labelingData ) +{ + if ( labelingData.empty() ) + return nullptr; + + QgsRuleBasedLabeling::Rule *root = new QgsRuleBasedLabeling::Rule( new QgsPalLayerSettings(), 0, 0, QString(), QString(), false ); + root->setActive( true ); + + int i = 1; + for ( const QVariant &lbl : labelingData ) + { + const QVariantMap labeling = lbl.toMap(); + + QgsPalLayerSettings *settings = new QgsPalLayerSettings(); + QgsTextFormat format; + + const QString placement = labeling.value( QStringLiteral( "labelPlacement" ) ).toString(); + if ( placement == QLatin1String( "esriServerPointLabelPlacementAboveCenter" ) ) + { + settings->placement = QgsPalLayerSettings::OverPoint; + settings->quadOffset = QgsPalLayerSettings::QuadrantAbove; + } + else if ( placement == QLatin1String( "esriServerPointLabelPlacementBelowCenter" ) ) + { + settings->placement = QgsPalLayerSettings::OverPoint; + settings->quadOffset = QgsPalLayerSettings::QuadrantBelow; + } + else if ( placement == QLatin1String( "esriServerPointLabelPlacementCenterCenter" ) ) + { + settings->placement = QgsPalLayerSettings::OverPoint; + settings->quadOffset = QgsPalLayerSettings::QuadrantOver; + } + else if ( placement == QLatin1String( "esriServerPointLabelPlacementAboveLeft" ) ) + { + settings->placement = QgsPalLayerSettings::OverPoint; + settings->quadOffset = QgsPalLayerSettings::QuadrantAboveLeft; + } + else if ( placement == QLatin1String( "esriServerPointLabelPlacementBelowLeft" ) ) + { + settings->placement = QgsPalLayerSettings::OverPoint; + settings->quadOffset = QgsPalLayerSettings::QuadrantBelowLeft; + } + else if ( placement == QLatin1String( "esriServerPointLabelPlacementCenterLeft" ) ) + { + settings->placement = QgsPalLayerSettings::OverPoint; + settings->quadOffset = QgsPalLayerSettings::QuadrantLeft; + } + else if ( placement == QLatin1String( "esriServerPointLabelPlacementAboveRight" ) ) + { + settings->placement = QgsPalLayerSettings::OverPoint; + settings->quadOffset = QgsPalLayerSettings::QuadrantAboveRight; + } + else if ( placement == QLatin1String( "esriServerPointLabelPlacementBelowRight" ) ) + { + settings->placement = QgsPalLayerSettings::OverPoint; + settings->quadOffset = QgsPalLayerSettings::QuadrantBelowRight; + } + else if ( placement == QLatin1String( "esriServerPointLabelPlacementCenterRight" ) ) + { + settings->placement = QgsPalLayerSettings::OverPoint; + settings->quadOffset = QgsPalLayerSettings::QuadrantRight; + } + else if ( placement == QLatin1String( "esriServerLinePlacementAboveAfter" ) || + placement == QLatin1String( "esriServerLinePlacementAboveStart" ) || + placement == QLatin1String( "esriServerLinePlacementAboveAlong" ) ) + { + settings->placement = QgsPalLayerSettings::Line; + settings->placementFlags = QgsPalLayerSettings::AboveLine | QgsPalLayerSettings::MapOrientation; + } + else if ( placement == QLatin1String( "esriServerLinePlacementBelowAfter" ) || + placement == QLatin1String( "esriServerLinePlacementBelowStart" ) || + placement == QLatin1String( "esriServerLinePlacementBelowAlong" ) ) + { + settings->placement = QgsPalLayerSettings::Line; + settings->placementFlags = QgsPalLayerSettings::BelowLine | QgsPalLayerSettings::MapOrientation; + } + else if ( placement == QLatin1String( "esriServerLinePlacementCenterAfter" ) || + placement == QLatin1String( "esriServerLinePlacementCenterStart" ) || + placement == QLatin1String( "esriServerLinePlacementCenterAlong" ) ) + { + settings->placement = QgsPalLayerSettings::Line; + settings->placementFlags = QgsPalLayerSettings::OnLine | QgsPalLayerSettings::MapOrientation; + } + else if ( placement == QLatin1String( "esriServerPolygonPlacementAlwaysHorizontal" ) ) + { + settings->placement = QgsPalLayerSettings::Horizontal; + } + + const double minScale = labeling.value( QStringLiteral( "minScale" ) ).toDouble(); + const double maxScale = labeling.value( QStringLiteral( "maxScale" ) ).toDouble(); + + QVariantMap symbol = labeling.value( QStringLiteral( "symbol" ) ).toMap(); + format.setColor( parseEsriColorJson( symbol.value( QStringLiteral( "color" ) ) ) ); + + const QString fontFamily = symbol.value( QStringLiteral( "font" ) ).toMap().value( QStringLiteral( "family" ) ).toString(); + const QString fontStyle = symbol.value( QStringLiteral( "font" ) ).toMap().value( QStringLiteral( "style" ) ).toString(); + const QString fontWeight = symbol.value( QStringLiteral( "font" ) ).toMap().value( QStringLiteral( "weight" ) ).toString(); + const int fontSize = symbol.value( QStringLiteral( "font" ) ).toMap().value( QStringLiteral( "size" ) ).toInt(); + QFont font( fontFamily, fontSize ); + font.setStyleName( fontStyle ); + font.setWeight( fontWeight == QLatin1String( "bold" ) ? QFont::Bold : QFont::Normal ); + + format.setFont( font ); + format.setSize( fontSize ); + format.setSizeUnit( QgsUnitTypes::RenderPoints ); + + settings->setFormat( format ); + + QString where = labeling.value( QStringLiteral( "where" ) ).toString(); + QgsExpression exp( where ); + // If the where clause isn't parsed as valid, don't use its + if ( !exp.isValid() ) + where.clear(); + + QString expression = labeling.value( QStringLiteral( "labelExpression" ) ).toString(); + settings->fieldName = expression.replace( '[', '"' ).replace( ']', '"' ); + settings->isExpression = true; + + QgsRuleBasedLabeling::Rule *child = new QgsRuleBasedLabeling::Rule( settings, maxScale, minScale, where, QObject::tr( "ASF label %1" ).arg( i++ ), false ); + child->setActive( true ); + root->appendChild( child ); + } + + return new QgsRuleBasedLabeling( root ); +} + QgsFeatureRenderer *QgsArcGisRestUtils::parseEsriRenderer( const QVariantMap &rendererData ) { const QString type = rendererData.value( QStringLiteral( "type" ) ).toString(); diff --git a/src/providers/arcgisrest/qgsarcgisrestutils.h b/src/providers/arcgisrest/qgsarcgisrestutils.h index fd613d70878..30cb0e69834 100644 --- a/src/providers/arcgisrest/qgsarcgisrestutils.h +++ b/src/providers/arcgisrest/qgsarcgisrestutils.h @@ -24,6 +24,7 @@ class QNetworkReply; class QgsNetworkAccessManager; class QgsFields; class QgsAbstractGeometry; +class QgsAbstractVectorLayerLabeling; class QgsCoordinateReferenceSystem; class QgsFeedback; class QgsSymbol; @@ -58,6 +59,7 @@ class QgsArcGisRestUtils static std::unique_ptr< QgsMarkerSymbol > parseEsriMarkerSymbolJson( const QVariantMap &symbolData ); static std::unique_ptr< QgsMarkerSymbol > parseEsriPictureMarkerSymbolJson( const QVariantMap &symbolData ); static QgsFeatureRenderer *parseEsriRenderer( const QVariantMap &rendererData ); + static QgsAbstractVectorLayerLabeling *parseEsriLabeling( const QVariantList &labelingData ); static QColor parseEsriColorJson( const QVariant &colorData ); static Qt::PenStyle parseEsriLineStyle( const QString &style ); diff --git a/tests/src/providers/testqgsarcgisrestutils.cpp b/tests/src/providers/testqgsarcgisrestutils.cpp index 779e112d2d9..bb68c9b85a9 100644 --- a/tests/src/providers/testqgsarcgisrestutils.cpp +++ b/tests/src/providers/testqgsarcgisrestutils.cpp @@ -23,6 +23,7 @@ #include "qgslinesymbollayer.h" #include "qgsfillsymbollayer.h" #include "qgsmarkersymbollayer.h" +#include "qgsrulebasedlabeling.h" #include "qgssinglesymbolrenderer.h" #include "qgscategorizedsymbolrenderer.h" #include @@ -49,6 +50,7 @@ class TestQgsArcGisRestUtils : public QObject void testParsePictureFillSymbol(); void testParseRendererSimple(); void testParseRendererCategorized(); + void testParseLabeling(); private: @@ -454,6 +456,107 @@ void TestQgsArcGisRestUtils::testParseRendererCategorized() QVERIFY( catRenderer->categories().at( 1 ).symbol() ); } +void TestQgsArcGisRestUtils::testParseLabeling() +{ + QVariantMap map = jsonStringToMap( "{" + "\"labelingInfo\": [" + "{" + "\"labelPlacement\": \"esriServerPointLabelPlacementAboveRight\"," + "\"where\": \"1=1\"," + "\"labelExpression\": \"[Name]\"," + "\"useCodedValues\": true," + "\"symbol\": {" + "\"type\": \"esriTS\"," + "\"color\": [" + "255," + "0," + "0," + "255" + "]," + "\"backgroundColor\": null," + "\"borderLineColor\": null," + "\"borderLineSize\": null," + "\"verticalAlignment\": \"bottom\"," + "\"horizontalAlignment\": \"center\"," + "\"rightToLeft\": false," + "\"angle\": 0," + "\"xoffset\": 0," + "\"yoffset\": 0," + "\"haloColor\": null," + "\"haloSize\": null," + "\"font\": {" + "\"family\": \"Arial\"," + "\"size\": 8," + "\"style\": \"normal\"," + "\"weight\": \"bold\"," + "\"decoration\": \"none\"" + "}" + "}," + "\"minScale\": 200000," + "\"maxScale\": 0" + "},{" + "\"labelPlacement\": \"esriServerPointLabelPlacementAboveRight\"," + "\"where\": \"1_testing broken where string\"," + "\"labelExpression\": \"[Name]\"," + "\"useCodedValues\": true," + "\"symbol\": {" + "\"type\": \"esriTS\"," + "\"color\": [" + "255," + "0," + "0," + "255" + "]," + "\"backgroundColor\": null," + "\"borderLineColor\": null," + "\"borderLineSize\": null," + "\"verticalAlignment\": \"bottom\"," + "\"horizontalAlignment\": \"center\"," + "\"rightToLeft\": false," + "\"angle\": 0," + "\"xoffset\": 0," + "\"yoffset\": 0," + "\"haloColor\": null," + "\"haloSize\": null," + "\"font\": {" + "\"family\": \"Arial\"," + "\"size\": 8," + "\"style\": \"normal\"," + "\"weight\": \"bold\"," + "\"decoration\": \"none\"" + "}" + "}," + "\"minScale\": 200000," + "\"maxScale\": 0" + "}" + "]" + "}" ); + std::unique_ptr< QgsAbstractVectorLayerLabeling > labeling( QgsArcGisRestUtils::parseEsriLabeling( map.value( QStringLiteral( "labelingInfo" ) ).toList() ) ); + QVERIFY( labeling ); + QgsRuleBasedLabeling *rules = dynamic_cast< QgsRuleBasedLabeling *>( labeling.get() ); + QVERIFY( rules ); + QgsRuleBasedLabeling::Rule *root = rules->rootRule(); + QVERIFY( root ); + + QgsRuleBasedLabeling::RuleList children = root->children(); + QCOMPARE( children.count(), 2 ); + //checking filter expression from valid where string + QCOMPARE( children.at( 0 )->filterExpression(), QStringLiteral( "1=1" ) ); + //checking empty filter expression from invalid where string + QCOMPARE( children.at( 1 )->filterExpression(), QString( "" ) ); + QCOMPARE( children.at( 0 )->minimumScale(), 200000.0 ); + QCOMPARE( children.at( 0 )->maximumScale(), 0.0 ); + + QgsPalLayerSettings *settings = children.at( 0 )->settings(); + QVERIFY( settings ); + QCOMPARE( settings->placement, QgsPalLayerSettings::OverPoint ); + QCOMPARE( settings->quadOffset, QgsPalLayerSettings::QuadrantAboveRight ); + + QgsTextFormat textFormat = settings->format(); + QCOMPARE( textFormat.color(), QColor( 255, 0, 0 ) ); + QCOMPARE( textFormat.size(), 8.0 ); +} + QVariantMap TestQgsArcGisRestUtils::jsonStringToMap( const QString &string ) const { QJsonDocument doc = QJsonDocument::fromJson( string.toUtf8() );