Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add dedicated conversion for MapInfo marker symbols
  • Loading branch information
nyalldawson committed May 10, 2021
1 parent 8bbac01 commit 94dc209
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 4 deletions.
Expand Up @@ -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

Expand Down
17 changes: 16 additions & 1 deletion src/core/qgsogrutils.cpp
Expand Up @@ -1451,6 +1451,22 @@ std::unique_ptr<QgsSymbol> 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<QgsSymbol> 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+)" ) );
Expand Down Expand Up @@ -1602,7 +1618,6 @@ std::unique_ptr<QgsSymbol> QgsOgrUtils::symbolFromStyleString( const QString &st
{
return nullptr;
}
break;

case QgsSymbol::Line:
if ( styles.contains( QStringLiteral( "pen" ) ) )
Expand Down
169 changes: 169 additions & 0 deletions src/core/symbology/qgsmapinfosymbolconverter.cpp
Expand Up @@ -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 );
}
10 changes: 10 additions & 0 deletions src/core/symbology/qgsmapinfosymbolconverter.h
Expand Up @@ -24,6 +24,7 @@

class QgsLineSymbol;
class QgsFillSymbol;
class QgsMarkerSymbol;

/**
* Context for a MapInfo symbol conversion operation.
Expand Down Expand Up @@ -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
28 changes: 25 additions & 3 deletions tests/src/python/test_qgsembeddedsymbolrenderer.py
Expand Up @@ -37,12 +37,14 @@
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):

Expand Down Expand Up @@ -174,10 +176,10 @@ def testMapInfoLineSymbolConversion(self):
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()
Expand All @@ -194,6 +196,26 @@ def testMapFillLineSymbolConversion(self):
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()
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/testdata/mapinfo/marker_styles.DAT
Binary file not shown.
Binary file added tests/testdata/mapinfo/marker_styles.ID
Binary file not shown.
Binary file added tests/testdata/mapinfo/marker_styles.MAP
Binary file not shown.
8 changes: 8 additions & 0 deletions 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) ;

0 comments on commit 94dc209

Please sign in to comment.