Skip to content

Commit

Permalink
Create converter for ArcGis FeatureServer type classBreaks
Browse files Browse the repository at this point in the history
  • Loading branch information
merydian authored Jul 17, 2024
1 parent 0a0625b commit b05a9ee
Show file tree
Hide file tree
Showing 3 changed files with 314 additions and 4 deletions.
130 changes: 128 additions & 2 deletions src/core/providers/arcgis/qgsarcgisrestutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@

#include <QRegularExpression>
#include <QUrl>
#include "qgsclassificationcustom.h"
#include "qgsclassificationequalinterval.h"
#include "qgsclassificationfixedinterval.h"
#include "qgsclassificationjenks.h"
#include "qgsclassificationquantile.h"
#include "qgsclassificationstandarddeviation.h"
#include "qgsgraduatedsymbolrenderer.h"

QMetaType::Type QgsArcGisRestUtils::convertFieldType( const QString &esriFieldType )
{
Expand Down Expand Up @@ -1021,8 +1028,127 @@ QgsFeatureRenderer *QgsArcGisRestUtils::convertRenderer( const QVariantMap &rend
}
else if ( type == QLatin1String( "classBreaks" ) )
{
// currently unsupported
return nullptr;
const QString attrName = rendererData.value( QStringLiteral( "field" ) ).toString();
std::unique_ptr< QgsGraduatedSymbolRenderer > graduatedRenderer = std::make_unique< QgsGraduatedSymbolRenderer >( attrName );

const QVariantList classBreakInfos = rendererData.value( QStringLiteral( "classBreakInfos" ) ).toList();
const QVariantMap authoringInfo = rendererData.value( QStringLiteral( "authoringInfo" ) ).toMap();
QVariantMap symbolData;

QString esriMode = authoringInfo.value( QStringLiteral( "classificationMethod" ) ).toString();
if ( esriMode.isEmpty() )
{
esriMode = rendererData.value( QStringLiteral( "classificationMethod" ) ).toString();
}

if ( esriMode == QLatin1String( "esriClassifyDefinedInterval" ) )
{
QgsClassificationFixedInterval *method = new QgsClassificationFixedInterval();
graduatedRenderer->setClassificationMethod( method );
}
else if ( esriMode == QLatin1String( "esriClassifyEqualInterval" ) )
{
QgsClassificationEqualInterval *method = new QgsClassificationEqualInterval();
graduatedRenderer->setClassificationMethod( method );
}
else if ( esriMode == QLatin1String( "esriClassifyGeometricalInterval" ) )
{
QgsClassificationCustom *method = new QgsClassificationCustom();
graduatedRenderer->setClassificationMethod( method );
}
else if ( esriMode == QLatin1String( "esriClassifyManual" ) )
{
QgsClassificationCustom *method = new QgsClassificationCustom();
graduatedRenderer->setClassificationMethod( method );
}
else if ( esriMode == QLatin1String( "esriClassifyNaturalBreaks" ) )
{
QgsClassificationJenks *method = new QgsClassificationJenks();
graduatedRenderer->setClassificationMethod( method );
}
else if ( esriMode == QLatin1String( "esriClassifyQuantile" ) )
{
QgsClassificationQuantile *method = new QgsClassificationQuantile();
graduatedRenderer->setClassificationMethod( method );
}
else if ( esriMode == QLatin1String( "esriClassifyStandardDeviation" ) )
{
QgsClassificationStandardDeviation *method = new QgsClassificationStandardDeviation();
graduatedRenderer->setClassificationMethod( method );
}
else
{
QgsDebugError( QStringLiteral( "ESRI classification mode %1 is not currently supported" ).arg( esriMode ) );
}


if ( !classBreakInfos.isEmpty() )
{
symbolData = classBreakInfos.at( 0 ).toMap().value( QStringLiteral( "symbol" ) ).toMap();
}
std::unique_ptr< QgsSymbol > symbol( QgsArcGisRestUtils::convertSymbol( symbolData ) );
double transparency = rendererData.value( QStringLiteral( "transparency" ) ).toDouble();

double opacity = ( 100.0 - transparency ) / 100.0;

if ( !symbol )
return nullptr;
else
{
symbol->setOpacity( opacity );
graduatedRenderer->setSourceSymbol( symbol.release() );
}

const QVariantList visualVariablesData = rendererData.value( QStringLiteral( "visualVariables" ) ).toList();
double lastValue = rendererData.value( QStringLiteral( "minValue" ) ).toDouble();
for ( const QVariant &visualVariable : visualVariablesData )
{
const QVariantList stops = visualVariable.toMap().value( QStringLiteral( "stops" ) ).toList();
for ( const QVariant &stop : stops )
{
const QVariantMap stopData = stop.toMap();
const QString label = stopData.value( QStringLiteral( "label" ) ).toString();
const double breakpoint = stopData.value( QStringLiteral( "value" ) ).toDouble();
std::unique_ptr< QgsSymbol > symbolForStop( graduatedRenderer->sourceSymbol()->clone() );

if ( visualVariable.toMap().value( QStringLiteral( "type" ) ).toString() == QStringLiteral( "colorInfo" ) )
{
// handle color change stops:
QColor fillColor = convertColor( stopData.value( QStringLiteral( "color" ) ) );
symbolForStop->setColor( fillColor );

QgsRendererRange range;

range.setLowerValue( lastValue );
range.setUpperValue( breakpoint );
range.setLabel( label );
range.setSymbol( symbolForStop.release() );

lastValue = breakpoint;
graduatedRenderer->addClass( range );
}
}
}
lastValue = rendererData.value( QStringLiteral( "minValue" ) ).toDouble();
for ( const QVariant &classBreakInfo : classBreakInfos )
{
const QVariantMap symbolData = classBreakInfo.toMap().value( QStringLiteral( "symbol" ) ).toMap();
std::unique_ptr< QgsSymbol > symbol( QgsArcGisRestUtils::convertSymbol( symbolData ) );
double classMaxValue = classBreakInfo.toMap().value( QStringLiteral( "classMaxValue" ) ).toDouble();
const QString label = classBreakInfo.toMap().value( QStringLiteral( "label" ) ).toString();

QgsRendererRange range;

range.setLowerValue( lastValue );
range.setUpperValue( classMaxValue );
range.setLabel( label );
range.setSymbol( symbol.release() );

lastValue = classMaxValue;
graduatedRenderer->addClass( range );
}

return graduatedRenderer.release();
}
else if ( type == QLatin1String( "heatmap" ) )
{
Expand Down
5 changes: 5 additions & 0 deletions src/providers/arcgisrest/qgsafsprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,11 @@ 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();
const QVariant transparency = layerData.value( QStringLiteral( "drawingInfo" ) ).toMap().value( QStringLiteral( "transparency" ) );
if ( transparency.isValid() )
{
mRendererDataMap.insert( QStringLiteral( "transparency" ), transparency );
}

mValid = true;
}
Expand Down
183 changes: 181 additions & 2 deletions tests/src/python/test_provider_afs.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
QgsVectorDataProviderTemporalCapabilities,
QgsVectorLayer,
QgsWkbTypes,
QgsGraduatedSymbolRenderer,
QgsSymbol,
QgsRendererRange,
)
import unittest
from qgis.testing import start_app, QgisTestCase
Expand Down Expand Up @@ -1122,8 +1125,8 @@ def testFieldAlias(self):
self.assertEqual(vl.fields().at(1).name(), 'second')
self.assertFalse(vl.fields().at(1).alias())

