Skip to content

Commit

Permalink
[FEATURE][afs] Automatically set default style for layers
Browse files Browse the repository at this point in the history
to match the server's rendering information.

Means that when an AFS layer is loaded into QGIS, it will
automatically have the same style applied as has been created
for that layer, matching the appearance of the layer when it
is loaded into ArcGIS.

Fixes #13349
  • Loading branch information
nyalldawson committed Mar 7, 2018
1 parent 678f658 commit 6002cc4
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 4 deletions.
1 change: 1 addition & 0 deletions src/providers/arcgisrest/CMakeLists.txt
Expand Up @@ -5,6 +5,7 @@ INCLUDE_DIRECTORIES(
${CMAKE_SOURCE_DIR}/src/core/geometry
${CMAKE_SOURCE_DIR}/src/core/raster
${CMAKE_SOURCE_DIR}/src/core/metadata
${CMAKE_SOURCE_DIR}/src/core/symbology
${CMAKE_SOURCE_DIR}/src/gui
${CMAKE_SOURCE_DIR}/src/gui/auth
${CMAKE_BINARY_DIR}/src/ui
Expand Down
76 changes: 73 additions & 3 deletions src/providers/arcgisrest/qgsafsprovider.cpp
Expand Up @@ -28,6 +28,9 @@
#include "qgssourceselectprovider.h"
#endif

#include "qgssinglesymbolrenderer.h"
#include "qgscategorizedsymbolrenderer.h"

#include <QEventLoop>
#include <QMessageBox>
#include <QNetworkRequest>
Expand All @@ -51,7 +54,7 @@ QgsAfsProvider::QgsAfsProvider( const QString &uri )

// Get layer info
QString errorTitle, errorMessage;
QVariantMap layerData = QgsArcGisRestUtils::getLayerInfo( mSharedData->mDataSource.param( QStringLiteral( "url" ) ), errorTitle, errorMessage );
const QVariantMap layerData = QgsArcGisRestUtils::getLayerInfo( mSharedData->mDataSource.param( QStringLiteral( "url" ) ), errorTitle, errorMessage );
if ( layerData.isEmpty() )
{
pushError( errorTitle + ": " + errorMessage );
Expand All @@ -74,7 +77,7 @@ QgsAfsProvider::QgsAfsProvider( const QString &uri )
mSharedData->mExtent = QgsRectangle();
}

QVariantMap layerExtentMap = layerData[QStringLiteral( "extent" )].toMap();
const QVariantMap layerExtentMap = layerData[QStringLiteral( "extent" )].toMap();
bool xminOk = false, yminOk = false, xmaxOk = false, ymaxOk = false;
QgsRectangle originalExtent;
originalExtent.setXMinimum( layerExtentMap[QStringLiteral( "xmin" )].toDouble( &xminOk ) );
Expand Down Expand Up @@ -208,6 +211,9 @@ QgsAfsProvider::QgsAfsProvider( const QString &uri )
mLayerMetadata.setRights( QStringList() << copyright );
mLayerMetadata.addLink( QgsLayerMetadata::Link( tr( "Source" ), QStringLiteral( "WWW:LINK" ), mSharedData->mDataSource.param( QStringLiteral( "url" ) ) ) );

// renderer
mRendererDataMap = layerData.value( QStringLiteral( "drawingInfo" ) ).toMap().value( QStringLiteral( "renderer" ) ).toMap();

mValid = true;
}

Expand Down Expand Up @@ -243,7 +249,12 @@ QgsLayerMetadata QgsAfsProvider::layerMetadata() const

QgsVectorDataProvider::Capabilities QgsAfsProvider::capabilities() const
{
return QgsVectorDataProvider::SelectAtId | QgsVectorDataProvider::ReadLayerMetadata;
QgsVectorDataProvider::Capabilities c = QgsVectorDataProvider::SelectAtId | QgsVectorDataProvider::ReadLayerMetadata;
if ( !mRendererDataMap.empty() )
{
c = c | QgsVectorDataProvider::CreateRenderer;
}
return c;
}

void QgsAfsProvider::setDataSourceUri( const QString &uri )
Expand Down Expand Up @@ -282,6 +293,65 @@ void QgsAfsProvider::reloadData()
mSharedData->clearCache();
}

