Skip to content

Commit

Permalink
Complete QGIS -> FSL symbol conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Apr 30, 2024
1 parent 50e86b9 commit d1badd0
Show file tree
Hide file tree
Showing 2 changed files with 208 additions and 14 deletions.
103 changes: 96 additions & 7 deletions felt/core/fsl_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
QgsSimpleFillSymbolLayer,
QgsShapeburstFillSymbolLayer,
QgsGradientFillSymbolLayer,
QgsRasterFillSymbolLayer,
QgsSimpleLineSymbolLayer,
QgsLinePatternFillSymbolLayer,
QgsHashedLineSymbolLayer,
Expand All @@ -33,6 +32,7 @@
QgsSVGFillSymbolLayer,
QgsFontMarkerSymbolLayer,
QgsRandomMarkerFillSymbolLayer,
QgsArrowSymbolLayer,
QgsRenderContext,
QgsUnitTypes
)
Expand Down Expand Up @@ -100,11 +100,8 @@ def symbol_layer_to_fsl(
# Line types
QgsSimpleLineSymbolLayer: FslConverter.simple_line_to_fsl,
QgsMarkerLineSymbolLayer: FslConverter.marker_line_to_fsl,
# QgsHashedLineSymbolLayer: FslConverter.hashed_line_to_fsl,
# QgsArrowSymbolLayer
# QgsInterpolatedLineSymbolLayer
# QgsRasterLineSymbolLayer
# QgsLineburstSymbolLayer
QgsHashedLineSymbolLayer: FslConverter.hashed_line_to_fsl,
QgsArrowSymbolLayer: FslConverter.arrow_to_fsl,

# Fill types
QgsSimpleFillSymbolLayer: FslConverter.simple_fill_to_fsl,
Expand All @@ -122,6 +119,9 @@ def symbol_layer_to_fsl(
# QgsAnimatedMarkerSymbolLayer
# QgsVectorFieldSymbolLayer
# QgsGeometryGeneratorSymbolLayer
# QgsInterpolatedLineSymbolLayer
# QgsRasterLineSymbolLayer
# QgsLineburstSymbolLayer
}

for _class, converter in SYMBOL_LAYER_CONVERTERS.items():
Expand Down Expand Up @@ -269,7 +269,6 @@ def marker_line_to_fsl(

res = {
'color': color_str,
'strokeColor': "rgba(0, 0, 0, 0)",
}

if layer.placement() == QgsMarkerLineSymbolLayer.Interval:
Expand All @@ -293,6 +292,96 @@ def marker_line_to_fsl(

return results

@staticmethod
def hashed_line_to_fsl(
layer: QgsHashedLineSymbolLayer,
context: ConversionContext,
symbol_opacity: float = 1) -> List[Dict[str, object]]:
"""
Converts a QGIS hashed line symbol layer to FSL
"""
hatch_symbol = layer.subSymbol()
if hatch_symbol is None:
return []

converted_hatch = FslConverter.symbol_to_fsl(hatch_symbol, context)
if not converted_hatch:
return []

context.push_warning('Hatched lines are not supported, converting to a solid line')

results = []
for converted_layer in converted_hatch:
color_str = converted_layer.get('color')
if not color_str:
continue

res = {
'color': color_str,
}

if layer.placement() == QgsMarkerLineSymbolLayer.Interval:
interval_pixels = FslConverter.convert_to_pixels(layer.interval(), layer.intervalUnit(), context)
try:
hatch_size = float(converted_layer['size'])
except TypeError:
continue

res['dashArray'] = [hatch_size, interval_pixels - hatch_size]
res['size'] = hatch_size
else:
# hardcoded size, there's no point using the marker size as it will visually appear
# as a much fatter line due to the missing spaces between markers
res['size'] = 1

if symbol_opacity < 1:
res['opacity'] = symbol_opacity

results.append(res)

return results

@staticmethod
def arrow_to_fsl(
layer: QgsArrowSymbolLayer,
context: ConversionContext,
symbol_opacity: float = 1) -> List[Dict[str, object]]:
"""
Converts a QGIS arrow symbol layer to FSL
"""
fill_symbol = layer.subSymbol()
if fill_symbol is None:
return []

converted_fill = FslConverter.symbol_to_fsl(fill_symbol, context)
if not converted_fill:
return []

context.push_warning('Arrows are not supported, converting to a solid line')

results = []
for converted_layer in converted_fill:
color_str = converted_layer.get('color')
if not color_str:
continue

# take average of start/end width
size = 0.5 * (FslConverter.convert_to_pixels(layer.arrowWidth(), layer.arrowWidthUnit(), context)
+ FslConverter.convert_to_pixels(layer.arrowStartWidth(), layer.arrowStartWidthUnit(),
context))

res = {
'color': color_str,
'size': size
}

if symbol_opacity < 1:
res['opacity'] = symbol_opacity

results.append(res)

return results

@staticmethod
def simple_fill_to_fsl(
layer: QgsSimpleFillSymbolLayer,
Expand Down
119 changes: 112 additions & 7 deletions felt/test/test_fsl_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
QgsPointPatternFillSymbolLayer,
QgsCentroidFillSymbolLayer,
QgsRandomMarkerFillSymbolLayer,
QgsMarkerLineSymbolLayer
QgsMarkerLineSymbolLayer,
QgsHashedLineSymbolLayer,
QgsArrowSymbolLayer
)

from .utilities import get_qgis_app
Expand Down Expand Up @@ -1026,16 +1028,14 @@ def test_marker_line_to_fsl(self):
self.assertEqual(
FslConverter.marker_line_to_fsl(line, conversion_context),
[{'color': 'rgb(120, 130, 140)',
'size': 2,
'strokeColor': 'rgba(0, 0, 0, 0)'}]
'size': 2}]
)

self.assertEqual(
FslConverter.marker_line_to_fsl(line, conversion_context, symbol_opacity=0.5),
[{'color': 'rgb(120, 130, 140)',
'size': 2,
'opacity': 0.5,
'strokeColor': 'rgba(0, 0, 0, 0)'}]
'opacity': 0.5}]
)

