[{"data":1,"prerenderedAt":1359},["ShallowReactive",2],{"doc:\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Fuse-svg-marker-symbol-pyqgis":3},{"id":4,"title":5,"body":6,"description":1348,"extension":1349,"meta":1350,"navigation":258,"path":1355,"seo":1356,"stem":1357,"__hash__":1358},"docs\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Fuse-svg-marker-symbol-pyqgis\u002Findex.md","Use SVG and Font Markers in PyQGIS",{"type":7,"value":8,"toc":1334},"minimark",[9,13,17,26,175,180,202,206,212,396,414,417,459,477,481,484,511,543,546,606,611,738,742,745,832,840,893,898,902,905,964,977,980,984,990,1043,1057,1061,1070,1074,1080,1165,1169,1226,1230,1236,1240,1258,1276,1285,1295,1299,1330],[10,11,5],"h1",{"id":12},"use-svg-and-font-markers-in-pyqgis",[14,15,16],"p",{},"Sooner or later a point layer needs to be something other than a circle: a hospital cross, a wind turbine, a numbered pin, a directional chevron. QGIS offers three routes — an SVG file, a glyph from an installed font, or a raster image — and the choice affects portability, recolouring and how fast the map draws far more than it affects how the marker looks.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002F","Symbol Layers & Advanced Symbology in PyQGIS",". It covers creating each marker type from Python, making the path survive a move to another machine, recolouring an SVG per feature, and choosing between the three when performance matters.",[14,27,28],{},[29,30,35,39,43,50,59,69,75,82,87,93,97,102,107,111,116,119,124,127,130,134,138,142,146,154,158,161,164,167,170],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 316","img","Comparison of SVG, font and raster markers across four properties: recolouring, portability, rendering cost and where the artwork comes from","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"Three marker sources compared",[40,41,42],"desc",{},"An SVG marker reads a file from the QGIS search path and can be recoloured per feature if it uses the param convention. A font marker draws one glyph from an installed font, is cached and therefore fast, and recolours freely. A raster marker places a bitmap, cannot be recoloured, and is the only option when the artwork is a photograph.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","316","#f6f3ea",[51,52,58],"text",{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","What the marker is made of decides what you can do with it",[44,60],{"x":61,"y":62,"width":63,"height":64,"rx":65,"fill":66,"stroke":67,"style":68},"24","52","228","222","10","#eff3ff","#2563eb","stroke-width:2.5",[51,70,74],{"x":71,"y":72,"style":73,"fill":67,"textAnchor":57},"138","80","text-anchor:middle;font-size:12.5px;font-weight:bold;font-family:sans-serif","SvgMarker",[76,77],"circle",{"cx":71,"cy":78,"r":79,"fill":80,"stroke":67,"style":81},"118","18","#dbeafe","stroke-width:2",[83,84],"path",{"d":85,"stroke":67,"style":86},"M130 118 H146 M138 110 V126","stroke-width:3",[51,88,92],{"x":71,"y":89,"style":90,"fill":91,"textAnchor":57},"164","text-anchor:middle;font-size:11px;font-family:sans-serif","#2f3b35","a file on disk",[51,94,96],{"x":71,"y":95,"style":90,"fill":91,"textAnchor":57},"186","recolours if it uses",[51,98,101],{"x":71,"y":99,"style":100,"fill":91,"textAnchor":57},"204","text-anchor:middle;font-size:11px;font-family:monospace","param(fill)",[51,103,106],{"x":71,"y":104,"style":90,"fill":105,"textAnchor":57},"230","#b45309","re-rasterised per",[51,108,110],{"x":71,"y":109,"style":90,"fill":105,"textAnchor":57},"248","size and colour",[44,112],{"x":113,"y":62,"width":63,"height":64,"rx":65,"fill":114,"stroke":115,"style":68},"266","#edf8e9","#15803d",[51,117,118],{"x":53,"y":72,"style":73,"fill":115,"textAnchor":57},"FontMarker",[51,120,123],{"x":53,"y":121,"style":122,"fill":115,"textAnchor":57},"130","text-anchor:middle;font-size:32px;font-family:serif","▲",[51,125,126],{"x":53,"y":89,"style":90,"fill":91,"textAnchor":57},"one glyph from a font",[51,128,129],{"x":53,"y":95,"style":90,"fill":91,"textAnchor":57},"recolours freely",[51,131,133],{"x":53,"y":132,"style":90,"fill":91,"textAnchor":57},"208","font must be installed",[51,135,137],{"x":53,"y":136,"style":90,"fill":115,"textAnchor":57},"238","glyph cache — fastest",[44,139],{"x":140,"y":62,"width":63,"height":64,"rx":65,"fill":141,"stroke":105,"style":68},"508","#fdf2e2",[51,143,145],{"x":144,"y":72,"style":73,"fill":105,"textAnchor":57},"622","RasterMarker",[44,147],{"x":148,"y":149,"width":150,"height":151,"rx":152,"fill":153,"stroke":105,"style":81},"600","100","44","36","3","#e7e2d4",[83,155],{"d":156,"fill":157},"M604 130 L616 116 L626 126 L636 108 L640 130 Z","#b0a488",[51,159,160],{"x":144,"y":89,"style":90,"fill":91,"textAnchor":57},"a bitmap",[51,162,163],{"x":144,"y":95,"style":90,"fill":91,"textAnchor":57},"cannot be recoloured",[51,165,166],{"x":144,"y":132,"style":90,"fill":91,"textAnchor":57},"blurs when scaled up",[51,168,169],{"x":144,"y":136,"style":90,"fill":105,"textAnchor":57},"only for photographs",[51,171,174],{"x":53,"y":172,"style":90,"fill":173,"textAnchor":57},"302","#59645f","all three are marker symbol layers and stack with everything else",[176,177,179],"h2",{"id":178},"prerequisites","Prerequisites",[181,182,183,191,199],"ul",{},[184,185,186,190],"li",{},[187,188,189],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[184,192,193,194,198],{},"A point layer in the project; the examples use one called ",[195,196,197],"code",{},"facilities",".",[184,200,201],{},"For SVG markers, either an SVG in the QGIS SVG search paths or a file you can reference. For font markers, the font installed on every machine that will open the project.",[176,203,205],{"id":204},"place-an-svg-marker","Place an SVG marker",[14,207,208,211],{},[195,209,210],{},"QgsSvgMarkerSymbolLayer"," takes a path and a size, and every other property is optional.",[213,214,219],"pre",{"className":215,"code":216,"language":217,"meta":218,"style":218},"language-python shiki shiki-themes github-dark","from qgis.core import QgsProject, QgsMarkerSymbol, QgsSvgMarkerSymbolLayer\nfrom qgis.PyQt.QtGui import QColor\n\nlayer = QgsProject.instance().mapLayersByName(\"facilities\")[0]\n\nmarker = QgsSvgMarkerSymbolLayer(\"gpsicons\u002Fhospital.svg\")\nmarker.setSize(6.0)\nmarker.setFillColor(QColor(\"#b91c1c\"))\nmarker.setStrokeColor(QColor(\"#7f1d1d\"))\nmarker.setStrokeWidth(0.3)\n\nsymbol = QgsMarkerSymbol()\nsymbol.deleteSymbolLayer(0)\nsymbol.appendSymbolLayer(marker)\nlayer.renderer().setSymbol(symbol)\nlayer.triggerRepaint()\n","python","",[195,220,221,240,253,260,285,290,307,318,330,341,352,357,368,378,384,390],{"__ignoreMap":218},[222,223,226,230,234,237],"span",{"class":224,"line":225},"line",1,[222,227,229],{"class":228},"snl16","from",[222,231,233],{"class":232},"s95oV"," qgis.core ",[222,235,236],{"class":228},"import",[222,238,239],{"class":232}," QgsProject, QgsMarkerSymbol, QgsSvgMarkerSymbolLayer\n",[222,241,243,245,248,250],{"class":224,"line":242},2,[222,244,229],{"class":228},[222,246,247],{"class":232}," qgis.PyQt.QtGui ",[222,249,236],{"class":228},[222,251,252],{"class":232}," QColor\n",[222,254,256],{"class":224,"line":255},3,[222,257,259],{"emptyLinePlaceholder":258},true,"\n",[222,261,263,266,269,272,276,279,282],{"class":224,"line":262},4,[222,264,265],{"class":232},"layer ",[222,267,268],{"class":228},"=",[222,270,271],{"class":232}," QgsProject.instance().mapLayersByName(",[222,273,275],{"class":274},"sU2Wk","\"facilities\"",[222,277,278],{"class":232},")[",[222,280,46],{"class":281},"sDLfK",[222,283,284],{"class":232},"]\n",[222,286,288],{"class":224,"line":287},5,[222,289,259],{"emptyLinePlaceholder":258},[222,291,293,296,298,301,304],{"class":224,"line":292},6,[222,294,295],{"class":232},"marker ",[222,297,268],{"class":228},[222,299,300],{"class":232}," QgsSvgMarkerSymbolLayer(",[222,302,303],{"class":274},"\"gpsicons\u002Fhospital.svg\"",[222,305,306],{"class":232},")\n",[222,308,310,313,316],{"class":224,"line":309},7,[222,311,312],{"class":232},"marker.setSize(",[222,314,315],{"class":281},"6.0",[222,317,306],{"class":232},[222,319,321,324,327],{"class":224,"line":320},8,[222,322,323],{"class":232},"marker.setFillColor(QColor(",[222,325,326],{"class":274},"\"#b91c1c\"",[222,328,329],{"class":232},"))\n",[222,331,333,336,339],{"class":224,"line":332},9,[222,334,335],{"class":232},"marker.setStrokeColor(QColor(",[222,337,338],{"class":274},"\"#7f1d1d\"",[222,340,329],{"class":232},[222,342,344,347,350],{"class":224,"line":343},10,[222,345,346],{"class":232},"marker.setStrokeWidth(",[222,348,349],{"class":281},"0.3",[222,351,306],{"class":232},[222,353,355],{"class":224,"line":354},11,[222,356,259],{"emptyLinePlaceholder":258},[222,358,360,363,365],{"class":224,"line":359},12,[222,361,362],{"class":232},"symbol ",[222,364,268],{"class":228},[222,366,367],{"class":232}," QgsMarkerSymbol()\n",[222,369,371,374,376],{"class":224,"line":370},13,[222,372,373],{"class":232},"symbol.deleteSymbolLayer(",[222,375,46],{"class":281},[222,377,306],{"class":232},[222,379,381],{"class":224,"line":380},14,[222,382,383],{"class":232},"symbol.appendSymbolLayer(marker)\n",[222,385,387],{"class":224,"line":386},15,[222,388,389],{"class":232},"layer.renderer().setSymbol(symbol)\n",[222,391,393],{"class":224,"line":392},16,[222,394,395],{"class":232},"layer.triggerRepaint()\n",[14,397,398,401,402,405,406,409,410,413],{},[187,399,400],{},"Breakdown:"," The path given here is relative, and that is deliberate — QGIS resolves relative SVG paths against its SVG search paths, so ",[195,403,404],{},"gpsicons\u002Fhospital.svg"," finds the bundled icon on every installation regardless of where QGIS was installed. An absolute path works on your machine and breaks on everyone else's, which is the single most common reason a shared project shows empty squares where the markers should be. ",[195,407,408],{},"setFillColor()"," and ",[195,411,412],{},"setStrokeColor()"," only do anything if the SVG uses the QGIS parameter convention; on a fixed-colour SVG they are silently ignored.",[14,415,416],{},"To see which directories QGIS is searching, ask it:",[213,418,420],{"className":215,"code":419,"language":217,"meta":218,"style":218},"from qgis.core import QgsApplication\n\nfor path in QgsApplication.svgPaths():\n    print(path)\n",[195,421,422,433,437,451],{"__ignoreMap":218},[222,423,424,426,428,430],{"class":224,"line":225},[222,425,229],{"class":228},[222,427,233],{"class":232},[222,429,236],{"class":228},[222,431,432],{"class":232}," QgsApplication\n",[222,434,435],{"class":224,"line":242},[222,436,259],{"emptyLinePlaceholder":258},[222,438,439,442,445,448],{"class":224,"line":255},[222,440,441],{"class":228},"for",[222,443,444],{"class":232}," path ",[222,446,447],{"class":228},"in",[222,449,450],{"class":232}," QgsApplication.svgPaths():\n",[222,452,453,456],{"class":224,"line":262},[222,454,455],{"class":281},"    print",[222,457,458],{"class":232},"(path)\n",[14,460,461,463,464,467,468,471,472,476],{},[187,462,400],{}," ",[195,465,466],{},"svgPaths()"," returns the resolved list — the bundled ",[195,469,470],{},"svg\u002F"," directory, anything added under ",[473,474,475],"em",{},"Settings → Options → System",", and the project's own directory when the project has been saved. Dropping your organisation's icon set into a folder listed here, and referencing it relatively, is what makes a style portable across a team.",[176,478,480],{"id":479},"make-an-svg-recolourable","Make an SVG recolourable",[14,482,483],{},"An SVG only accepts colours from QGIS if its fill and stroke attributes are written as parameters.",[213,485,489],{"className":486,"code":487,"language":488,"meta":218,"style":218},"language-xml shiki shiki-themes github-dark","\u003Cpath d=\"M12 2 L22 22 L2 22 Z\"\n      fill=\"param(fill) #cccccc\"\n      stroke=\"param(outline) #333333\"\n      stroke-width=\"param(outline-width) 1\" \u002F>\n","xml",[195,490,491,496,501,506],{"__ignoreMap":218},[222,492,493],{"class":224,"line":225},[222,494,495],{},"\u003Cpath d=\"M12 2 L22 22 L2 22 Z\"\n",[222,497,498],{"class":224,"line":242},[222,499,500],{},"      fill=\"param(fill) #cccccc\"\n",[222,502,503],{"class":224,"line":255},[222,504,505],{},"      stroke=\"param(outline) #333333\"\n",[222,507,508],{"class":224,"line":262},[222,509,510],{},"      stroke-width=\"param(outline-width) 1\" \u002F>\n",[14,512,513,515,516,519,520,523,524,409,527,530,531,523,533,409,535,538,539,542],{},[187,514,400],{}," That element sits inside an ordinary SVG root with a square viewBox. The ",[195,517,518],{},"param(name) fallback"," syntax is QGIS-specific: the parameter is used when QGIS supplies one, and the literal after it is used by any other SVG renderer, so the file still opens correctly in a browser or Inkscape. The parameter names ",[195,521,522],{},"fill",", ",[195,525,526],{},"outline",[195,528,529],{},"outline-width"," are the ones wired to ",[195,532,408],{},[195,534,412],{},[195,536,537],{},"setStrokeWidth()","; arbitrary names are exposed through ",[195,540,541],{},"setParameters()"," instead and are how a marker can take, say, a per-feature label colour.",[14,544,545],{},"With that in place, a data-defined colour becomes possible:",[213,547,549],{"className":215,"code":548,"language":217,"meta":218,"style":218},"from qgis.core import QgsProperty, QgsSymbolLayer\n\nmarker.setDataDefinedProperty(\n    QgsSymbolLayer.PropertyFillColor,\n    QgsProperty.fromExpression(\n        \"case when \\\"beds\\\" > 200 then '#b91c1c' else '#2563eb' end\"\n    ),\n)\n",[195,550,551,562,566,571,576,581,597,602],{"__ignoreMap":218},[222,552,553,555,557,559],{"class":224,"line":225},[222,554,229],{"class":228},[222,556,233],{"class":232},[222,558,236],{"class":228},[222,560,561],{"class":232}," QgsProperty, QgsSymbolLayer\n",[222,563,564],{"class":224,"line":242},[222,565,259],{"emptyLinePlaceholder":258},[222,567,568],{"class":224,"line":255},[222,569,570],{"class":232},"marker.setDataDefinedProperty(\n",[222,572,573],{"class":224,"line":262},[222,574,575],{"class":232},"    QgsSymbolLayer.PropertyFillColor,\n",[222,577,578],{"class":224,"line":287},[222,579,580],{"class":232},"    QgsProperty.fromExpression(\n",[222,582,583,586,589,592,594],{"class":224,"line":292},[222,584,585],{"class":274},"        \"case when ",[222,587,588],{"class":281},"\\\"",[222,590,591],{"class":274},"beds",[222,593,588],{"class":281},[222,595,596],{"class":274}," > 200 then '#b91c1c' else '#2563eb' end\"\n",[222,598,599],{"class":224,"line":309},[222,600,601],{"class":232},"    ),\n",[222,603,604],{"class":224,"line":320},[222,605,306],{"class":232},[14,607,608,610],{},[187,609,400],{}," This works, and it is also the most expensive thing you can ask an SVG marker to do. Each distinct colour produces a separate rasterisation of the file, and the cache is keyed on the combination of size and colour — so a continuous colour ramp on fifty thousand points effectively disables caching. Where the colour varies continuously, a font marker or a simple marker will draw the same map in a fraction of the time.",[14,612,613],{},[29,614,617,620,623,626,641,644,651,657,661,663,667,671,676,680,683,687,691,694,700,704,708,712,718,722,725,728,732,735],{"viewBox":615,"role":32,"ariaLabel":616,"xmlns":34},"0 0 760 292","How an SVG marker is resolved and cached: a relative path is looked up in the QGIS search paths, the file is parsed, parameters are substituted, and the result is rasterised into a cache keyed by size and colour",[36,618,619],{},"The SVG marker render path",[40,621,622],{},"A relative path is resolved against the QGIS SVG search paths. The file is parsed once. For each distinct combination of size and colour the parameters are substituted and the artwork is rasterised into a cache entry. Features reusing a combination hit the cache; a per-feature colour produces a new entry each time.",[44,624],{"x":46,"y":46,"width":47,"height":625,"fill":49},"292",[627,628,629],"defs",{},[630,631,638],"marker",{"id":632,"viewBox":633,"refX":634,"refY":635,"markerWidth":636,"markerHeight":636,"orient":637},"svgmArrow","0 0 10 10","8","5","7","auto-start-reverse",[83,639],{"d":640,"fill":91},"M0 0 L10 5 L0 10 z",[51,642,643],{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"The cache is keyed on size and colour together",[44,645],{"x":646,"y":647,"width":648,"height":649,"rx":634,"fill":650,"stroke":173,"style":81},"20","58","150","72","#fffdf7",[51,652,656],{"x":653,"y":654,"style":655,"fill":56,"textAnchor":57},"95","84","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","relative path",[51,658,404],{"x":653,"y":659,"style":660,"fill":91,"textAnchor":57},"106","text-anchor:middle;font-size:10px;font-family:monospace",[44,662],{"x":132,"y":647,"width":648,"height":649,"rx":634,"fill":66,"stroke":67,"style":81},[51,664,666],{"x":665,"y":654,"style":655,"fill":67,"textAnchor":57},"283","search paths",[51,668,670],{"x":665,"y":659,"style":669,"fill":91,"textAnchor":57},"text-anchor:middle;font-size:10px;font-family:sans-serif","bundled · user · project",[44,672],{"x":673,"y":647,"width":648,"height":649,"rx":634,"fill":674,"stroke":675,"style":81},"396","#eef7f4","#0f766e",[51,677,679],{"x":678,"y":654,"style":655,"fill":675,"textAnchor":57},"471","param substitution",[51,681,682],{"x":678,"y":659,"style":660,"fill":91,"textAnchor":57},"fill · outline · width",[44,684],{"x":685,"y":647,"width":686,"height":649,"rx":634,"fill":114,"stroke":115,"style":68},"584","156",[51,688,690],{"x":689,"y":654,"style":655,"fill":115,"textAnchor":57},"662","raster cache",[51,692,693],{"x":689,"y":659,"style":669,"fill":91,"textAnchor":57},"one entry per combo",[224,695],{"x1":696,"y1":697,"x2":698,"y2":697,"stroke":91,"style":699},"170","94","202","stroke-width:2;marker-end:url(#svgmArrow)",[224,701],{"x1":702,"y1":697,"x2":703,"y2":697,"stroke":91,"style":699},"358","390",[224,705],{"x1":706,"y1":697,"x2":707,"y2":697,"stroke":91,"style":699},"546","578",[44,709],{"x":710,"y":89,"width":711,"height":697,"rx":65,"fill":114,"stroke":115,"style":81},"60","300",[51,713,717],{"x":714,"y":715,"style":716,"fill":115,"textAnchor":57},"210","190","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","four fixed classes",[51,719,721],{"x":714,"y":720,"style":90,"fill":91,"textAnchor":57},"214","4 cache entries, reused 50 000 times",[51,723,724],{"x":714,"y":136,"style":90,"fill":91,"textAnchor":57},"draws instantly",[44,726],{"x":727,"y":89,"width":711,"height":697,"rx":65,"fill":141,"stroke":105,"style":81},"400",[51,729,731],{"x":730,"y":715,"style":716,"fill":105,"textAnchor":57},"550","continuous colour ramp",[51,733,734],{"x":730,"y":720,"style":90,"fill":91,"textAnchor":57},"≈50 000 cache entries",[51,736,737],{"x":730,"y":136,"style":90,"fill":91,"textAnchor":57},"the cache stops being a cache",[176,739,741],{"id":740},"font-markers-and-why-they-are-usually-faster","Font markers, and why they are usually faster",[14,743,744],{},"A font marker draws a single character. Because Qt already caches rendered glyphs, changing colour or size costs almost nothing.",[213,746,748],{"className":215,"code":747,"language":217,"meta":218,"style":218},"from qgis.core import QgsFontMarkerSymbolLayer\nfrom qgis.PyQt.QtGui import QColor\n\nmarker = QgsFontMarkerSymbolLayer(\"Noto Sans Symbols 2\")\nmarker.setCharacter(\"▲\")           # black up-pointing triangle\nmarker.setSize(5.0)\nmarker.setColor(QColor(\"#0f766e\"))\nmarker.setAngle(0)\n",[195,749,750,761,771,775,789,804,813,823],{"__ignoreMap":218},[222,751,752,754,756,758],{"class":224,"line":225},[222,753,229],{"class":228},[222,755,233],{"class":232},[222,757,236],{"class":228},[222,759,760],{"class":232}," QgsFontMarkerSymbolLayer\n",[222,762,763,765,767,769],{"class":224,"line":242},[222,764,229],{"class":228},[222,766,247],{"class":232},[222,768,236],{"class":228},[222,770,252],{"class":232},[222,772,773],{"class":224,"line":255},[222,774,259],{"emptyLinePlaceholder":258},[222,776,777,779,781,784,787],{"class":224,"line":262},[222,778,295],{"class":232},[222,780,268],{"class":228},[222,782,783],{"class":232}," QgsFontMarkerSymbolLayer(",[222,785,786],{"class":274},"\"Noto Sans Symbols 2\"",[222,788,306],{"class":232},[222,790,791,794,797,800],{"class":224,"line":287},[222,792,793],{"class":232},"marker.setCharacter(",[222,795,796],{"class":274},"\"▲\"",[222,798,799],{"class":232},")           ",[222,801,803],{"class":802},"sjoCn","# black up-pointing triangle\n",[222,805,806,808,811],{"class":224,"line":292},[222,807,312],{"class":232},[222,809,810],{"class":281},"5.0",[222,812,306],{"class":232},[222,814,815,818,821],{"class":224,"line":309},[222,816,817],{"class":232},"marker.setColor(QColor(",[222,819,820],{"class":274},"\"#0f766e\"",[222,822,329],{"class":232},[222,824,825,828,830],{"class":224,"line":320},[222,826,827],{"class":232},"marker.setAngle(",[222,829,46],{"class":281},[222,831,306],{"class":232},[14,833,834,463,836,839],{},[187,835,400],{},[195,837,838],{},"setCharacter()"," takes the character itself, not a code point number, so a Python escape or a literal both work. The font family string must match what Qt reports, and Qt silently substitutes a default when the family is missing — which is why a project that looked right on the author's machine shows the wrong glyph elsewhere. Guard against that by checking availability before applying the style:",[213,841,843],{"className":215,"code":842,"language":217,"meta":218,"style":218},"from qgis.PyQt.QtGui import QFontDatabase\n\nif \"Noto Sans Symbols 2\" not in QFontDatabase().families():\n    raise RuntimeError(\"required marker font is not installed\")\n",[195,844,845,856,860,877],{"__ignoreMap":218},[222,846,847,849,851,853],{"class":224,"line":225},[222,848,229],{"class":228},[222,850,247],{"class":232},[222,852,236],{"class":228},[222,854,855],{"class":232}," QFontDatabase\n",[222,857,858],{"class":224,"line":242},[222,859,259],{"emptyLinePlaceholder":258},[222,861,862,865,868,871,874],{"class":224,"line":255},[222,863,864],{"class":228},"if",[222,866,867],{"class":274}," \"Noto Sans Symbols 2\"",[222,869,870],{"class":228}," not",[222,872,873],{"class":228}," in",[222,875,876],{"class":232}," QFontDatabase().families():\n",[222,878,879,882,885,888,891],{"class":224,"line":262},[222,880,881],{"class":228},"    raise",[222,883,884],{"class":281}," RuntimeError",[222,886,887],{"class":232},"(",[222,889,890],{"class":274},"\"required marker font is not installed\"",[222,892,306],{"class":232},[14,894,895,897],{},[187,896,400],{}," Failing loudly at style time is much better than shipping a map where every wind turbine is the letter that Qt happened to substitute. In a plugin, a check like this belongs next to the other startup validation rather than inside the render path.",[176,899,901],{"id":900},"anchors-offsets-and-pins","Anchors, offsets and pins",[14,903,904],{},"A circle is symmetric, so it does not matter where QGIS considers its centre. A pin does — the point of the pin has to land on the coordinate, not the middle of its head.",[213,906,908],{"className":215,"code":907,"language":217,"meta":218,"style":218},"from qgis.core import QgsMarkerSymbolLayer\nfrom qgis.PyQt.QtCore import QPointF\n\nmarker.setVerticalAnchorPoint(QgsMarkerSymbolLayer.Bottom)\nmarker.setHorizontalAnchorPoint(QgsMarkerSymbolLayer.HCenter)\nmarker.setOffset(QPointF(0, -0.4))\n",[195,909,910,921,933,937,942,947],{"__ignoreMap":218},[222,911,912,914,916,918],{"class":224,"line":225},[222,913,229],{"class":228},[222,915,233],{"class":232},[222,917,236],{"class":228},[222,919,920],{"class":232}," QgsMarkerSymbolLayer\n",[222,922,923,925,928,930],{"class":224,"line":242},[222,924,229],{"class":228},[222,926,927],{"class":232}," qgis.PyQt.QtCore ",[222,929,236],{"class":228},[222,931,932],{"class":232}," QPointF\n",[222,934,935],{"class":224,"line":255},[222,936,259],{"emptyLinePlaceholder":258},[222,938,939],{"class":224,"line":262},[222,940,941],{"class":232},"marker.setVerticalAnchorPoint(QgsMarkerSymbolLayer.Bottom)\n",[222,943,944],{"class":224,"line":287},[222,945,946],{"class":232},"marker.setHorizontalAnchorPoint(QgsMarkerSymbolLayer.HCenter)\n",[222,948,949,952,954,956,959,962],{"class":224,"line":292},[222,950,951],{"class":232},"marker.setOffset(QPointF(",[222,953,46],{"class":281},[222,955,523],{"class":232},[222,957,958],{"class":228},"-",[222,960,961],{"class":281},"0.4",[222,963,329],{"class":232},[14,965,966,968,969,972,973,976],{},[187,967,400],{}," The anchor decides which part of the artwork is placed on the coordinate: ",[195,970,971],{},"Bottom"," plus ",[195,974,975],{},"HCenter"," puts the bottom-centre of the marker on the point, which is exactly what a map pin, a tree symbol or a flag wants. The offset is applied afterwards in the layer's output unit and is the right tool for nudging a marker clear of a line it sits on, or for building the offset copy that acts as a drop shadow in a two-layer stack.",[14,978,979],{},"Anchoring and offsetting interact with rotation in a way worth knowing: rotation happens about the anchor point, so a chevron anchored at its base rotates like a weather vane, while the same chevron anchored at its centre spins in place. When a rotated marker drifts as the angle changes, the anchor is almost always the reason.",[176,981,983],{"id":982},"raster-markers-and-when-they-are-the-right-answer","Raster markers, and when they are the right answer",[14,985,986,989],{},[195,987,988],{},"QgsRasterMarkerSymbolLayer"," places a PNG or JPEG at each point. It cannot be recoloured and it softens when scaled beyond its native resolution, so it is a poor general-purpose marker — but for a layer of photographs, a set of national flags or a client's logo with gradients that must not be redrawn, it is the only faithful option.",[213,991,993],{"className":215,"code":992,"language":217,"meta":218,"style":218},"from qgis.core import QgsRasterMarkerSymbolLayer\n\nmarker = QgsRasterMarkerSymbolLayer(\"\u002Fdata\u002Ficons\u002Fturbine_128.png\")\nmarker.setSize(7.0)\nmarker.setOpacity(0.9)\n",[195,994,995,1006,1010,1024,1033],{"__ignoreMap":218},[222,996,997,999,1001,1003],{"class":224,"line":225},[222,998,229],{"class":228},[222,1000,233],{"class":232},[222,1002,236],{"class":228},[222,1004,1005],{"class":232}," QgsRasterMarkerSymbolLayer\n",[222,1007,1008],{"class":224,"line":242},[222,1009,259],{"emptyLinePlaceholder":258},[222,1011,1012,1014,1016,1019,1022],{"class":224,"line":255},[222,1013,295],{"class":232},[222,1015,268],{"class":228},[222,1017,1018],{"class":232}," QgsRasterMarkerSymbolLayer(",[222,1020,1021],{"class":274},"\"\u002Fdata\u002Ficons\u002Fturbine_128.png\"",[222,1023,306],{"class":232},[222,1025,1026,1028,1031],{"class":224,"line":262},[222,1027,312],{"class":232},[222,1029,1030],{"class":281},"7.0",[222,1032,306],{"class":232},[222,1034,1035,1038,1041],{"class":224,"line":287},[222,1036,1037],{"class":232},"marker.setOpacity(",[222,1039,1040],{"class":281},"0.9",[222,1042,306],{"class":232},[14,1044,1045,1047,1048,1051,1052,1056],{},[187,1046,400],{}," Size is again in the output unit, and the image is scaled to fit it, so supply artwork at roughly twice the largest size you will print at and no more — an oversized PNG per point is memory the renderer holds for no visible gain. ",[195,1049,1050],{},"setOpacity()"," here is per symbol layer, distinct from the whole-layer opacity discussed in ",[21,1053,1055],{"href":1054},"\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Fset-layer-opacity-and-blend-mode-pyqgis\u002F","opacity and blend modes",", and it is the one to use when only the icon should fade while its casing stays solid.",[176,1058,1060],{"id":1059},"which-one-to-choose","Which one to choose",[14,1062,1063,1064,972,1066,1069],{},"Use a simple marker when the shape is a shape — circle, square, triangle, star are all built in and cost nothing. Use a font marker when you need a symbol library and control over colour, and you can guarantee the font. Use an SVG marker when the artwork is genuinely bespoke, when it must match a house style, or when it needs the two-colour treatment that ",[195,1065,101],{},[195,1067,1068],{},"param(outline)"," gives. Reach for a raster marker only when the artwork is a photograph or a logo whose gradients will not survive being redrawn.",[176,1071,1073],{"id":1072},"qgis-version-compatibility","QGIS version compatibility",[14,1075,1076,1077,1079],{},"The examples target ",[187,1078,189],{}," (Python 3.12).",[1081,1082,1083,1099],"table",{},[1084,1085,1086],"thead",{},[1087,1088,1089,1093,1096],"tr",{},[1090,1091,1092],"th",{},"QGIS version",[1090,1094,1095],{},"Python",[1090,1097,1098],{},"Notes",[1100,1101,1102,1117,1131,1141,1152],"tbody",{},[1087,1103,1104,1108,1111],{},[1105,1106,1107],"td",{},"3.16 LTR",[1105,1109,1110],{},"3.7",[1105,1112,1113,1116],{},[195,1114,1115],{},"param()"," convention and all three marker classes present.",[1087,1118,1119,1122,1125],{},[1105,1120,1121],{},"3.22 LTR",[1105,1123,1124],{},"3.9",[1105,1126,1127,1130],{},[195,1128,1129],{},"QgsSvgMarkerSymbolLayer.setParameters()"," accepts arbitrary named parameters.",[1087,1132,1133,1136,1138],{},[1105,1134,1135],{},"3.28 LTR",[1105,1137,1124],{},[1105,1139,1140],{},"Remote SVG paths (http) supported with a network cache.",[1087,1142,1143,1146,1149],{},[1105,1144,1145],{},"3.34 LTR",[1105,1147,1148],{},"3.12",[1105,1150,1151],{},"Baseline for this page.",[1087,1153,1154,1157,1159],{},[1105,1155,1156],{},"3.40+",[1105,1158,1148],{},[1105,1160,1161,1164],{},[195,1162,1163],{},"QgsApplication.svgPaths()"," unchanged; SVG cache sizing exposed in settings.",[176,1166,1168],{"id":1167},"troubleshooting","Troubleshooting",[181,1170,1171,1180,1191,1201,1211,1217],{},[184,1172,1173,1176,1177,1179],{},[187,1174,1175],{},"Markers render as empty squares."," The SVG path did not resolve. Print ",[195,1178,1163],{}," and use a path relative to one of them.",[184,1181,1182,1187,1188,198],{},[187,1183,1184,1186],{},[195,1185,408],{}," does nothing."," The SVG has literal colours. Rewrite its fill as ",[195,1189,1190],{},"param(fill) #cccccc",[184,1192,1193,1196,1197,1200],{},[187,1194,1195],{},"The SVG is black."," Same cause — no parameters, so QGIS draws the file's own colours, and a file authored with ",[195,1198,1199],{},"fill=\"#000000\""," stays black.",[184,1202,1203,1206,1207,1210],{},[187,1204,1205],{},"The wrong glyph appears on another machine."," The font is not installed there and Qt substituted one. Check ",[195,1208,1209],{},"QFontDatabase().families()"," before applying the style.",[184,1212,1213,1216],{},[187,1214,1215],{},"The canvas crawls after switching to SVG markers."," A data-defined colour or size is defeating the raster cache. Reduce to a handful of discrete classes, or switch to a font marker.",[184,1218,1219,1222,1223,198],{},[187,1220,1221],{},"The marker is offset from the point."," The SVG's viewBox is not centred on the artwork. Either fix the file or compensate with ",[195,1224,1225],{},"setOffset(QPointF(x, y))",[176,1227,1229],{"id":1228},"conclusion","Conclusion",[14,1231,1232,1233,1235],{},"Reference SVGs by a path relative to the QGIS search paths so styles travel, write the artwork with ",[195,1234,101],{}," if it must be recoloured, and check the font exists before relying on a font marker. When a marker's colour or size varies per feature, remember that SVG markers pay for it in rasterisation and font markers do not.",[176,1237,1239],{"id":1238},"frequently-asked-questions","Frequently Asked Questions",[14,1241,1242,1245,1246,1249,1250,1253,1254,198],{},[187,1243,1244],{},"Can I load an SVG marker from a URL?","\nYes — QGIS 3.28 and newer accept an ",[195,1247,1248],{},"http"," or ",[195,1251,1252],{},"https"," path and cache the fetched file. It makes styles portable without shipping icons, at the cost of a network dependency at first render, which is a poor trade for anything that must run ",[21,1255,1257],{"href":1256},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002F","headless on a schedule",[14,1259,1260,1263,1264,1267,1268,1271,1272,1275],{},[187,1261,1262],{},"How do I use my own parameter names in an SVG?","\nWrite them as ",[195,1265,1266],{},"param(mycolour)"," in the file and set them with ",[195,1269,1270],{},"marker.setParameters({\"mycolour\": QgsProperty.fromExpression(\"...\")})",". The values are ",[195,1273,1274],{},"QgsProperty"," objects, so each one can be static or data-defined.",[14,1277,1278,1281,1284],{},[187,1279,1280],{},"Why does the marker size not match the SVG's own dimensions?",[195,1282,1283],{},"setSize()"," is the size of the rendered marker in the layer's output unit, and the SVG's viewBox is scaled to fit it. The file's internal units are irrelevant, which is why a well-made marker SVG uses a square viewBox.",[14,1286,1287,1290,1291,1294],{},[187,1288,1289],{},"Can a marker rotate to follow a bearing field?","\nYes. Set a data-defined ",[195,1292,1293],{},"QgsSymbolLayer.PropertyAngle"," from the field. Combined with a triangle glyph this turns a point layer into a flow map without touching the geometry.",[176,1296,1298],{"id":1297},"related","Related",[181,1300,1301,1306,1312,1318,1324],{},[184,1302,1303,1305],{},[21,1304,24],{"href":23}," — the guide this recipe belongs to",[184,1307,1308],{},[21,1309,1311],{"href":1310},"\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Fstack-symbol-layers-pyqgis\u002F","Stack Symbol Layers in PyQGIS",[184,1313,1314],{},[21,1315,1317],{"href":1316},"\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Fgeometry-generator-symbol-layer-pyqgis\u002F","Draw with a Geometry Generator in PyQGIS",[184,1319,1320],{},[21,1321,1323],{"href":1322},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002Fdata-defined-symbol-size-expression-pyqgis\u002F","Data-Defined Symbol Size with Expressions in PyQGIS",[184,1325,1326],{},[21,1327,1329],{"href":1328},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fadd-plugin-icon-and-resources-pyqgis\u002F","Add a Plugin Icon and Resources in PyQGIS",[1331,1332,1333],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}",{"title":218,"searchDepth":242,"depth":242,"links":1335},[1336,1337,1338,1339,1340,1341,1342,1343,1344,1345,1346,1347],{"id":178,"depth":242,"text":179},{"id":204,"depth":242,"text":205},{"id":479,"depth":242,"text":480},{"id":740,"depth":242,"text":741},{"id":900,"depth":242,"text":901},{"id":982,"depth":242,"text":983},{"id":1059,"depth":242,"text":1060},{"id":1072,"depth":242,"text":1073},{"id":1167,"depth":242,"text":1168},{"id":1228,"depth":242,"text":1229},{"id":1238,"depth":242,"text":1239},{"id":1297,"depth":242,"text":1298},"Place SVG, font and raster markers from Python — QGIS SVG search paths, the param(fill) recolouring convention, portable relative paths, and why font markers render faster.","md",{"slug":1351,"type":1352,"breadcrumb":1353,"datePublished":1354,"dateModified":1354},"use-svg-marker-symbol-pyqgis","article","SVG & Font Markers","2026-08-27","\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Fuse-svg-marker-symbol-pyqgis",{"title":5,"description":1348},"pyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Fuse-svg-marker-symbol-pyqgis\u002Findex","V2wO-fq_7jpEbE1pUv_R0hTe5qx3lzr-L-UBinhsEns",1787823360561]