# trainmap
`trainmap` is a LaTeX + TikZ package for drawing **schematic railway and
transit maps** declaratively, in the style of the FGC / Rodalies line
diagrams. Instead of positioning every node by hand, you describe a line or a
whole network with a small, readable set of commands — `\addstation`,
`\addterminal`, `\forkup`, `\branch`, and so on — and the package takes care of
the geometry.
## Table of contents
- [Features](#features)
- [The two map styles (`mode`)](#the-two-map-styles-mode)
- [Documentation](#documentation)
- [Installation](#installation)
- [Using it in your editor (TeXstudio)](#using-it-in-your-editor-texstudio)
- [Quick start](#quick-start)
- [Long lines: the serpentine layout](#long-lines-the-serpentine-layout)
- [Command reference](#command-reference)
- [Configuration keys](#configuration-keys)
- [Predefined colours](#predefined-colours)
- [How the engine works](#how-the-engine-works)
- [Repository layout](#repository-layout)
- [License](#license)
## Features
- **Two map styles.** An **FGC** style that shows every line at once (with a
line badge on each terminal), and a **Rodalies** style for a single line
whose code is shown on a square plate at each end. Both still show
interchanges. Selected with the `mode` key.
- **Key–value configuration** through `pgfkeys`, all under the `/trainmap/`
namespace: line colours, stroke widths, label angles and station sizes. Any
key can be set once for the whole map or overridden on an individual station.
- **Common trunk of parallel tracks.** Several lines can run together as a
stacked bundle and then branch off or join at different heights.
- **Multi-line station names.** Break a long name across lines with `\\`.
- **Standard stations** drawn as a perpendicular tick crossing the whole
bundle, with a rotated label (45° by default, configurable).
- **Terminal / head stations** drawn as a larger filled disc, a **bold** name
and a coloured **badge** carrying the line code (for example `S1`, `L6`).
- **Interchange stations** drawn as a rounded capsule that groups all the
parallel tracks, for stops shared by several services.
- **Branches and forks** with smoothly curved diagonal connectors:
`\forkup`, `\forkdown`, `\joinline`, `\branch`, `\track`.
- **Automatic serpentine (snake) wrapping** for lines that are too long to fit
on one row: set a maximum width and the line folds onto stacked rows joined
by nested U-turns.
- **Two ways to load it:** as a LaTeX package (`\usepackage{trainmap}`) or as a
TikZ library (`\usetikzlibrary{trainmap}`).
## The two map styles (`mode`)
The `mode` key selects how the diagram is presented.
### `mode=fgc` (default) — several lines at once
Every line of the trunk is drawn. Each `\addterminal` shows a filled disc, the
bold station name, and the line badge **below the disc**; interchanges are drawn
as capsules. Use this when several services share the diagram (the classic FGC
schematic look).
Full source: [`examples/fgc_style_map.tex`](examples/fgc_style_map.tex).
### `mode=rodalies` — a single line
Only one line is drawn. Its code is shown as a **square plate at each end** of
the whole route, with the line starting right next to the plate — so terminals
no longer carry an individual badge. Station names appear as usual and shared
stops are still marked as correspondences with `\addinterchange`. The code for
the end plates is taken from `line code`, or automatically from the first
`\addterminal` if you do not set it.
```latex
\begin{trainmap}[mode=rodalies, badge color=tmR4]
\settrunk{0/tmR4}
\addterminal{Sant Vicenç de Calders}{R4} % opening [R4] plate
\addstation{El Vendrell}
% ...
\addinterchange{Barcelona Sants} % a correspondence
% ...
\addterminal{Manresa}{R4} % closing [R4] plate
\end{trainmap}
```
Because Rodalies lines tend to be long, this mode is normally combined with the
[serpentine layout](#long-lines-the-serpentine-layout). See
[`examples/rodalies_r4.tex`](examples/rodalies_r4.tex) for the full R4 line.
## Documentation
The full user manual is included as **[`trainmap.pdf`](trainmap.pdf)** (source:
[`trainmap-doc.tex`](trainmap-doc.tex)). It covers installation, the complete
command and key reference, and worked examples with rendered diagrams.
## Installation
The package is made of two files that must always travel together:
| File | Role |
|------|------|
| `trainmap.sty` | LaTeX wrapper — enables `\usepackage{trainmap}`. |
| `tikzlibrarytrainmap.code.tex` | The implementation — loaded by `\usetikzlibrary{trainmap}`. |
### Option A — from CTAN (once published)
`trainmap` is prepared for distribution on [CTAN](https://ctan.org). Once it is
available there, install it with your TeX distribution's package manager:
```bash
tlmgr install trainmap # TeX Live
mpm --install=trainmap # MiKTeX (or use the MiKTeX Console)
```
Most distributions also ship it in their package repositories, so it may already
be installed. After that, `\usepackage{trainmap}` just works.
### Option B — next to your document (quickest)
Copy **both** files into the same folder as your `.tex` file and compile with
`pdflatex`. LaTeX always searches the document's own folder first, so nothing
else is required.
### Option C — install into your personal `texmf` tree (available everywhere)
```bash
# 1. Find your personal texmf root
kpsewhich -var-value TEXMFHOME
# Linux/macOS: usually ~/texmf
# Windows/MiKTeX: usually C:\Users\\AppData\Roaming\MiKTeX
# 2. Create the destination folder and copy both files
mkdir -p "$(kpsewhich -var-value TEXMFHOME)/tex/latex/trainmap"
cp trainmap.sty tikzlibrarytrainmap.code.tex \
"$(kpsewhich -var-value TEXMFHOME)/tex/latex/trainmap/"
# 3. Refresh the filename database
mktexlsr # TeX Live
# MiKTeX: open the MiKTeX Console -> Tasks -> Refresh file name database
# (or run: initexmf --update-fndb)
```
On Windows you can copy the files manually instead of using the shell; the
destination folder is
`C:\Users\\AppData\Roaming\MiKTeX\tex\latex\trainmap\`.
After a database refresh, `\usepackage{trainmap}` works from any document.
## Using it in your editor (TeXstudio)
**Compiling.** Build with **pdfLaTeX**. In TeXstudio press **F5** (build and
view) or **F6** (build only). If the default compiler is not pdfLaTeX, set it
under *Options → Configure TeXstudio → Build → Default Compiler → PdfLaTeX*.
**Removing the orange highlighting.** By default TeXstudio underlines any
command it does not know — so `\addstation`, `\addterminal`, etc. appear in a
warning colour even though the document compiles perfectly. This is only a
cosmetic check in the editor, not an error. To make TeXstudio recognise the
`trainmap` commands, install the bundled completion file `trainmap.cwl`:
1. In TeXstudio open *Help → Show Manual* is not needed — instead go to
*Options → Configure TeXstudio → Completion* and note the user completion
directory, or simply copy `trainmap.cwl` into TeXstudio's completion folder:
- Windows: `C:\Users\\AppData\Roaming\texstudio\completion\user\`
- Linux: `~/.config/texstudio/completion/user/`
- macOS: `~/Library/Application Support/texstudio/completion/user/`
2. Restart TeXstudio.
Once the file is in place and your document contains `\usepackage{trainmap}`,
the commands are treated as known and the orange highlighting disappears; you
also get autocompletion for the commands and their keys.
## Quick start
```latex
\documentclass[border=10pt]{standalone}
\usepackage{trainmap}
\begin{document}
% mode=rodalies -> a square [S1] plate at each end of the line
\begin{trainmap}[mode=rodalies, station step=2.0, badge color=tmS1]
\settrunk{0/tmS1} % a single orange track (S1)
\addterminal{Pl. Espanya}{S1} % first end (sets the line code)
\addstation{Magòria}
\addstation{Ildefons Cerdà}
\addinterchange{Sant Josep} % a correspondence / transfer
\addstation[label position=below]{Gornal}
\addstation{L'Hospitalet\\de Llobregat} % \\ = manual line break
\addterminal{Cornellà}{S1} % last end (closing plate)
\end{trainmap}
\end{document}
```
Full source: [`examples/simple_line.tex`](examples/simple_line.tex).
## Long lines: the serpentine layout
When a line has too many stations to fit on a single row, enable **serpentine
wrapping**: set `max width` (the x, in cm, at which the line should wrap) and
optionally `row sep` (the vertical distance between rows). The cursor turns
automatically at the edge, drawing a nested U-turn, and you keep calling
`\addstation` as usual — no manual bookkeeping.
```latex
\begin{trainmap}[mode=rodalies, station step=1.6, max width=20, row sep=3.8,
badge color=tmR4]
\settrunk{0/tmR4}
\addterminal{Sant Vicenç de Calders}{R4}
\addstation{El Vendrell}
% ... many more stations ...
\addterminal{Manresa}{R4}
\end{trainmap}
```
Full source: [`examples/rodalies_r4.tex`](examples/rodalies_r4.tex) — the complete
R4 line (38 stations) in Rodalies style, folded over three rows.
For a bundle of several parallel tracks the U-turns nest concentrically, so the
lines stay parallel and never cross. Serpentine wrapping is most useful for one
or a few lines; give `row sep` enough room that the rotated labels of one row do
not reach the row above.
## Command reference
### The `trainmap` environment
```latex
\begin{trainmap}[] ... \end{trainmap}
```
Wraps a `tikzpicture`, fixes the drawing unit to 1 cm, and resets the internal
cursor and trunk state. Any `/trainmap/` key may be given in `` as a map
wide default. You can still add ordinary TikZ code (`\node`, `\draw`,
`\coordinate`, …) inside the environment, which is how the examples place
off-trunk terminals.
### Defining the trunk
```latex
\settrunk{/, /, ...}
```
Declares the parallel tracks that currently make up the common trunk. Each
entry is a vertical offset in centimetres and the colour of that track. The
tracks are drawn automatically between consecutive stations.
`\settrunk` may be **called again at any point** to change the active bundle —
for example after a line branches away it should be dropped from the trunk, or
a new line can be added. In the FGC example the trunk starts as four tracks and
is reduced to three and then two as L7 and L6 leave.
### Stations
| Command | What it draws |
|---------|---------------|
| `\addstation[]{}` | A **standard station**: a perpendicular tick crossing every track of the trunk, plus a rotated label. |
| `\addterminal[]{}{}` | A **terminal / head station**: a filled disc in the badge colour and the name in bold above. In `fgc` mode a badge with `` is drawn below the disc; in `rodalies` mode there is no per-terminal badge (the code appears on the end plates instead, and the first terminal's `` becomes the line code if none was set). |
| `\addinterchange[]{}` | An **interchange station**: a rounded capsule grouping all trunk tracks, with a coloured dot per line, and a bold label. |
Any `` may contain `\\` to break it across several lines.
Every station call **advances the cursor** by `station step` centimetres (in
the current direction) before drawing, and publishes the reached position in the
macros `\tmlastx` and `\tmlasty`. This is how you anchor a branch to a station:
capture the value right after the call.
```latex
\addstation[name=gracia]{Gràcia}
\edef\gx{\tmlastx} % \gx now holds Gràcia's x coordinate
```
Passing `name=` additionally creates three reusable coordinates:
`()` at the top track, `(-mid)` at the band centre, and `(-bot)` at
the bottom track.
Useful per-station keys: `label position=below` (label under the track),
`station step=` (a custom gap before this one), `badge color=`
(for terminals and interchanges).
### Tracks, branches and forks
The `` and `` arguments are passed **with their parentheses**, so they
accept explicit coordinates, named nodes, or `calc` expressions
interchangeably:
```latex
\forkup{(\gx,0.36)}{(avtib)}{tmL7} % coordinate -> named node
\track{(sarria)}{($(sarria)+(2,0)$)}{tmL6} % named node -> calc expression
```
| Command | What it draws |
|---------|---------------|
| `\track[]{}{}{}` | A straight track segment. |
| `\branch[]{}{}{}` | A gentle, near-horizontal curved branch. |
| `\forkup[]{}{}{}` | A branch that peels **upwards** (alias: `\forkline`). |
| `\forkdown[]{}{}{}` | A branch that peels **downwards**. |
| `\joinline[]{}{}{}` | A branch **merging back** into the trunk from below. |
`` is an optional list of raw TikZ options appended to the `\draw`
(for example `[looseness=1.2]` or `[dashed]`).
## Configuration keys
All keys live under `/trainmap/`. Set them on the environment for a map-wide
default, or in a command's `[ ... ]` to override a single element.
| Key | Default | Meaning |
|-----|---------|---------|
| `mode` | `fgc` | Map style: `fgc` (all lines, badges on terminals) or `rodalies` (single line, code on end plates). |
| `line code` | (empty) | Code shown on the end plates in `rodalies` mode. If empty, it is taken from the first `\addterminal`. |
| `line color` | `black` | Fallback colour of a single track. |
| `track width` | `2pt` | Stroke width of a track. |
| `station color` | `black` | Colour of the station tick mark. |
| `station width` | `1.8pt` | Stroke width of the tick mark. |
| `terminal size` | `8pt` | Diameter of the terminal disc. |
| `terminal border` | `1.4pt` | Border width of the terminal disc. |
| `badge color` | `black` | Background colour of the line badge. |
| `capsule width` | `12pt` | Outer width of the interchange capsule. |
| `capsule inner` | `8pt` | Inner (white) width of the capsule. |
| `capsule color` | `black` | Border colour of the capsule. |
| `station step` | `1.6` | Horizontal distance between stations (cm). |
| `track sep` | `0.16` | Suggested vertical gap between parallel tracks (cm). |
| `max width` | `0` | Wrap to a new row once the cursor passes this x (cm). `0` disables wrapping. |
| `row sep` | `2.6` | Vertical distance between wrapped rows (cm). |
| `label angle` | `45` | Rotation of the station labels (degrees). |
| `label position` | `above` | `above` or `below` — label over or under the track. |
## Predefined colours
The package defines the official line colours of **Rodalies de Catalunya** and
**FGC**; you can also use any `xcolor` colour such as `red` or `blue!60!black`.
**Rodalies de Catalunya**
| Name | Line | Name | Line | Name | Line |
|------|------|------|------|------|------|
| `tmR1` | R1 | `tmR8` | R8 | `tmR16` | R16 |
| `tmR2` | R2 | `tmR11` | R11 | `tmR17` | R17 |
| `tmR2N` | R2 Nord | `tmR12` | R12 | `tmRL1` | RL1 |
| `tmR2S` | R2 Sud | `tmR13` | R13 | `tmRL2` | RL2 |
| `tmR3` | R3 | `tmR14` | R14 | `tmRG1` | RG1 |
| `tmR4` | R4 | `tmR15` | R15 | `tmRT1` | RT1 |
| `tmR7` | R7 | | | `tmRT2` | RT2 |
**FGC** — Barcelona-Vallès: `tmL6`, `tmL7`, `tmL12`, `tmS1`, `tmS2`.
Llobregat-Anoia: `tmL8`, `tmR5`, `tmR50`, `tmR6`, `tmR60`.
### Correspondences
Any station accepts a `corr` key listing the connecting lines as
`CODE/colour` pairs; each becomes a small coloured chip stacked beneath the
station (as on the R7 map at the top of this page):
```latex
\addstation[corr={R3/tmR3, R4/tmR4}]{Montcada Bifurcació}
```
## How the engine works
`trainmap` keeps a small amount of state — a horizontal **cursor**, a travelling
**direction**, a **row** index and the list of trunk tracks. Each `\add...`
call advances the cursor, draws the trunk segment(s) leading up to it, and then
draws the station symbol and label. Coordinates are placed with `pgfmath`, so
the vertical positions of the tracks, the tick extents and the label anchors are
all derived from the keys rather than hard-coded.
For the serpentine layout the absolute y of a track stored as `l` is
```
y(l) = (rowbase + center) + sign * (l - center)
```
On the first row this reduces to `y = l`, so ordinary maps keep their absolute
coordinates (hand-placed branch endpoints remain valid). On a wrapped row the
`sign` flips, which mirrors the track order — exactly what a parallel U-turn
needs so the connecting half-loops nest concentrically instead of crossing.
## Repository layout
```
trainmap/
├── trainmap.sty # LaTeX wrapper
├── tikzlibrarytrainmap.code.tex # Core TikZ library (the implementation)
├── trainmap.cwl # TeXstudio/TeXworks completion file
├── trainmap-doc.tex # User manual (source)
├── trainmap.pdf # User manual (compiled)
├── examples/
│ ├── simple_line.tex # Rodalies style: one line, end plates
│ ├── rodalies_r7.tex # Rodalies style + correspondences (the R7 line)
│ ├── fgc_style_map.tex # FGC style: common trunk, branches, interchange
│ └── rodalies_r4.tex # Rodalies style + serpentine: the full R4 line
├── docs/
│ ├── preview_simple_line.png
│ ├── preview_rodalies_r7.png
│ ├── preview_fgc_style_map.png
│ └── preview_rodalies_r4.png
├── README.md
├── LICENSE # LaTeX Project Public License v1.3c
└── .gitignore
```
To build every example locally:
```bash
cd examples
pdflatex simple_line.tex
pdflatex rodalies_r7.tex
pdflatex fgc_style_map.tex
pdflatex rodalies_r4.tex
```
## License
Released under the **LaTeX Project Public License (LPPL), version 1.3c** or (at
your option) any later version — see [`LICENSE`](LICENSE). This is the standard
license for LaTeX packages and is the license under which `trainmap` is
distributed on CTAN. The package has LPPL maintenance status *maintained*; the
Current Maintainer is **Pol Sardà Palla**.