Under the hooddev
How the app builds a poster from the GTFS feed and prints it as a CMYK PDF. The steps are in How to, the result in the Poster gallery.
Data and query
Departures come from the seasonal SNCB feed, agency nmbssncb-seasonal. A station picked in the search is re-stamped onto that feed, so its id reads gs:nmbssncb-seasonal:S88xxxxx.
| Step | What happens |
|---|---|
| Query | One SQL query per schedule (weekdays or weekend) and scope (national or international). Each row carries the time, route pictogram, train number, headsign, the stops after this one with their times, the platform and the GTFS service id. |
| Circulation sentence | The service id is turned into the ! line of the row from the dates the service actually runs, e.g. circule/rijdt 29.06. au/tot 28.08. Lu/Ma-Ve/Vr. Services that run on public holidays only are dropped: they belong to the Sunday timetable. |
| Cache | Results are cached in memory for 15 minutes per station, schedule, language and scope. |
| Valid from | The footer's Depuis le / Vanaf date is the feed's start date (feed_info). Without one, the production date is used. |
The uploaded file is kept in the tab's sessionStorage, keyed by station. Reloading the preview keeps it; a new tab or a closed browser loses it, and the file has to be uploaded again.
International rules
What counts as international
A route whose short name is one of the high-speed and international brands, or a TRN route whose long name ends with a foreign country code such as (FR) or (DE): the SNCB feed often publishes Eurostar, ICE and TGV trips that way.
International trains on national posters
Seven hubs also list international trains on their national poster: Liège-Guillemins, Mons, Antwerpen-Centraal, Antwerpen-Noorderkempen, Brussel-Noord, Brussel-Centraal and Brussel-Zuid. There, the rows of SNCB's list print in red: Nightjet, Ouigo, ICE, Eurostar, EuroCity, EuroCity Direct, TGV inOui and European Sleeper. Cross-border IC and S trains keep the national black, and so does INT: European Sleeper runs under that code in the feed, but SNCB uses it for other trains too.
Specific ticket
Nightjet, Ouigo, ICE and European Sleeper trains get the mention accessible with a specific ticket on international posters and at the hubs above, whatever their stops: the route code alone decides. It is always printed in FR / NL / DE / EN, whatever the poster's language.
National vs international poster
| National | International | |
|---|---|---|
| Trains | IC, S, L, P… (SNCB products) | EC, ECD, European Sleeper, Eurostar, ICE, inOui / TGV, Nightjet, Ouigo… |
| Formats | A1 or A3 | A1 only (the SNCB international template) |
| Header | Annual timetable title, disclaimer and QR code, in the page language | Day range in four languages, no disclaimer |
| Logo | NMBS mark in blue, plus the S logo at the S-stations | NMBS mark in black, never the S logo |
| Colour | International trains in red (#cd1719) | All black |
Excel spec
First sheet: one row per departure
Columns: departure-time, trip_short_name, route_icon, headsign, stops_after_this_stop, service_days, platform, comment. Only departure-time is required; rows without it are skipped.
| Column | Notes |
|---|---|
trip_short_name | Train number. Several numbers separated by commas print one per line. |
route_icon | Product code that picks the pictogram (IC, S1, EUR…). An unknown code prints as text. |
stops_after_this_stop | Comma-separated Name H:MM tokens. Keep the commas so the file can be re-uploaded. |
service_days | The circulation sentence printed after the !. Empty means the train runs every day of the schedule. |
comment | Free text printed under the row. |
Metadata sheet
Two columns, Key and Value. Files from Export Excel already have it; without station_id the upload is refused.
| Key | Value |
|---|---|
station_id | Required. The station the poster is for; it also decides the S-station logo. |
name | Station name printed in the footer. |
type | weekdays or weekends |
scope | national or international |
lang | fr, fr-nl, nl-fr, nl or de |
valid_from | Start of validity: YYYY-MM-DD, DD.MM.YYYY or DD/MM/YYYY. |
schoolHolidays | Legend periods: the exported JSON, or YYYY-MM-DD/YYYY-MM-DD, … |
holidays | Legend public holidays: the exported JSON, or YYYY-MM-DD, … |
Invalid holiday values are ignored and the defaults bundled with the app apply.
Layout rules
Zones
| Zone | Content |
|---|---|
| Header | Title (weekdays or weekend), disclaimer with the sncb.be / nmbs.be QR code, NMBS logo, plus the S logo at S-stations. Bilingual pages repeat title and disclaimer in the second language. |
| Body | Three columns of hour blocks. A row: time, pictogram, train number, airport plane if it serves Brussels Airport; destination and the stops after this one (hubs in bold, Brussels Airport in blue); circulation sentence and comment; platform. |
| Legend | School holidays, public holidays (with the Sunday note) and symbols: ✈ Brussels Airport supplement, △ tourist train, Côte-Express. |
| Footer | Station • valid from, production date in the centre, page n / N. |
Header, legend and circulation sentences shrink to fit their frames: the sentence of a row keeps to four lines.
Sheets
| Format | Paper | Margins (top, right, bottom, left) | Columns | Scale % |
|---|---|---|---|---|
| A1 | 620 × 1000 mm (SNCB custom) | 14, 14, 7, 14 mm | 3 × 190 mm, 11 mm gaps | 67 to 100 |
| A3 | 297 × 420 mm | 7.8, 12.5, 7, 12.5 mm | 3 × 88 mm, 4 mm gaps | 100 only |
Overflowing columns continue on a second sheet with its own header, legend and footer. Max pages scales the body only, until it fits that many sheets; header, legend and footer always print at true size.
The preview lays the sheet out at its true printed size and only zooms it visually, so the columns break at the same rows on screen as in the PDF.
Languages
The language drives titles, disclaimer, platform label, circulation sentences and legend labels. Stop names come from the feed as they are.
PDF rendering
- The preview posts its departures to
/yellow-pages/:stationId/pdf, so the PDF prints the uploaded file, not a fresh query. - The server builds the poster HTML with the same stylesheet and opens it in headless Chrome (Puppeteer).
- The column flow is split into one sheet per page, then printed at the paper size of the format.
- Ghostscript converts the RGB PDF to CMYK prepress: 300 dpi rasters, fonts embedded. Without
gs, production fails and development returns the RGB file with a warning.