QgsFeatureRenderer *QgsAfsProvider::createRenderer( const QVariantMap & ) const
{
const QString type = mRendererDataMap.value( QStringLiteral( "type" ) ).toString();
if ( type == QLatin1String( "simple" ) )
{
const QVariantMap symbolProps = mRendererDataMap.value( QStringLiteral( "symbol" ) ).toMap();
std::unique_ptr< QgsSymbol > symbol = QgsArcGisRestUtils::parseEsriSymbolJson( symbolProps );
if ( symbol )
return new QgsSingleSymbolRenderer( symbol.release() );
else
return nullptr;
}
else if ( type == QLatin1String( "uniqueValue" ) )
{
const QString attribute = mRendererDataMap.value( QStringLiteral( "field1" ) ).toString();
// TODO - handle field2, field3
const QVariantList categories = mRendererDataMap.value( QStringLiteral( "uniqueValueInfos" ) ).toList();
QgsCategoryList categoryList;
for ( const QVariant &category : categories )
{
const QVariantMap categoryData = category.toMap();
const QString value = categoryData.value( QStringLiteral( "value" ) ).toString();
const QString label = categoryData.value( QStringLiteral( "label" ) ).toString();
std::unique_ptr< QgsSymbol > symbol = QgsArcGisRestUtils::parseEsriSymbolJson( categoryData.value( QStringLiteral( "symbol" ) ).toMap() );
if ( symbol )
{
categoryList.append( QgsRendererCategory( value, symbol.release(), label ) );
}
}

std::unique_ptr< QgsSymbol > defaultSymbol = QgsArcGisRestUtils::parseEsriSymbolJson( mRendererDataMap.value( QStringLiteral( "defaultSymbol" ) ).toMap() );
if ( defaultSymbol )
{
categoryList.append( QgsRendererCategory( QVariant(), defaultSymbol.release(), mRendererDataMap.value( QStringLiteral( "defaultLabel" ) ).toString() ) );
}

if ( categoryList.empty() )
return nullptr;

return new QgsCategorizedSymbolRenderer( attribute, categoryList );
}
else if ( type == QLatin1String( "classBreaks" ) )
{
// currently unsupported
return nullptr;
}
else if ( type == QLatin1String( "heatmap" ) )
{
// currently unsupported
return nullptr;
}
else if ( type == QLatin1String( "vectorField" ) )
{
// currently unsupported
return nullptr;
}
return nullptr;
}


#ifdef HAVE_GUI

Expand Down
2 changes: 2 additions & 0 deletions src/providers/arcgisrest/qgsafsprovider.h
Expand Up @@ -70,6 +70,7 @@ class QgsAfsProvider : public QgsVectorDataProvider
QString description() const override;
QString dataComment() const override;
void reloadData() override;
QgsFeatureRenderer *createRenderer( const QVariantMap &configuration = QVariantMap() ) const override;

private:
bool mValid;
Expand All @@ -78,6 +79,7 @@ class QgsAfsProvider : public QgsVectorDataProvider
QString mLayerName;
QString mLayerDescription;
QgsLayerMetadata mLayerMetadata;
QVariantMap mRendererDataMap;
};

#endif // QGSAFSPROVIDER_H
190 changes: 189 additions & 1 deletion src/providers/arcgisrest/qgsarcgisrestutils.cpp
Expand Up @@ -28,7 +28,11 @@
#include "geometry/qgspolygon.h"
#include "geometry/qgspoint.h"
#include "qgsfeedback.h"

#include "qgssymbol.h"
#include "qgssymbollayer.h"
#include "qgslinesymbollayer.h"
#include "qgsfillsymbollayer.h"
#include "qgsmarkersymbollayer.h"
#include <QEventLoop>
#include <QNetworkRequest>
#include <QNetworkReply>
Expand Down Expand Up @@ -506,6 +510,190 @@ QVariantMap QgsArcGisRestUtils::queryServiceJSON( const QUrl &url, QString &erro
return doc.object().toVariantMap();
}

std::unique_ptr<QgsSymbol> QgsArcGisRestUtils::parseEsriSymbolJson( const QVariantMap &symbolData )
{
const QString type = symbolData.value( QStringLiteral( "type" ) ).toString();
if ( type == QLatin1String( "esriSMS" ) )
{
// marker symbol
return parseEsriMarkerSymbolJson( symbolData );
}
else if ( type == QLatin1String( "esriSLS" ) )
{
// line symbol
return parseEsriLineSymbolJson( symbolData );
}
else if ( type == QLatin1String( "esriSFS" ) )
{
// fill symbol
return parseEsriFillSymbolJson( symbolData );
}
else if ( type == QLatin1String( "esriPFS" ) )
{
// picture fill - not supported
return nullptr;
}
else if ( type == QLatin1String( "esriPMS" ) )
{
// picture marker - not supported
return nullptr;
}
else if ( type == QLatin1String( "esriTS" ) )
{
// text symbol - not supported
return nullptr;
}
return nullptr;
}

