[GIS] How to create a symbol with graduated marker size and line width in pyQGIS

pyqgispythonqgis

I am using QGIS + pyQGIS + Python. Layer geometry is line. Each line has a magnitude and a direction (from 1st node to 2nd node).

What exactly I want to perform is "graduated width marker symbology overlaid on the graduated width line symbology"

Example:

line thickness 5mm. and 5mm. width marker symbol overlaid on it. ===(5mm)===

line thickness 3mm. and 3mm. width marker symbol overlaid on it. —–(3mm)—–

For the time being, I can perform the following; either "(i) lines with graduated thickness" or "(ii) single line symbology overlaid with single marker symbol (no graduated thickness)".

Can I perform it at the same time? Graduated width markers on graduated thickness lines.

Below code does all same appearing markers overlaid on all same appearing lines:

#Use the currently selected layer
layer = qgis.utils.iface.mapCanvas().currentLayer()
registry = QgsSymbolLayerV2Registry.instance()
lineMeta = registry.symbolLayerMetadata("SimpleLine")
markerMeta = registry.symbolLayerMetadata("MarkerLine")

symbol = QgsSymbolV2.defaultSymbol(layer.geometryType())

#Line layer
lineLayer = lineMeta.createSymbolLayer({'width': '0.26', 'color': '255,0,0', 'offset': '0', 'penstyle': 'solid', 'use_custom_dash': '0', 'joinstyle': 'bevel', 'capstyle': 'square'})

#Marker layer
markerLayer = markerMeta.createSymbolLayer({'width': '0.26', 'color': '255,0,0', 'rotate': '1', 'placement': 'centralpoint', 'offset': '0'})
subSymbol = markerLayer.subSymbol()

#Replace the default layer with our own SimpleMarker
subSymbol.deleteSymbolLayer(0)
triangle = registry.symbolLayerMetadata("SimpleMarker").createSymbolLayer({'name': 'filled_arrowhead', 'color': '255,0,0', 'color_border': '0,0,0', 'offset': '0,0', 'size': '3', 'angle': '0'})
subSymbol.appendSymbolLayer(triangle)

#Replace the default layer with our two custom layers
symbol.deleteSymbolLayer(0)
symbol.appendSymbolLayer(lineLayer)
symbol.appendSymbolLayer(markerLayer)

#Replace the renderer of the current layer
renderer = QgsSingleSymbolRendererV2(symbol)
layer.setRendererV2(renderer)    

QgsMapLayerRegistry.instance().addMapLayer( layer )

Below code performs only graduated width lines (equal interval) BUT NO MARKERS OVERLAID

def validatedDefaultSymbol(geometryType):
    symbol = QgsSymbolV2.defaultSymbol(geometryType)
    if symbol is None:
        if geometryType == QGis.Point:
            symbol = QgsMarkerSymbolV2()
        elif geometryType == QGis.Line:
            symbol =  QgsLineSymbolV2()
        elif geometryType == QGis.Polygon:
            symbol = QgsFillSymbolV2()
    return symbol

def makeSymbologyForRange(layer, min ,max, label ,colour, alpha, width):
    symbol = validatedDefaultSymbol(layer.geometryType())
    symbol.setColor(colour)
    symbol.setAlpha(alpha)
    symbol.setWidth(width)
    range = QgsRendererRangeV2(min, max, symbol, label)
    return range                    

vlayer = QgsVectorLayer(str(SaveShpName), str(SaveShpName), 'ogr')
myTargetField = 'magnitude'

#For Equal Interval Classes
if dlg.ui.comboBoxSelectSymbology.currentText() == "Equal Interval":
    GradSymNoOfClasses = dlg.ui.spinBoxClasses.value() 
    GradSymInterval = round(((int(GradSymMax) - int(GradSymMin)) / float(GradSymNoOfClasses)),0)
    myRangeList = []
    for i in range(GradSymNoOfClasses):
        if i == 0:
            classlabel = str(GradSymMin)+" - "+str(int(GradSymMin)+GradSymInterval)
            myRangeList.append(makeSymbologyForRange( vlayer, int(GradSymMin), int(GradSymMin)+GradSymInterval, classlabel , QColor(0, 255-(255*i/GradSymNoOfClasses), 255*i/GradSymNoOfClasses), 1, 0.25 ))
        elif i == (GradSymNoOfClasses - 1):
            classlabel = str(int(GradSymMin)+(GradSymInterval*i)+0.001)+" - "+str(GradSymMax)
            myRangeList.append(makeSymbologyForRange( vlayer, (GradSymInterval*i)+0.001, int(GradSymMax), classlabel , QColor(0, 255-(255*i/GradSymNoOfClasses), 255*i/GradSymNoOfClasses), 1, i/1.25 ))
        else:
            classlabel = str(int(GradSymMin)+(GradSymInterval*i)+0.001)+" - "+str(int(GradSymMin)+GradSymInterval*(i+1))
            myRangeList.append(makeSymbologyForRange( vlayer, int(GradSymMin)+(GradSymInterval*i)+0.001, int(GradSymMin)+GradSymInterval*(i+1), classlabel , QColor(0, 255-(255*i/GradSymNoOfClasses), 255*i/GradSymNoOfClasses), 1, i/1.25 ))

    myRenderer = QgsGraduatedSymbolRendererV2( myTargetField, myRangeList )
    vlayer.setRendererV2( myRenderer )

    QgsMapLayerRegistry.instance().addMapLayer( vlayer ) 

