For reports, presentations, and websites, I regularly need maps: a raster layer, contextual boundaries, a legend, and maybe a logo. Nothing requiring an elaborate page layout. And I often need more than one. For the COMBINED project, for example, I map the same study area in Rotterdam over and over. The extent, the boundaries, and the logo stay put; only the raster and its legend change. Exactly the kind of repetition worth automating.
GRASS already has good tools for this, but neither quite fit my workflow. ps.map and the Cartographic Composer (g.gui.psmap) are made for standalone, page-based cartography: excellent for that, but page-centered and without per-layer transparency, so combining two rasters is not an option.
m.printws comes closer. It renders the visible layers of a saved workspace, with per-layer transparency, and can crop the result to the map area. But the map definition is the workspace, which belongs to the Map Display. Swapping the raster for the next map means returning to the GUI and saving again. Because the composition is tied to the display, consistent legend positions, font sizes, and line widths across figure sizes take some fiddling.
What I was missing was an editable description of the map itself: something I can build layer by layer, keep as a template, and change one line when only the raster changes. I had a few custom scripts for this, but for easier use, I turned these into an addons: m.printmap. And because composing a map in the Map Display is still the quickest way to get the layers right, I created the accompanying addon m.printmap.gxw that turns that workspace into a reusable spec file.
The map as a small text file
With m.printmap, the composition lives in a separate, human-readable JSON file. You build it one layer at a time, from the GUI or command line; each add call appends a layer. Size, resolution, font sizes, and line widths are explicit, and the computational region determines what appears on the map.
# Libraries
from grass.tools import Tools
tools = Tools()
# Set the region you want to print
tools.g_region(raster="bgt_osm")
# Create the spec file, including the
# font size and type, and background color
tools.m_printmap(
new_spec="bgt_osm.json",
operation="settings",
fontsize=11,
font="arial",
background="#66b2ff",
)
# Add a raster layer
tools.m_printmap(spec="bgt_osm.json", type="raster", raster="bgt_osm")
# Add a vector layer (note that you can define
# colors using RGB or hex notation, or by name)
tools.m_printmap(
spec="bgt_osm.json",
type="vector",
vector="RotterdamMask",
cats=1,
color="104:104:104:255",
fill_color="white",
opacity=0.73,
)
# Add the logo
tools.m_printmap(
spec="bgt_osm.json",
operation="add",
type="image",
image="Logo_Combined.png",
image_at="5,20,2,50",
)
# Print the map
tools.m_printmap(
spec="bgt_osm.json",
operation="render",
output="example01.png",
width=650,
overwrite=True,
)The resulting JSON file holds the general settings (font, font size, and background color) and the parameters of each individual layer. Parameters are stored under the name used by the underlying display command, so a vector layer reads like d.vect and a raster legend like d.legend.
{
"settings": {
"font": "arial",
"fontsize": 11.0,
"background": "#66b2ff"
},
"layers": [
{
"kind": "raster",
"map": "bgt_osm@Rotterdam",
"opacity": 1.0
},
{
"kind": "vector",
"map": "RotterdamMask",
"type": "area",
"color": "104:104:104:255",
"fill_color": "white",
"cats": "1",
"opacity": 0.73
},
{
"kind": "image",
"image": "Logo_Combined.png",
"at": [5.0, 20.0, 2.0, 50.0],
"opacity": 1.0
}
]
}Layers can be inserted, deleted, reordered, updated, or replaced by position. That makes the spec file easy to reuse. Below, I replaced the raster layer, added a scalebar and moved the logo.
# Move the logo to the other side of the map
tools.m_printmap(
spec="bgt_osm.json",
operation="update",
position=3,
image_at="2,20,88,99",
)
# Add a bar scale
tools.m_printmap(
spec="bgt_osm.json",
operation="add",
type="barscale",
barscale_at="2,10",
barscale_bgcolor="none",
)
# Replace the raster layer
tools.m_printmap(
spec="bgt_osm.json",
operation="replace",
type="raster",
position=1,
raster="cond_lage_veg",
)
# Print the new map
tools.m_printmap(
spec="bgt_osm.json",
operation="render",
output="example02.png",
width=650,
overwrite=True,
){
"settings": {
"font": "arial",
"fontsize": 11.0,
"background": "#66b2ff"
},
"layers": [
{
"kind": "raster",
"map": "cond_lage_veg",
"opacity": 1.0
},
{
"kind": "vector",
"map": "RotterdamMask",
"type": "area",
"color": "104:104:104:255",
"fill_color": "white",
"cats": "1",
"opacity": 0.73
},
{
"kind": "image",
"image": "Logo_Combined.png",
"at": [2.0, 20.0, 88.0, 99.0],
"opacity": 1.0
},
{
"kind": "barscale",
"at": [2.0, 10.0],
"bgcolor": "none",
"opacity": 1.0
}
]
}The rest of the layout stays untouched. And because the spec is plain JSON, you can just as well edit it in a text editor or loop over it in a script to generate a whole series of maps.
This also makes it easier to keep a series of figures consistent. The requested size refers to the final image, either in pixels (width=650) or physically (figure_width=16 cm at dpi=300). Font sizes and line widths are in points and rendered at the output resolution, so the same spec gives the same-looking figure whether you render it small for the web or large for print.
From a workspace to a template
m.printmap.gxw converts the visible, supported layers and overlays of a saved .gxw workspace into a spec file. It reads the same workspaces as m.printws, from which this part was borrowed, but with a different target. Where m.printws renders the workspace into a map, m.printmap.gxw turns it into an editable spec file that you can adjust, script, or reuse as a template. If you just want to export an existing Map Display, m.printws is more direct. m.printmap.gxw pays off when the workspace is a starting point for a composition you will render repeatedly.
Acknowledgment
These addons started as a solution to my own research needs, but just in case somebody else might find them useful, check out the manual pages of m.printmap and m.printmap.gxw here, or download the addons and try them out yourself.
Besides the aforementioned COMBINED-project, my work in the research groups Innovative biomonitoring and Climate-robust Landscapes at the HAS green academy has provided much of the context and motivation for developing these addons.




Período: 31/08 a 01/10/2026
Horário: das 19h às 22h
Carga horária: 48 horas – 16 aulas
As duas primeiras aulas serão gravadas. A partir de 02/09, os encontros serão online e ao vivo.
















Historic map of Belém used in the QGIS 4.2 splash screen. Source:
MobiML architecture overview. Photo by Michael Szell. Source:
Imagen de la plataforma ciudadana Observadores del Mar
Event/occurrence model (Fuente: Biodiversity Information Standards (TDWG), licensed under a 