std::unique_ptr<QgsLineSymbol> QgsArcGisRestUtils::parseEsriLineSymbolJson( const QVariantMap &symbolData )
{
QColor lineColor = parseEsriColorJson( symbolData.value( QStringLiteral( "color" ) ) );
if ( !lineColor.isValid() )
return nullptr;

bool ok = false;
double widthInPoints = symbolData.value( QStringLiteral( "width" ) ).toDouble( &ok );
if ( !ok )
return nullptr;

QgsSymbolLayerList layers;
Qt::PenStyle penStyle = parseEsriLineStyle( symbolData.value( QStringLiteral( "style" ) ).toString() );
std::unique_ptr< QgsSimpleLineSymbolLayer > lineLayer = qgis::make_unique< QgsSimpleLineSymbolLayer >( lineColor, widthInPoints, penStyle );
lineLayer->setWidthUnit( QgsUnitTypes::RenderPoints );
layers.append( lineLayer.release() );

std::unique_ptr< QgsLineSymbol > symbol = qgis::make_unique< QgsLineSymbol >( layers );
return symbol;
}

std::unique_ptr<QgsFillSymbol> QgsArcGisRestUtils::parseEsriFillSymbolJson( const QVariantMap &symbolData )
{
QColor fillColor = parseEsriColorJson( symbolData.value( QStringLiteral( "color" ) ) );
Qt::BrushStyle brushStyle = parseEsriFillStyle( symbolData.value( QStringLiteral( "style" ) ).toString() );

const QVariantMap outlineData = symbolData.value( QStringLiteral( "outline" ) ).toMap();
QColor lineColor = parseEsriColorJson( outlineData.value( QStringLiteral( "color" ) ) );
Qt::PenStyle penStyle = parseEsriLineStyle( outlineData.value( QStringLiteral( "style" ) ).toString() );
bool ok = false;
double penWidthInPoints = outlineData.value( QStringLiteral( "width" ) ).toDouble( &ok );

QgsSymbolLayerList layers;
std::unique_ptr< QgsSimpleFillSymbolLayer > fillLayer = qgis::make_unique< QgsSimpleFillSymbolLayer >( fillColor, brushStyle, lineColor, penStyle, penWidthInPoints );
fillLayer->setStrokeWidthUnit( QgsUnitTypes::RenderPoints );
layers.append( fillLayer.release() );

std::unique_ptr< QgsFillSymbol > symbol = qgis::make_unique< QgsFillSymbol >( layers );
return symbol;
}

QgsSimpleMarkerSymbolLayerBase::Shape parseEsriMarkerShape( const QString &style )
{
if ( style == QLatin1String( "esriSMSCircle" ) )
return QgsSimpleMarkerSymbolLayerBase::Circle;
else if ( style == QLatin1String( "esriSMSCross" ) )
return QgsSimpleMarkerSymbolLayerBase::Cross;
else if ( style == QLatin1String( "esriSMSDiamond" ) )
return QgsSimpleMarkerSymbolLayerBase::Diamond;
else if ( style == QLatin1String( "esriSMSSquare" ) )
return QgsSimpleMarkerSymbolLayerBase::Square;
else if ( style == QLatin1String( "esriSMSX" ) )
return QgsSimpleMarkerSymbolLayerBase::Cross2;
else if ( style == QLatin1String( "esriSMSTriangle" ) )
return QgsSimpleMarkerSymbolLayerBase::Triangle;
else
return QgsSimpleMarkerSymbolLayerBase::Circle;
}

std::unique_ptr<QgsMarkerSymbol> QgsArcGisRestUtils::parseEsriMarkerSymbolJson( const QVariantMap &symbolData )
{
QColor fillColor = parseEsriColorJson( symbolData.value( QStringLiteral( "color" ) ) );
bool ok = false;
const double sizeInPoints = symbolData.value( QStringLiteral( "size" ) ).toDouble( &ok );
if ( !ok )
return nullptr;
const double angleCCW = symbolData.value( QStringLiteral( "angle" ) ).toDouble( &ok );
double angleCW = 0;
if ( ok )
angleCW = -angleCCW;

QgsSimpleMarkerSymbolLayerBase::Shape shape = parseEsriMarkerShape( symbolData.value( QStringLiteral( "style" ) ).toString() );

const double xOffset = symbolData.value( QStringLiteral( "xoffset" ) ).toDouble();
const double yOffset = symbolData.value( QStringLiteral( "yoffset" ) ).toDouble();

const QVariantMap outlineData = symbolData.value( QStringLiteral( "outline" ) ).toMap();
QColor lineColor = parseEsriColorJson( outlineData.value( QStringLiteral( "color" ) ) );
Qt::PenStyle penStyle = parseEsriLineStyle( outlineData.value( QStringLiteral( "style" ) ).toString() );
double penWidthInPoints = outlineData.value( QStringLiteral( "width" ) ).toDouble( &ok );

QgsSymbolLayerList layers;
std::unique_ptr< QgsSimpleMarkerSymbolLayer > markerLayer = qgis::make_unique< QgsSimpleMarkerSymbolLayer >( shape, sizeInPoints, angleCW, QgsSymbol::ScaleArea, fillColor, lineColor );
markerLayer->setSizeUnit( QgsUnitTypes::RenderPoints );
markerLayer->setStrokeWidthUnit( QgsUnitTypes::RenderPoints );
markerLayer->setStrokeStyle( penStyle );
markerLayer->setStrokeWidth( penWidthInPoints );
markerLayer->setOffset( QPointF( xOffset, yOffset ) );
markerLayer->setOffsetUnit( QgsUnitTypes::RenderPoints );
layers.append( markerLayer.release() );

std::unique_ptr< QgsMarkerSymbol > symbol = qgis::make_unique< QgsMarkerSymbol >( layers );
return symbol;
}