# interval mode
Expand All @@ -1046,8 +1046,113 @@ def test_marker_line_to_fsl(self):
FslConverter.marker_line_to_fsl(line, conversion_context),
[{'color': 'rgb(120, 130, 140)',
'size': 8,
'dashArray': [8.0, 5.0],
'strokeColor': 'rgba(0, 0, 0, 0)'}]
'dashArray': [8.0, 5.0]
}]
)

def test_hashed_line_to_fsl(self):
"""
Test hashed line conversion
"""
conversion_context = ConversionContext()

hatch = QgsSimpleLineSymbolLayer()
# invisible hatch
hatch.setColor(QColor(255, 0, 0, 0))

hatch_symbol = QgsLineSymbol()
hatch_symbol.changeSymbolLayer(0, hatch.clone())
line = QgsHashedLineSymbolLayer()
line.setSubSymbol(hatch_symbol.clone())
line.setPlacement(QgsHashedLineSymbolLayer.Vertex)

self.assertFalse(
FslConverter.hashed_line_to_fsl(line, conversion_context)
)

hatch.setColor(QColor(255, 0, 255))
hatch.setPenStyle(Qt.NoPen)
hatch_symbol.changeSymbolLayer(0, hatch.clone())
line.setSubSymbol(hatch_symbol.clone())
self.assertFalse(
FslConverter.hashed_line_to_fsl(line, conversion_context)
)

# with hatch
hatch.setColor(QColor(120, 130, 140))
hatch.setPenStyle(Qt.SolidLine)
hatch_symbol.changeSymbolLayer(0, hatch.clone())
line.setSubSymbol(hatch_symbol.clone())

self.assertEqual(
FslConverter.hashed_line_to_fsl(line, conversion_context),
[{'color': 'rgb(120, 130, 140)',
'size': 1}]
)

self.assertEqual(
FslConverter.hashed_line_to_fsl(line, conversion_context, symbol_opacity=0.5),
[{'color': 'rgb(120, 130, 140)',
'size': 1,
'opacity': 0.5}]
)

# interval mode
hatch.setWidth(3)
hatch_symbol.changeSymbolLayer(0, hatch.clone())
line.setSubSymbol(hatch_symbol.clone())
line.setPlacement(QgsHashedLineSymbolLayer.Interval)
line.setInterval(10)
line.setIntervalUnit(QgsUnitTypes.RenderPoints)
self.assertEqual(
FslConverter.hashed_line_to_fsl(line, conversion_context),
[{'color': 'rgb(120, 130, 140)',
'size': 11,
'dashArray': [11.0, 2.0]
}]
)

def test_arrow_to_fsl(self):
"""
Test arrow conversion
"""
conversion_context = ConversionContext()

fill = QgsSimpleFillSymbolLayer()
# invisible fill
fill.setColor(QColor(255, 0, 0, 0))
fill.setStrokeStyle(Qt.NoPen)

fill_symbol = QgsFillSymbol()
fill_symbol.changeSymbolLayer(0, fill.clone())
line = QgsArrowSymbolLayer()
line.setSubSymbol(fill_symbol.clone())

self.assertFalse(
FslConverter.arrow_to_fsl(line, conversion_context)
)

# with fill
fill.setColor(QColor(120, 130, 140))
fill_symbol.changeSymbolLayer(0, fill.clone())
line.setSubSymbol(fill_symbol.clone())

line.setArrowWidth(5)
line.setArrowWidthUnit(QgsUnitTypes.RenderPoints)
line.setArrowStartWidth(0.3)
line.setArrowStartWidthUnit(QgsUnitTypes.RenderInches)

self.assertEqual(
FslConverter.arrow_to_fsl(line, conversion_context),
[{'color': 'rgb(120, 130, 140)',
'size': 18}]
)

self.assertEqual(
FslConverter.arrow_to_fsl(line, conversion_context, symbol_opacity=0.5),
[{'color': 'rgb(120, 130, 140)',
'size': 18,
'opacity': 0.5}]
)


Expand Down

0 comments on commit d1badd0

Please sign in to comment.