Short answer
Dense geological maps fail not because QGIS cannot draw labels but because they try to label everything at once. The fix is a deliberate hierarchy: rule-based labeling to reveal labels progressively by scale, placement and priority settings so the important features win collisions, obstacles so text routes around contacts and faults instead of burying them, and buffers or callouts to keep labels legible against busy fills. A good labeled map shows the right labels at each scale, not the most labels.
Why dense maps overload
A geological map packs in lithological units, formation codes, fault names, strike-and-dip stations, sample points, contacts, and structural annotation — often overlapping in the same crowded extent. QGIS's default "label every feature" behaviour then produces collisions the placement engine resolves by silently dropping labels, usually the wrong ones, with no record of why. The discipline is to decide which labels matter at which scale and let the engine enforce that, rather than fighting an all-or-nothing wall of text.
QGIS uses the PAL placement engine, which scores candidate label positions and resolves overlaps by priority. Almost every technique below is about giving that engine better instructions.
Rule-based labeling: the core tool
Switch the layer's labeling mode from Single labels to Rule-based labeling (Layer Styling panel > Labels). Each rule is an expression filter with its own text style and its own scale range. This is what lets a single layer behave differently as you zoom.
A typical formation layer:
- Rule 1 — Major units, all scales. Filter:
"rank" = 'formation'. Visible from 1:250,000 to 1:5,000. Larger, bold text. - Rule 2 — Member units, mid scale. Filter:
"rank" = 'member'. Visible only from 1:50,000 to 1:5,000. Smaller text. - Rule 3 — Minor lenses, large scale only. Filter:
"rank" = 'lens'. Visible from 1:10,000 to 1:1,000.
Zoomed out, only the major formations are labeled and the map reads cleanly; zoom in and the detail appears where there is room for it. You can also vary the content per rule with expressions — for example showing just a unit code ("unit_code") at small scales and the full name plus code at large scales: "unit_code" || '\n' || "unit_name".
Placement, priority, and obstacles
Three settings on the Placement and Rendering tabs do most of the heavy lifting.
Placement mode. Match it to geometry. For polygons (units), Around centroid or Free (Horizontal) with Only draw labels which fit inside the polygon keeps text on the right unit. For lines (faults, contacts), Curved or Parallel runs the label along the feature; set On line / Above line and allow it to follow the geometry. For points (stations, samples), Around point with a small offset.
Priority (0–10). When two labels compete for the same space, the higher priority wins and the loser is dropped. Give major formations and named faults a high priority (8–10) and incidental sample points a low one (1–3). This is how you control which labels survive a collision instead of leaving it to chance.
Obstacles. On the Rendering tab, mark important layers (the contacts layer, the fault layer, key point symbols) as obstacles with an appropriate weight. The engine then routes labels around those features so a formation name does not sit on top of a fault trace. You can also set a layer's labels to treat its own geometry as an obstacle.
Data-defined overrides. Almost every property has a data-defined button (the small ε/expression icon). Use Show label with an expression to force-show or suppress specific features (e.g. always label mapped type sections: "is_type_section" = 1), and Always show this label for legally or scientifically mandatory annotations even if they would otherwise collide.
Legibility against busy fills
Geological fills are saturated and patterned, so text needs help to stand out.
- Buffer. Add a 1–1.5 mm white (or light) text buffer (Buffer tab) so labels read over any unit colour. Keep it subtle; an over-thick halo looks crude at print scale.
- Callouts. When a label cannot fit on a small polygon, enable Callouts (a leader line) and place the label in nearby empty space, keeping the link explicit. This is far better than letting the label drift onto a neighbouring unit.
- Background / shadow. A faint drop shadow or a subtle background rectangle helps for critical labels, used sparingly.
- Font and size in millimetres. Set text size in mm (not points-on-screen) so it stays consistent between the canvas and the print layout. Aim for 6–9 pt equivalent body text; smaller becomes unreadable at print resolution.
A repeatable workflow
- Style after the analysis is stable. Don't label while the data model still changes; you will redo it.
- Classify a labeling rank attribute (formation/member/lens, major/minor fault) so rules can filter on it cleanly.
- Build rule-based rules with scale ranges matching the intended output scales (web zoom levels or print scales).
- Set placement per geometry type, priorities by importance, and obstacles for contacts/faults.
- Add buffers and callouts for legibility.
- Proof in the Print Layout at the real output scale, not just the canvas — placement and overlaps look different at the layout's fixed scale and DPI.
- Save the style (
.qmlor to the GeoPackage style table) and document the scale ranges so the next analyst inherits the logic rather than guessing.
Common pitfalls and why they happen
- Labeling every feature at every scale. The engine then drops labels unpredictably and the map looks both cluttered and incomplete. Use scale-ranged rules instead.
- Proofing only on the canvas. The canvas scale floats; the layout scale is fixed. Collisions and overlaps you didn't see on screen appear in the export because placement is scale-dependent.
- Labels covering contacts and faults because obstacles were not set — the linework, the actual geological information, gets buried under text.
- Point-size text instead of mm. Sizes that look fine on screen turn tiny or huge in a print at a different DPI. Millimetre units keep print output predictable.
- Leaving styled layers as temporary scratch layers, so the carefully tuned labeling is lost when the project reopens. Save styles to a persistent store.
QA and validation
Before export: review the map at each intended output scale in the Print Layout and confirm the right labels appear and the right ones drop; check that no formation name sits on a fault or contact; verify callouts point to the correct features; confirm text reads against the busiest fills (buffer is doing its job); and confirm the labeling style is saved with the project and the underlying data, CRS, units, and scale are documented so the cartography is reproducible.
Bathyl perspective
We treat labeling on dense geological maps as a hierarchy problem solved with rules, priority, and obstacles — not a styling afterthought. A map that shows the major units cleanly when zoomed out and reveals detail on the way in communicates the geology, and saving the rule logic with the project means the next person can extend it instead of rebuilding it.
Related reading
- QGIS Symbology for Faults and Contacts
- QGIS Contours From DEM
- QGIS vs ArcGIS for Geological and Terrain Workflows
- GIS and spatial analysis