QColor QgsArcGisRestUtils::parseEsriColorJson( const QVariant &colorData )
{
const QVariantList colorParts = colorData.toList();
if ( colorParts.count() < 4 )
return QColor();

int red = colorParts.at( 0 ).toInt();
int green = colorParts.at( 1 ).toInt();
int blue = colorParts.at( 2 ).toInt();
int alpha = colorParts.at( 3 ).toInt();
return QColor( red, green, blue, alpha );
}

Qt::PenStyle QgsArcGisRestUtils::parseEsriLineStyle( const QString &style )
{
if ( style == QLatin1String( "esriSLSSolid" ) )
return Qt::SolidLine;
else if ( style == QLatin1String( "esriSLSDash" ) )
return Qt::DashLine;
else if ( style == QLatin1String( "esriSLSDashDot" ) )
return Qt::DashDotLine;
else if ( style == QLatin1String( "esriSLSDashDotDot" ) )
return Qt::DashDotDotLine;
else if ( style == QLatin1String( "esriSLSDot" ) )
return Qt::DotLine;
else if ( style == QLatin1String( "esriSLSNull" ) )
return Qt::NoPen;
else
return Qt::SolidLine;
}

Qt::BrushStyle QgsArcGisRestUtils::parseEsriFillStyle( const QString &style )
{
if ( style == QLatin1String( "esriSFSBackwardDiagonal" ) )
return Qt::BDiagPattern;
else if ( style == QLatin1String( "esriSFSCross" ) )
return Qt::CrossPattern;
else if ( style == QLatin1String( "esriSFSDiagonalCross" ) )
return Qt::DiagCrossPattern;
else if ( style == QLatin1String( "esriSFSForwardDiagonal" ) )
return Qt::FDiagPattern;
else if ( style == QLatin1String( "esriSFSHorizontal" ) )
return Qt::HorPattern;
else if ( style == QLatin1String( "esriSFSNull" ) )
return Qt::NoBrush;
else if ( style == QLatin1String( "esriSFSSolid" ) )
return Qt::SolidPattern;
else if ( style == QLatin1String( "esriSFSVertical" ) )
return Qt::VerPattern;
else
return Qt::SolidPattern;
}

QUrl QgsArcGisRestUtils::parseUrl( const QUrl &url )
{
QUrl modifiedUrl( url );
Expand Down
13 changes: 13 additions & 0 deletions src/providers/arcgisrest/qgsarcgisrestutils.h
Expand Up @@ -27,6 +27,10 @@ class QgsRectangle;
class QgsAbstractGeometry;
class QgsCoordinateReferenceSystem;
class QgsFeedback;
class QgsSymbol;
class QgsLineSymbol;
class QgsFillSymbol;
class QgsMarkerSymbol;

class QgsArcGisRestUtils
{
Expand All @@ -46,6 +50,15 @@ class QgsArcGisRestUtils
static QByteArray queryService( const QUrl &url, QString &errorTitle, QString &errorText, QgsFeedback *feedback = nullptr );
static QVariantMap queryServiceJSON( const QUrl &url, QString &errorTitle, QString &errorText, QgsFeedback *feedback = nullptr );

static std::unique_ptr< QgsSymbol > parseEsriSymbolJson( const QVariantMap &symbolData );
static std::unique_ptr< QgsLineSymbol > parseEsriLineSymbolJson( const QVariantMap &symbolData );
static std::unique_ptr< QgsFillSymbol > parseEsriFillSymbolJson( const QVariantMap &symbolData );
static std::unique_ptr< QgsMarkerSymbol > parseEsriMarkerSymbolJson( const QVariantMap &symbolData );

static QColor parseEsriColorJson( const QVariant &colorData );
static Qt::PenStyle parseEsriLineStyle( const QString &style );
static Qt::BrushStyle parseEsriFillStyle( const QString &style );

static QUrl parseUrl( const QUrl &url );
};

Expand Down

0 comments on commit 6002cc4

Please sign in to comment.