Styles for vector tiles are typically written in the MapLibre GL style language. These definitions exist in JSON, which, for various reasons, is not a good language for humans to write in. Software called Charites preprocessed my Street Spirit style to improve readability. This helped a great deal and removed the two largest pain points: no comments and only one file.
Charites’ main features are:1
- letting you write in YAML instead of JSON,
- importing other YAML files into the main one,
- and the use of simple variables to allow common style constants to be set once.
I made use of the first two features, but I still found myself limited by them. I still faced issues where the project’s structure revolved around the styling language rather than what makes sense to a cartographer.
A good example of this was road layers. With Charites I had to have separate files for each layer, so I had separate files for each of the thirteen layers. With glug I was able to have one file for the twelve layers that drew the casings and fill, and one file for the road text layer. This kept related definitions in the same place, which makes everything more readable.
Expressions are essential for writing performant MapLibre GL styles. A simple expression example is filtering to only show labels of larger areas. A filter property such the one below does this.
{"filter":
[">=",
["get","way_area"],
['*', 750, 6126430366.1, ['^', 0.25, ["zoom"]]]
]}
This JSON doesn’t allow comments, so you have to hope it’s obvious from the text what is happening.[2]
Charites lets this be reformulated to YAML
filter:
- '>='
- [get, way_area]
- ['*', 750, 6126430366.1, ['^', 0.25, [zoom]]] # Only show areas larger than 750 pixels at current zoom
It’s a bit better, but the comment shows a limitation of the language. Filtering by area is a very common task. It shouldn’t require a comment to explain the basic math. With glug this instead becomes
# 750 pixels is generally large enough to make it obvious
# the label is associated with the fill in the water layer
filter way_area >= (750 * 6_126_430_366.1 * 0.25**zoom)
Data-driven expressions are what are important for performance. You tend to have one style layer for each layer in the vector tile, rather than separate layers for each differently styled feature. A good example of this is a landuse layer where you have different colors for different types of landuse. You’d write something like
{
"id": "landuse",
"source": "spirit",
"source-layer": "landuse",
"type": "fill",
"layout": {
"fill-sort-key": [
"match", ["get", "landuse"],
"commercial", 4,
"retail", 3,
"residential", 2,
"industrial", 1,
0
]
},
"paint": {
"fill-color": [
"match", ["get", "landuse"],
"commercial", "#f9f0dd",
"retail", "#fde2e5"
"residential", "#fde2e5",
"industrial", "#e0e3de",
0
]
}
}
I’ll omit the charites version for brevity, but it’s the same formulated into YAML. That allows comments but is otherwise the same.
layer(:landuse, source: :spirit, source_layer: :landuse) {
fill_sort_key match(landuse,
'commercial', 4,
'retail', 3,
'residential', 2,
'industrial', 1,
0)
fill_color match(landuse,
'residential', '#f9f0dd', # lch(95,10,90)
'commercial', '#fde2e5', # lch(92,10,10)
'retail', '#fde2e5', # lch(92,10,10)
'industrial', '#e0e3de', # lch(90,3,130)
:red)
}
This reduces the line-count and complexity significantly. It’s not a huge deal in this case, but this was the simplest expression example I could find! Each road layer is about five times as long.
The current Street Spirit style is 1,842 lines over 47 files. Rewritten into glug it’s 743 lines over 22 files.
Glug isn’t perfect, but I’m finding it way easier to read the styling I’ve written. I’m hopeful I can use some features to cut down on repetitive definitions but at this stage I was focused on a simple port of the existing style.
[2]: Adding a “_comment” key as others have suggested doesn’t help because you can’t stick it in the middle of the filter expression yaml-include
Discussion
dpschepಅವರಿಂದ 12:22 ರಲ್ಲಿ 2 ಸೆಪ್ಟೆಂಬರ್ 2025ರಂದು ಅಭಿಪ್ರಾಯ
Interesting! I also have made the transition from a plain MapLibre style to charites and then from charites to something else. But I took inspiration from OSM Americana & Protomaps and used JavaScript. Is there any reason you chose a domain of nascent domain specific language like glug instead of a general purpose language?
Some links that folks might find useful: * https://github.com/systemed/glug * https://github.com/unvt/charites