Best Answer

After working one full day on both codes; at last I was able to modify the code to create graduated symbology overlayed with proportional direction arrows.

def validatedDefaultSymbol(geometryType):
    symbol = QgsSymbolV2.defaultSymbol(geometryType)
    if symbol is None:
        if geometryType == QGis.Point:
            symbol = QgsMarkerSymbolV2()
        elif geometryType == QGis.Line:
            symbol =  QgsLineSymbolV2()
        elif geometryType == QGis.Polygon:
            symbol = QgsFillSymbolV2()
    return symbol

def makeSymbologyForRange(layer, min ,max, label ,colour, alpha, width):
    symbol = validatedDefaultSymbol(layer.geometryType())


    #Line layer
    lineLayer = lineMeta.createSymbolLayer({'width': '1', 'color': '255,0,0', 'offset': '0', 'penstyle': 'solid', 'use_custom_dash': '0', 'joinstyle': 'bevel', 'capstyle': 'square'})
    #Marker layer
    markerLayer = markerMeta.createSymbolLayer({'width': '0.26', 'color': '255,0,0', 'rotate': '1', 'placement': 'centralpoint', 'offset': '0'})
    subSymbol = markerLayer.subSymbol()
    #Replace the default layer with our own SimpleMarker
    subSymbol.deleteSymbolLayer(0)
    triangle = registry.symbolLayerMetadata("SimpleMarker").createSymbolLayer({'name': 'filled_arrowhead', 'color': '255,0,0', 'color_border': '255,255,255', 'offset': '0,0', 'size': '3', 'outline_width': '0.5', 'angle': '0'})
    subSymbol.appendSymbolLayer(triangle)
    #Replace the default layer with our two custom layers
    symbol.deleteSymbolLayer(0)
    symbol.appendSymbolLayer(lineLayer)
    symbol.appendSymbolLayer(markerLayer)


    symbol.setColor(colour)
    symbol.setAlpha(alpha)
    symbol.setWidth(width)
    range = QgsRendererRangeV2(min, max, symbol, label)
    return range                    

vlayer = QgsVectorLayer(str(SaveShpName), str(SaveShpName), 'ogr')
myTargetField = 'magnitude'
#Equal Interval
if dlg.ui.comboBoxSelectSymbology.currentText() == "Equal Interval":
    GradSymNoOfClasses = dlg.ui.spinBoxClasses.value() 
    GradSymInterval = round(((int(GradSymMax) - int(GradSymMin)) / float(GradSymNoOfClasses)),0)
    myRangeList = []
    for i in range(GradSymNoOfClasses):
        if i == 0:
            classlabel = str(GradSymMin)+" - "+str(int(GradSymMin)+GradSymInterval)
            myRangeList.append(makeSymbologyForRange( vlayer, int(GradSymMin), int(GradSymMin)+GradSymInterval, classlabel , QColor(0, 255-(255*i/GradSymNoOfClasses), 255*i/GradSymNoOfClasses), 1, 1 ))
        elif i == (GradSymNoOfClasses - 1):
            classlabel = str(int(GradSymMin)+(GradSymInterval*i)+0.001)+" - "+str(GradSymMax)
            myRangeList.append(makeSymbologyForRange( vlayer, (GradSymInterval*i)+0.001, int(GradSymMax), classlabel , QColor(0, 255-(255*i/GradSymNoOfClasses), 255*i/GradSymNoOfClasses), 1, i*3 ))
        else:
            classlabel = str(int(GradSymMin)+(GradSymInterval*i)+0.001)+" - "+str(int(GradSymMin)+GradSymInterval*(i+1))
            myRangeList.append(makeSymbologyForRange( vlayer, int(GradSymMin)+(GradSymInterval*i)+0.001, int(GradSymMin)+GradSymInterval*(i+1), classlabel , QColor(0, 255-(255*i/GradSymNoOfClasses), 255*i/GradSymNoOfClasses), 1, i*3 ))

    myRenderer = QgsGraduatedSymbolRendererV2( myTargetField, myRangeList )
    vlayer.setRendererV2( myRenderer )
    QgsMapLayerRegistry.instance().addMapLayer( vlayer )                     
Related Question