mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-25 00:58:06 -05:00
Add method QgsCategorizedSymbolRenderer::matchToSymbols which
matches existing categories to symbol names from a QgsStyle object and copies matching symbols to these categories
This commit is contained in:
parent
97c95803c6
commit
97a964af4a
@ -255,6 +255,26 @@ Returns configuration of appearance of legend when using data-defined size for m
|
||||
Will return null if the functionality is disabled.
|
||||
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
int matchToSymbols( QgsStyle *style, QgsSymbol::SymbolType type,
|
||||
QVariantList &unmatchedCategories /Out/, QStringList &unmatchedSymbols /Out/, bool caseSensitive = true, bool useTolerantMatch = false );
|
||||
%Docstring
|
||||
Replaces category symbols with the symbols from a ``style`` that have a matching
|
||||
name and symbol ``type``.
|
||||
|
||||
The ``unmatchedCategories`` list will be filled with all existing categories which could not be matched
|
||||
to a symbol in ``style``.
|
||||
|
||||
The ``unmatchedSymbols`` list will be filled with all symbol names from ``style`` which were not be matched
|
||||
to an existing category.
|
||||
|
||||
If ``caseSensitive`` is false, then a case-insensitive match will be performed. If ``useTolerantMatch``
|
||||
is true, then non-alphanumeric characters in style and category names will be ignored during the match.
|
||||
|
||||
Returns the count of symbols matched.
|
||||
|
||||
.. versionadded:: 3.4
|
||||
%End
|
||||
|
||||
protected:
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "qgsvectorlayer.h"
|
||||
#include "qgslogger.h"
|
||||
#include "qgsproperty.h"
|
||||
#include "qgsstyle.h"
|
||||
|
||||
#include <QDomDocument>
|
||||
#include <QDomElement>
|
||||
@ -956,3 +957,65 @@ QgsDataDefinedSizeLegend *QgsCategorizedSymbolRenderer::dataDefinedSizeLegend()
|
||||
{
|
||||
return mDataDefinedSizeLegend.get();
|
||||
}
|
||||
|
||||
int QgsCategorizedSymbolRenderer::matchToSymbols( QgsStyle *style, const QgsSymbol::SymbolType type, QVariantList &unmatchedCategories, QStringList &unmatchedSymbols, const bool caseSensitive, const bool useTolerantMatch )
|
||||
{
|
||||
if ( !style )
|
||||
return 0;
|
||||
|
||||
int matched = 0;
|
||||
unmatchedSymbols = style->symbolNames();
|
||||
const QSet< QString > allSymbolNames = unmatchedSymbols.toSet();
|
||||
|
||||
const QRegularExpression tolerantMatchRe( QStringLiteral( "[^\\w\\d ]" ), QRegularExpression::UseUnicodePropertiesOption );
|
||||
|
||||
for ( int catIdx = 0; catIdx < mCategories.count(); ++catIdx )
|
||||
{
|
||||
const QVariant value = mCategories.at( catIdx ).value();
|
||||
const QString val = value.toString().trimmed();
|
||||
std::unique_ptr< QgsSymbol > symbol( style->symbol( val ) );
|
||||
// case-sensitive match
|
||||
if ( symbol && symbol->type() == type )
|
||||
{
|
||||
matched++;
|
||||
unmatchedSymbols.removeAll( val );
|
||||
updateCategorySymbol( catIdx, symbol.release() );
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( !caseSensitive || useTolerantMatch )
|
||||
{
|
||||
QString testVal = val;
|
||||
if ( useTolerantMatch )
|
||||
testVal.replace( tolerantMatchRe, QString() );
|
||||
|
||||
bool foundMatch = false;
|
||||
for ( const QString &name : allSymbolNames )
|
||||
{
|
||||
QString testName = name.trimmed();
|
||||
if ( useTolerantMatch )
|
||||
testName.replace( tolerantMatchRe, QString() );
|
||||
|
||||
if ( testName == testVal || ( !caseSensitive && testName.trimmed().compare( testVal, Qt::CaseInsensitive ) == 0 ) )
|
||||
{
|
||||
// found a case-insensitive match
|
||||
std::unique_ptr< QgsSymbol > symbol( style->symbol( name ) );
|
||||
if ( symbol && symbol->type() == type )
|
||||
{
|
||||
matched++;
|
||||
unmatchedSymbols.removeAll( name );
|
||||
updateCategorySymbol( catIdx, symbol.release() );
|
||||
foundMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( foundMatch )
|
||||
continue;
|
||||
}
|
||||
|
||||
unmatchedCategories << value;
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <QHash>
|
||||
|
||||
class QgsVectorLayer;
|
||||
class QgsStyle;
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
@ -225,6 +226,26 @@ class CORE_EXPORT QgsCategorizedSymbolRenderer : public QgsFeatureRenderer
|
||||
*/
|
||||
QgsDataDefinedSizeLegend *dataDefinedSizeLegend() const;
|
||||
|
||||
/**
|
||||
* Replaces category symbols with the symbols from a \a style that have a matching
|
||||
* name and symbol \a type.
|
||||
*
|
||||
* The \a unmatchedCategories list will be filled with all existing categories which could not be matched
|
||||
* to a symbol in \a style.
|
||||
*
|
||||
* The \a unmatchedSymbols list will be filled with all symbol names from \a style which were not be matched
|
||||
* to an existing category.
|
||||
*
|
||||
* If \a caseSensitive is false, then a case-insensitive match will be performed. If \a useTolerantMatch
|
||||
* is true, then non-alphanumeric characters in style and category names will be ignored during the match.
|
||||
*
|
||||
* Returns the count of symbols matched.
|
||||
*
|
||||
* \since QGIS 3.4
|
||||
*/
|
||||
int matchToSymbols( QgsStyle *style, QgsSymbol::SymbolType type,
|
||||
QVariantList &unmatchedCategories SIP_OUT, QStringList &unmatchedSymbols SIP_OUT, bool caseSensitive = true, bool useTolerantMatch = false );
|
||||
|
||||
protected:
|
||||
QString mAttrName;
|
||||
QgsCategoryList mCategories;
|
||||
|
@ -921,20 +921,14 @@ int QgsCategorizedSymbolRendererWidget::matchToSymbols( QgsStyle *style )
|
||||
if ( !mLayer || !style )
|
||||
return 0;
|
||||
|
||||
int matched = 0;
|
||||
for ( int catIdx = 0; catIdx < mRenderer->categories().count(); ++catIdx )
|
||||
{
|
||||
QString val = mRenderer->categories().at( catIdx ).value().toString();
|
||||
std::unique_ptr< QgsSymbol > symbol( style->symbol( val ) );
|
||||
if ( symbol &&
|
||||
( ( symbol->type() == QgsSymbol::Marker && mLayer->geometryType() == QgsWkbTypes::PointGeometry )
|
||||
|| ( symbol->type() == QgsSymbol::Line && mLayer->geometryType() == QgsWkbTypes::LineGeometry )
|
||||
|| ( symbol->type() == QgsSymbol::Fill && mLayer->geometryType() == QgsWkbTypes::PolygonGeometry ) ) )
|
||||
{
|
||||
matched++;
|
||||
mRenderer->updateCategorySymbol( catIdx, symbol.release() );
|
||||
}
|
||||
}
|
||||
const QgsSymbol::SymbolType type = mLayer->geometryType() == QgsWkbTypes::PointGeometry ? QgsSymbol::Marker
|
||||
: mLayer->geometryType() == QgsWkbTypes::LineGeometry ? QgsSymbol::Line
|
||||
: QgsSymbol::Fill;
|
||||
|
||||
QVariantList unmatchedCategories;
|
||||
QStringList unmatchedSymbols;
|
||||
const int matched = mRenderer->matchToSymbols( style, type, unmatchedCategories, unmatchedSymbols );
|
||||
|
||||
mModel->updateSymbology();
|
||||
return matched;
|
||||
}
|
||||
|
@ -18,10 +18,14 @@ from qgis.testing import unittest, start_app
|
||||
from qgis.core import (QgsCategorizedSymbolRenderer,
|
||||
QgsRendererCategory,
|
||||
QgsMarkerSymbol,
|
||||
QgsLineSymbol,
|
||||
QgsFillSymbol,
|
||||
QgsField,
|
||||
QgsFields,
|
||||
QgsFeature,
|
||||
QgsRenderContext
|
||||
QgsRenderContext,
|
||||
QgsSymbol,
|
||||
QgsStyle
|
||||
)
|
||||
from qgis.PyQt.QtCore import QVariant
|
||||
from qgis.PyQt.QtGui import QColor
|
||||
@ -38,6 +42,20 @@ def createMarkerSymbol():
|
||||
return symbol
|
||||
|
||||
|
||||
def createLineSymbol():
|
||||
symbol = QgsLineSymbol.createSimple({
|
||||
"color": "100,150,50"
|
||||
})
|
||||
return symbol
|
||||
|
||||
|
||||
def createFillSymbol():
|
||||
symbol = QgsFillSymbol.createSimple({
|
||||
"color": "100,150,50"
|
||||
})
|
||||
return symbol
|
||||
|
||||
|
||||
class TestQgsCategorizedSymbolRenderer(unittest.TestCase):
|
||||
|
||||
def testFilter(self):
|
||||
@ -312,6 +330,134 @@ class TestQgsCategorizedSymbolRenderer(unittest.TestCase):
|
||||
|
||||
renderer.stopRender(context)
|
||||
|
||||
def testMatchToSymbols(self):
|
||||
"""
|
||||
Test QgsCategorizedSymbolRender.matchToSymbols
|
||||
"""
|
||||
renderer = QgsCategorizedSymbolRenderer()
|
||||
renderer.setClassAttribute('x')
|
||||
|
||||
symbol_a = createMarkerSymbol()
|
||||
symbol_a.setColor(QColor(255, 0, 0))
|
||||
renderer.addCategory(QgsRendererCategory('a', symbol_a, 'a'))
|
||||
symbol_b = createMarkerSymbol()
|
||||
symbol_b.setColor(QColor(0, 255, 0))
|
||||
renderer.addCategory(QgsRendererCategory('b', symbol_b, 'b'))
|
||||
symbol_c = createMarkerSymbol()
|
||||
symbol_c.setColor(QColor(0, 0, 255))
|
||||
renderer.addCategory(QgsRendererCategory('c ', symbol_c, 'c'))
|
||||
|
||||
matched, unmatched_cats, unmatched_symbols = renderer.matchToSymbols(None, QgsSymbol.Marker)
|
||||
self.assertEqual(matched, 0)
|
||||
|
||||
style = QgsStyle()
|
||||
symbol_a = createMarkerSymbol()
|
||||
symbol_a.setColor(QColor(255, 10, 10))
|
||||
self.assertTrue(style.addSymbol('a', symbol_a))
|
||||
symbol_B = createMarkerSymbol()
|
||||
symbol_B.setColor(QColor(10, 255, 10))
|
||||
self.assertTrue(style.addSymbol('B ', symbol_B))
|
||||
symbol_b = createFillSymbol()
|
||||
symbol_b.setColor(QColor(10, 255, 10))
|
||||
self.assertTrue(style.addSymbol('b', symbol_b))
|
||||
symbol_C = createLineSymbol()
|
||||
symbol_C.setColor(QColor(10, 255, 10))
|
||||
self.assertTrue(style.addSymbol('C', symbol_C))
|
||||
symbol_C = createMarkerSymbol()
|
||||
symbol_C.setColor(QColor(10, 255, 10))
|
||||
self.assertTrue(style.addSymbol(' ----c/- ', symbol_C))
|
||||
|
||||
# non-matching symbol type
|
||||
matched, unmatched_cats, unmatched_symbols = renderer.matchToSymbols(style, QgsSymbol.Line)
|
||||
self.assertEqual(matched, 0)
|
||||
self.assertEqual(unmatched_cats, ['a', 'b', 'c '])
|
||||
self.assertEqual(unmatched_symbols, [' ----c/- ', 'B ', 'C', 'a', 'b'])
|
||||
|
||||
# exact match
|
||||
matched, unmatched_cats, unmatched_symbols = renderer.matchToSymbols(style, QgsSymbol.Marker)
|
||||
self.assertEqual(matched, 1)
|
||||
self.assertEqual(unmatched_cats, ['b', 'c '])
|
||||
self.assertEqual(unmatched_symbols, [' ----c/- ', 'B ', 'C', 'b'])
|
||||
|
||||
# make sure symbol was applied
|
||||
context = QgsRenderContext()
|
||||
renderer.startRender(context, QgsFields())
|
||||
symbol, ok = renderer.symbolForValue2('a')
|
||||
self.assertTrue(ok)
|
||||
self.assertEqual(symbol.color().name(), '#ff0a0a')
|
||||
renderer.stopRender(context)
|
||||
|
||||
# case insensitive match
|
||||
matched, unmatched_cats, unmatched_symbols = renderer.matchToSymbols(style, QgsSymbol.Marker, False)
|
||||
self.assertEqual(matched, 2)
|
||||
self.assertEqual(unmatched_cats, ['c '])
|
||||
self.assertEqual(unmatched_symbols, [' ----c/- ', 'C', 'b'])
|
||||
|
||||
# make sure symbols were applied
|
||||
context = QgsRenderContext()
|
||||
renderer.startRender(context, QgsFields())
|
||||
symbol, ok = renderer.symbolForValue2('a')
|
||||
self.assertTrue(ok)
|
||||
self.assertEqual(symbol.color().name(), '#ff0a0a')
|
||||
symbol, ok = renderer.symbolForValue2('b')
|
||||
self.assertTrue(ok)
|
||||
self.assertEqual(symbol.color().name(), '#0aff0a')
|
||||
renderer.stopRender(context)
|
||||
|
||||
# case insensitive match
|
||||
matched, unmatched_cats, unmatched_symbols = renderer.matchToSymbols(style, QgsSymbol.Marker, False)
|
||||
self.assertEqual(matched, 2)
|
||||
self.assertEqual(unmatched_cats, ['c '])
|
||||
self.assertEqual(unmatched_symbols, [' ----c/- ', 'C', 'b'])
|
||||
|
||||
# make sure symbols were applied
|
||||
context = QgsRenderContext()
|
||||
renderer.startRender(context, QgsFields())
|
||||
symbol, ok = renderer.symbolForValue2('a')
|
||||
self.assertTrue(ok)
|
||||
self.assertEqual(symbol.color().name(), '#ff0a0a')
|
||||
symbol, ok = renderer.symbolForValue2('b')
|
||||
self.assertTrue(ok)
|
||||
self.assertEqual(symbol.color().name(), '#0aff0a')
|
||||
renderer.stopRender(context)
|
||||
|
||||
# tolerant match
|
||||
matched, unmatched_cats, unmatched_symbols = renderer.matchToSymbols(style, QgsSymbol.Marker, True, True)
|
||||
self.assertEqual(matched, 2)
|
||||
self.assertEqual(unmatched_cats, ['b'])
|
||||
self.assertEqual(unmatched_symbols, ['B ', 'C', 'b'])
|
||||
|
||||
# make sure symbols were applied
|
||||
context = QgsRenderContext()
|
||||
renderer.startRender(context, QgsFields())
|
||||
symbol, ok = renderer.symbolForValue2('a')
|
||||
self.assertTrue(ok)
|
||||
self.assertEqual(symbol.color().name(), '#ff0a0a')
|
||||
symbol, ok = renderer.symbolForValue2('c ')
|
||||
self.assertTrue(ok)
|
||||
self.assertEqual(symbol.color().name(), '#0aff0a')
|
||||
renderer.stopRender(context)
|
||||
|
||||
# tolerant match, case insensitive
|
||||
matched, unmatched_cats, unmatched_symbols = renderer.matchToSymbols(style, QgsSymbol.Marker, False, True)
|
||||
self.assertEqual(matched, 3)
|
||||
self.assertFalse(unmatched_cats)
|
||||
self.assertEqual(unmatched_symbols, ['C', 'b'])
|
||||
|
||||
# make sure symbols were applied
|
||||
context = QgsRenderContext()
|
||||
renderer.startRender(context, QgsFields())
|
||||
symbol, ok = renderer.symbolForValue2('a')
|
||||
self.assertTrue(ok)
|
||||
self.assertEqual(symbol.color().name(), '#ff0a0a')
|
||||
symbol, ok = renderer.symbolForValue2('b')
|
||||
self.assertTrue(ok)
|
||||
self.assertEqual(symbol.color().name(), '#0aff0a')
|
||||
symbol, ok = renderer.symbolForValue2('c ')
|
||||
self.assertTrue(ok)
|
||||
self.assertEqual(symbol.color().name(), '#0aff0a')
|
||||
renderer.stopRender(context)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
Loading…
x
Reference in New Issue
Block a user