Short answer

Geological faults and contacts are line features, but they carry meaning that plain lines cannot: a thrust has teeth on the hanging wall, a normal fault has ticks on the downthrown block, and the certainty of a trace (observed, inferred, concealed) must be legible without reading the attribute table. In QGIS you build this with rule-based symbology driven by attribute fields, Marker Line sub-symbols for teeth and ticks, and data-defined overrides to switch dash patterns by certainty. Done well, the line drawing itself communicates structure type and confidence, and the whole thing is reusable via a saved QML style.

Set up the attribute schema first

Symbology is only as good as the fields it reads. Before styling, make sure the layer has explicit columns, for example:

  • feature_typecontact, fault, thrust, normal_fault, shear_zone
  • certaintyobserved, inferred, concealed
  • dip_dir or a boolean flip to control which side teeth/ticks sit on

Encoding meaning in attributes rather than in many hand-drawn layers is what makes the map reproducible and lets one rule set drive every feature. It also follows the logic of standards such as the USGS FGDC Digital Cartographic Standard for Geologic Map Symbolization, where line type and ornament map directly to feature meaning and confidence.

Contacts and simple faults

Start with the base traces using rule-based rendering (Layer Properties > Symbology > Rule-based). Add a rule per feature_type:

  • Contact: a single Simple Line, ~0.3–0.4 mm, solid, neutral dark grey or black. Contacts are the quiet baseline of a geological map, so keep them visually subordinate.
  • Fault (undifferentiated): a Simple Line at ~0.6–0.8 mm in a strong colour (red is conventional in many schemes), heavier than contacts so structure reads at a glance.

For certainty, do not make three separate rules if you can avoid it. Instead put a data-defined override on the line's dash pattern. With the Simple Line selected, open the dropdown next to "Dash pattern" (or use "Use custom dash pattern" with an override) and feed an expression keyed on certainty:

CASE
  WHEN "certainty" = 'observed'  THEN ''               -- solid
  WHEN "certainty" = 'inferred'  THEN '4;2'            -- 4mm dash, 2mm gap
  WHEN "certainty" = 'concealed' THEN '1;2'            -- dotted
END

Now one rule renders all three confidence levels, and the line's appearance encodes uncertainty directly.

Thrust faults: teeth with a Marker Line

A thrust trace needs filled triangles on the hanging-wall side. In QGIS, a line symbol can hold several stacked sub-symbols. For a thrust rule, build two layers in the symbol:

  1. A Simple Line for the fault trace (heavy, solid).
  2. A Marker Line for the teeth. Set placement to "on every interval" (older builds call it "with interval") at, say, 6 mm spacing. The Marker Line's sub-marker is a filled triangle (Simple Marker, triangle shape) sized ~3 mm.

The key controls:

  • Enable "Rotate marker to follow line direction" so triangles stay perpendicular as the line bends.
  • Offset the marker (Marker Line "Offset along line" / marker "Offset") so the base sits on the trace and the point faces the hanging wall.
  • To flip teeth onto the correct side, drive the marker's rotation or the line offset with a data-defined override on your flip/dip_dir field, e.g. offset if("flip", 1.5, -1.5).

Digitise the line consistently in the dip direction so "follow line direction" puts teeth on the right side without per-feature edits. Where direction is inconsistent, the flip override is your escape hatch.

Normal faults and other ornaments

A normal (extensional) fault uses a short tick (ball-and-bar or a perpendicular bar) on the downthrown side. Same recipe: Simple Line plus a Marker Line, but the marker is a short line/bar or a small filled circle ("ball") with an offset that pushes it to one side. Set the interval wider (e.g. 10–15 mm) so ticks read as periodic annotation, not a continuous fringe. Strike-slip faults are typically left as bold solid lines with paired half-arrows added as a separate marker line if needed.

Worked workflow

  1. Confirm the project CRS is a projected system (e.g. UTM). Marker Line intervals and offsets are in map/layout units; in a geographic CRS like EPSG:4326 the spacing will be meaningless and teeth will distort.
  2. Build the attribute schema (feature_type, certainty, flip).
  3. Create the rule-based renderer: contact, fault, thrust, normal_fault rules.
  4. For thrust/normal, stack Simple Line + Marker Line; enable "rotate to follow line direction"; set interval and offset.
  5. Add the dash-pattern data-defined override for certainty.
  6. Preview at the target print scale (e.g. 1:25 000). Symbol sizes that look right on screen are often far too large on paper — set sizes in millimetres and check in the layout.
  7. Save the result: Style > Save Style… as a .qml (QGIS-native, supports all of the above) and optionally .sld for interoperability. Store reusable triangle/tick markers in the Style Manager so other layers and projects pull identical symbols.

Common pitfalls and why they happen

  • Teeth on the wrong side. Cause: lines digitised in mixed directions while "rotate to follow line direction" assumes a consistent sense. Fix by standardising digitising direction or using a flip override.
  • Ornaments scale wrong between screen and print. Cause: symbol sizes set in pixels rather than millimetres. Always size in mm so the layout matches the print scale.
  • Certainty only in the legend, not the line. Cause: three colour rules instead of a dash override. If a reviewer must consult the table to know a fault is inferred, the symbology has failed its job.
  • SLD round-trip drops marker lines. Cause: SLD support for interval-placed markers is limited. Keep QML as the source of truth and treat SLD as a lossy export.
  • Interval distortion in lat/long. Cause: styling a layer in a geographic CRS where map units are degrees. Reproject or set the project to a projected CRS first.

QA and validation

Before export, do a structured visual pass: zoom to several fault segments and confirm teeth/ticks sit on the geologically correct block; check that inferred and concealed traces render with their intended dash patterns; verify the legend entries match the rule labels you set (rename rules to human-readable text so the auto-legend is publishable). Render the layout at full target scale and inspect the densest fault cluster — that is where overlapping ornaments first become unreadable and where interval/size need tuning.

Bathyl perspective

For Bathyl maps the test is whether someone can read fault type and confidence from the lines alone, then trace any symbol back to an attribute and a source. We keep the meaning in the data and the appearance in a versioned QML, so the same fault renders identically across a static report figure, a print plate, and a web export.

Related reading

Sources