def testRenderer(self):
""" Test that renderer is correctly acquired from provider """
def testCategorizedRenderer(self):
""" Test that the categorized renderer is correctly acquired from provider """

endpoint = self.basetestpath + '/renderer_fake_qgis_http_endpoint'
with open(sanitize(endpoint, '?f=json'), 'wb') as f:
Expand Down Expand Up @@ -1223,6 +1226,182 @@ def testRenderer(self):
self.assertEqual(vl.renderer().categories()[0].value(), 'US')
self.assertEqual(vl.renderer().categories()[1].value(), 'Canada')

def testGraduatedRenderer(self):
""" Test that the graduated renderer is correctly acquired from provider """

endpoint = self.basetestpath + '/class_breaks_renderer_fake_qgis_http_endpoint'
with open(sanitize(endpoint, '?f=json'), 'wb') as f:
f.write(b"""{
"currentVersion": 11.2,
"id": 0,
"name": "Test graduated renderer",
"type": "Feature Layer",
"useStandardizedQueries": true,
"geometryType": "esriGeometryPolygon",
"minScale": 0,
"maxScale": 1155581,
"extent": {
"xmin": -17771274.9623,
"ymin": 2175061.919500001,
"xmax": -7521909.497300002,
"ymax": 9988155.384400003,
"spatialReference": {
"wkid": 102100,
"latestWkid": 3857
}
},
"drawingInfo": {
"renderer": {
"visualVariables": [
{
"type": "colorInfo",
"field": "SUM",
"valueExpression": null,
"stops": [
{
"value": 10151,
"color": [
255,
196,
174,
255
],
"label": "< 10,151"
},
{
"value": 632613.25,
"color": [
249,
129,
108,
255
],
"label": null
},
{
"value": 1255075.5,
"color": [
236,
82,
68,
255
],
"label": "1,255,075"
},
{
"value": 1877537.75,
"color": [
194,
61,
51,
255
],
"label": null
},
{
"value": 2500000,
"color": [
123,
66,
56,
255
],
"label": "> 2,500,000"
}
]
},
{
"type": "sizeInfo",
"target": "outline",
"expression": "view.scale",
"valueExpression": "$view.scale",
"stops": [
{
"size": 1.5,
"value": 3468153
},
{
"size": 0.75,
"value": 10837979
},
{
"size": 0.375,
"value": 43351915
},
{
"size": 0,
"value": 86703831
}
]
}
],
"authoringInfo": {
"classificationMethod": "esriClassifyEqualInterval",
"visualVariables": [
{
"type": "colorInfo",
"minSliderValue": 10151,
"maxSliderValue": 15185477,
"theme": "high-to-low"
}
]
},
"type": "classBreaks",
"field": "SUM",
"minValue": -9007199254740991,
"classBreakInfos": [
{
"symbol": {
"color": [
170,
170,
170,
255
],
"outline": {
"color": [
194,
194,
194,
64
],
"width": 0.75,
"type": "esriSLS",
"style": "esriSLSSolid"
},
"type": "esriSFS",
"style": "esriSFSSolid"
},
"classMaxValue": 9007199254740991
}
]
},
"transparency": 20
},
"allowGeometryUpdates": true
}""")

with open(sanitize(endpoint, '/query?f=json_where=1=1&returnIdsOnly=true'), 'wb') as f:
f.write(b"""
{
"objectIdFieldName": "OBJECTID",
"objectIds": [
1
]
}
""")

# Create test layer
vl = QgsVectorLayer("url='http://" + endpoint + "' crs='epsg:3857'", 'test', 'arcgisfeatureserver')
self.assertTrue(vl.isValid())
self.assertIsNotNone(vl.dataProvider().createRenderer())
self.assertIsInstance(vl.renderer(), QgsGraduatedSymbolRenderer)
self.assertIsInstance(vl.renderer().sourceSymbol(), QgsSymbol)
self.assertIsInstance(vl.renderer().ranges()[0], QgsRendererRange)
self.assertEqual(len(vl.renderer().ranges()), 6)
self.assertEqual(vl.renderer().ranges()[0][0], -9007199254740991)
self.assertEqual(vl.renderer().ranges()[-1][1], 9007199254740991)

def testBboxRestriction(self):
"""
Test limiting provider to features within a preset bounding box
Expand Down

0 comments on commit b05a9ee

Please sign in to comment.