diff --git a/python/core/auto_generated/symbology/qgsmapinfosymbolconverter.sip.in b/python/core/auto_generated/symbology/qgsmapinfosymbolconverter.sip.in index a99e8c587d9..a330bac6db1 100644 --- a/python/core/auto_generated/symbology/qgsmapinfosymbolconverter.sip.in +++ b/python/core/auto_generated/symbology/qgsmapinfosymbolconverter.sip.in @@ -67,6 +67,15 @@ The caller takes ownership of the returned symbol. %Docstring Converts the MapInfo fill symbol with the specified ``identifier`` to a :py:class:`QgsFillSymbol`. +The caller takes ownership of the returned symbol. +%End + + static QgsMarkerSymbol *convertMarkerSymbol( int identifier, QgsMapInfoSymbolConversionContext &context, const QColor &color, double size, QgsUnitTypes::RenderUnit sizeUnit ) /Factory/; +%Docstring +Converts the MapInfo marker symbol with the specified ``identifier`` to a :py:class:`QgsMarkerSymbol`. + +This method will convert a MapInfo "MapInfo 3.0 Compatible" symbol with a specific ``identifier`` to a :py:class:`QgsMarkerSymbol`. + The caller takes ownership of the returned symbol. %End diff --git a/src/core/qgsogrutils.cpp b/src/core/qgsogrutils.cpp index 385bc2321f1..f486720378c 100644 --- a/src/core/qgsogrutils.cpp +++ b/src/core/qgsogrutils.cpp @@ -1451,6 +1451,22 @@ std::unique_ptr QgsOgrUtils::symbolFromStyleString( const QString &st const QString id = symbolStyle.value( QStringLiteral( "id" ) ).toString(); + // if the symbol is a mapinfo symbol, use dedicated converter for more accurate results + const thread_local QRegularExpression sMapInfoId = QRegularExpression( QStringLiteral( "mapinfo-sym-(\\d+)" ) ); + const QRegularExpressionMatch match = sMapInfoId.match( id ); + if ( match.hasMatch() ) + { + const int symbolId = match.captured( 1 ).toInt(); + QgsMapInfoSymbolConversionContext context; + + // ogr interpretations of mapinfo symbol sizes are too large -- scale these down + symbolSize *= 0.61; + + std::unique_ptr res( QgsMapInfoSymbolConverter::convertMarkerSymbol( symbolId, context, color, symbolSize, symbolSizeUnit ) ); + if ( res ) + return res; + } + std::unique_ptr< QgsMarkerSymbolLayer > markerLayer; const thread_local QRegularExpression sFontId = QRegularExpression( QStringLiteral( "font-sym-(\\d+)" ) ); @@ -1602,7 +1618,6 @@ std::unique_ptr QgsOgrUtils::symbolFromStyleString( const QString &st { return nullptr; } - break; case QgsSymbol::Line: if ( styles.contains( QStringLiteral( "pen" ) ) ) diff --git a/src/core/symbology/qgsmapinfosymbolconverter.cpp b/src/core/symbology/qgsmapinfosymbolconverter.cpp index 4e7b2736956..8593af2df88 100644 --- a/src/core/symbology/qgsmapinfosymbolconverter.cpp +++ b/src/core/symbology/qgsmapinfosymbolconverter.cpp @@ -1386,3 +1386,172 @@ QgsFillSymbol *QgsMapInfoSymbolConverter::convertFillSymbol( int identifier, Qgs } return new QgsFillSymbol( layers ); } + +QgsMarkerSymbol *QgsMapInfoSymbolConverter::convertMarkerSymbol( int identifier, QgsMapInfoSymbolConversionContext &context, const QColor &color, double size, QgsUnitTypes::RenderUnit sizeUnit ) +{ + QgsSimpleMarkerSymbolLayerBase::Shape shape; + bool isFilled = true; + bool isNull = false; + bool hasShadow = false; + double angle = 0; + QgsMarkerSymbolLayer::VerticalAnchorPoint vertAlign = QgsMarkerSymbolLayer::VCenter; + QPointF shadowOffset; + switch ( identifier ) + { + case 31: + // null symbol + isNull = true; + break; + + case 32: + shape = QgsSimpleMarkerSymbolLayer::Shape::Square; + break; + + case 33: + shape = QgsSimpleMarkerSymbolLayer::Shape::Diamond; + break; + + case 34: + shape = QgsSimpleMarkerSymbolLayer::Shape::Circle; + break; + + case 35: + shape = QgsSimpleMarkerSymbolLayer::Shape::Star; + break; + + case 36: + shape = QgsSimpleMarkerSymbolLayer::Shape::Triangle; + break; + + case 37: + shape = QgsSimpleMarkerSymbolLayer::Shape::Triangle; + angle = 180; + break; + + case 38: + shape = QgsSimpleMarkerSymbolLayer::Shape::Square; + isFilled = false; + break; + + case 39: + shape = QgsSimpleMarkerSymbolLayer::Shape::Diamond; + isFilled = false; + break; + + case 40: + shape = QgsSimpleMarkerSymbolLayer::Shape::Circle; + isFilled = false; + break; + + case 41: + shape = QgsSimpleMarkerSymbolLayer::Shape::Star; + isFilled = false; + break; + + case 42: + shape = QgsSimpleMarkerSymbolLayer::Shape::Triangle; + isFilled = false; + break; + + case 43: + shape = QgsSimpleMarkerSymbolLayer::Shape::Triangle; + angle = 180; + isFilled = false; + break; + + case 44: + shape = QgsSimpleMarkerSymbolLayer::Shape::Square; + hasShadow = true; + shadowOffset = QPointF( size * 0.1, size * 0.1 ); + break; + + case 45: + shape = QgsSimpleMarkerSymbolLayer::Shape::Triangle; + shadowOffset = QPointF( size * 0.2, size * 0.1 ); + hasShadow = true; + break; + + case 46: + shape = QgsSimpleMarkerSymbolLayer::Shape::Circle; + shadowOffset = QPointF( size * 0.1, size * 0.1 ); + hasShadow = true; + break; + + case 47: + shape = QgsSimpleMarkerSymbolLayer::Shape::Arrow; + size *= 0.66666; + angle = 45; + vertAlign = QgsMarkerSymbolLayer::Top; + break; + + case 48: + shape = QgsSimpleMarkerSymbolLayer::Shape::Arrow; + size *= 0.66666; + angle = 225; + vertAlign = QgsMarkerSymbolLayer::Top; + break; + + case 49: + shape = QgsSimpleMarkerSymbolLayer::Shape::Cross; + break; + + case 50: + shape = QgsSimpleMarkerSymbolLayer::Shape::Cross2; + break; + + case 51: + shape = QgsSimpleMarkerSymbolLayer::Shape::Cross; + break; + + default: + context.pushWarning( QObject::tr( "The symbol is not supported in QGIS" ) ); + return nullptr; + } + + std::unique_ptr< QgsSimpleMarkerSymbolLayer > simpleMarker = std::make_unique< QgsSimpleMarkerSymbolLayer >( shape, size ); + simpleMarker->setSizeUnit( sizeUnit ); + simpleMarker->setAngle( angle ); + simpleMarker->setVerticalAnchorPoint( vertAlign ); + + if ( isNull ) + { + simpleMarker->setFillColor( QColor( 0, 0, 0, 0 ) ); + simpleMarker->setStrokeStyle( Qt::NoPen ); + } + if ( isFilled && QgsSimpleMarkerSymbolLayer::shapeIsFilled( shape ) ) + { + simpleMarker->setColor( color ); + simpleMarker->setStrokeColor( QColor( 0, 0, 0 ) ); + simpleMarker->setStrokeWidth( 0 ); + } + else + { + simpleMarker->setFillColor( QColor( 0, 0, 0, 0 ) ); + simpleMarker->setStrokeColor( color ); + } + + QgsSymbolLayerList symbols; + if ( hasShadow ) + { + std::unique_ptr< QgsSimpleMarkerSymbolLayer > shadow( simpleMarker->clone() ); + shadow->setColor( QColor( 0, 0, 0 ) ); + shadow->setLocked( true ); + shadow->setOffset( shadowOffset ); + shadow->setOffsetUnit( sizeUnit ); + + symbols << shadow.release(); + symbols << simpleMarker.release(); + } + else + { + if ( identifier == 51 ) + { + std::unique_ptr< QgsSimpleMarkerSymbolLayer > second( simpleMarker->clone() ); + second->setShape( QgsSimpleMarkerSymbolLayer::Shape::Cross2 ); + symbols << second.release(); + } + symbols << simpleMarker.release(); + } + + return new QgsMarkerSymbol( symbols ); +} diff --git a/src/core/symbology/qgsmapinfosymbolconverter.h b/src/core/symbology/qgsmapinfosymbolconverter.h index fa17bb6bc1a..fd83bfac9a2 100644 --- a/src/core/symbology/qgsmapinfosymbolconverter.h +++ b/src/core/symbology/qgsmapinfosymbolconverter.h @@ -24,6 +24,7 @@ class QgsLineSymbol; class QgsFillSymbol; +class QgsMarkerSymbol; /** * Context for a MapInfo symbol conversion operation. @@ -80,6 +81,15 @@ class CORE_EXPORT QgsMapInfoSymbolConverter */ static QgsFillSymbol *convertFillSymbol( int identifier, QgsMapInfoSymbolConversionContext &context, const QColor &foreColor, const QColor &backColor = QColor() ) SIP_FACTORY; + /** + * Converts the MapInfo marker symbol with the specified \a identifier to a QgsMarkerSymbol. + * + * This method will convert a MapInfo "MapInfo 3.0 Compatible" symbol with a specific \a identifier to a QgsMarkerSymbol. + * + * The caller takes ownership of the returned symbol. + */ + static QgsMarkerSymbol *convertMarkerSymbol( int identifier, QgsMapInfoSymbolConversionContext &context, const QColor &color, double size, QgsUnitTypes::RenderUnit sizeUnit ) SIP_FACTORY; + }; #endif // QGSMAPINFOSYMBOLCONVERTER_H diff --git a/tests/src/python/test_qgsembeddedsymbolrenderer.py b/tests/src/python/test_qgsembeddedsymbolrenderer.py index c59b9adba45..0533a14ffae 100644 --- a/tests/src/python/test_qgsembeddedsymbolrenderer.py +++ b/tests/src/python/test_qgsembeddedsymbolrenderer.py @@ -37,12 +37,14 @@ from qgis.core import (QgsVectorLayer, QgsFeature, QgsGeometry ) -from qgis.testing import unittest +from qgis.testing import unittest, start_app from utilities import unitTestDataPath TEST_DATA_DIR = unitTestDataPath() +start_app() + class TestQgsEmbeddedSymbolRenderer(unittest.TestCase): @@ -174,10 +176,10 @@ class TestQgsEmbeddedSymbolRenderer(unittest.TestCase): renderchecker.setControlName('expected_embedded_mapinfo_lines') self.assertTrue(renderchecker.runTest('embedded_mapinfo_lines')) - def testMapFillLineSymbolConversion(self): + def testMapInfoFillSymbolConversion(self): line_layer = QgsVectorLayer(TEST_DATA_DIR + '/mapinfo/fill_styles.TAB', 'Fills', 'ogr') - renderer = QgsEmbeddedSymbolRenderer(defaultSymbol=QgsLineSymbol.createSimple({})) + renderer = QgsEmbeddedSymbolRenderer(defaultSymbol=QgsFillSymbol.createSimple({})) line_layer.setRenderer(renderer) mapsettings = QgsMapSettings() @@ -194,6 +196,26 @@ class TestQgsEmbeddedSymbolRenderer(unittest.TestCase): renderchecker.setControlName('expected_embedded_mapinfo_fills') self.assertTrue(renderchecker.runTest('embedded_mapinfo_fills')) + def testMapInfoMarkerSymbolConversion(self): + line_layer = QgsVectorLayer(TEST_DATA_DIR + '/mapinfo/marker_styles.TAB', 'Marker', 'ogr') + + renderer = QgsEmbeddedSymbolRenderer(defaultSymbol=QgsMarkerSymbol.createSimple({})) + line_layer.setRenderer(renderer) + + mapsettings = QgsMapSettings() + mapsettings.setOutputSize(QSize(2000, 4000)) + mapsettings.setOutputDpi(96) + mapsettings.setMagnificationFactor(2) + mapsettings.setExtent(line_layer.extent().buffered(0.1)) + + mapsettings.setLayers([line_layer]) + + renderchecker = QgsMultiRenderChecker() + renderchecker.setMapSettings(mapsettings) + renderchecker.setControlPathPrefix('embedded') + renderchecker.setControlName('expected_embedded_mapinfo_markers') + self.assertTrue(renderchecker.runTest('embedded_mapinfo_markers')) + if __name__ == '__main__': unittest.main() diff --git a/tests/testdata/control_images/embedded/expected_embedded_mapinfo_markers/expected_embedded_mapinfo_markers.png b/tests/testdata/control_images/embedded/expected_embedded_mapinfo_markers/expected_embedded_mapinfo_markers.png new file mode 100644 index 00000000000..4cae996fc6d Binary files /dev/null and b/tests/testdata/control_images/embedded/expected_embedded_mapinfo_markers/expected_embedded_mapinfo_markers.png differ diff --git a/tests/testdata/mapinfo/marker_styles.DAT b/tests/testdata/mapinfo/marker_styles.DAT new file mode 100644 index 00000000000..76f7dd8f912 Binary files /dev/null and b/tests/testdata/mapinfo/marker_styles.DAT differ diff --git a/tests/testdata/mapinfo/marker_styles.ID b/tests/testdata/mapinfo/marker_styles.ID new file mode 100644 index 00000000000..48c69d4f74d Binary files /dev/null and b/tests/testdata/mapinfo/marker_styles.ID differ diff --git a/tests/testdata/mapinfo/marker_styles.MAP b/tests/testdata/mapinfo/marker_styles.MAP new file mode 100644 index 00000000000..c415e673329 Binary files /dev/null and b/tests/testdata/mapinfo/marker_styles.MAP differ diff --git a/tests/testdata/mapinfo/marker_styles.TAB b/tests/testdata/mapinfo/marker_styles.TAB new file mode 100644 index 00000000000..f5de2b413e6 --- /dev/null +++ b/tests/testdata/mapinfo/marker_styles.TAB @@ -0,0 +1,8 @@ +!table +!version 300 +!charset WindowsLatin1 + +Definition Table + Type NATIVE Charset "WindowsLatin1" + Fields 1 + Field1 Char (10) ;