Doc

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.

StepWhat happens
QueryOne 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 sentenceThe 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.
CacheResults are cached in memory for 15 minutes per station, schedule, language and scope.
Valid fromThe 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

NationalInternational
TrainsIC, S, L, P… (SNCB products)EC, ECD, European Sleeper, Eurostar, ICE, inOui / TGV, Nightjet, Ouigo…
FormatsA1 or A3A1 only (the SNCB international template)
HeaderAnnual timetable title, disclaimer and QR code, in the page languageDay range in four languages, no disclaimer
LogoNMBS mark in blue, plus the S logo at the S-stationsNMBS mark in black, never the S logo
ColourInternational 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.

ColumnNotes
trip_short_nameTrain number. Several numbers separated by commas print one per line.
route_iconProduct code that picks the pictogram (IC, S1, EUR…). An unknown code prints as text.
stops_after_this_stopComma-separated Name H:MM tokens. Keep the commas so the file can be re-uploaded.
service_daysThe circulation sentence printed after the !. Empty means the train runs every day of the schedule.
commentFree 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.

KeyValue
station_idRequired. The station the poster is for; it also decides the S-station logo.
nameStation name printed in the footer.
typeweekdays or weekends
scopenational or international
langfr, fr-nl, nl-fr, nl or de
valid_fromStart of validity: YYYY-MM-DD, DD.MM.YYYY or DD/MM/YYYY.
schoolHolidaysLegend periods: the exported JSON, or YYYY-MM-DD/YYYY-MM-DD, …
holidaysLegend 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

ZoneContent
HeaderTitle (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.
BodyThree 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.
LegendSchool holidays, public holidays (with the Sunday note) and symbols: ✈ Brussels Airport supplement, △ tourist train, Côte-Express.
FooterStation • 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

FormatPaperMargins (top, right, bottom, left)ColumnsScale %
A1620 × 1000 mm (SNCB custom)14, 14, 7, 14 mm3 × 190 mm, 11 mm gaps67 to 100
A3297 × 420 mm7.8, 12.5, 7, 12.5 mm3 × 88 mm, 4 mm gaps100 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

  1. The preview posts its departures to /yellow-pages/:stationId/pdf, so the PDF prints the uploaded file, not a fresh query.
  2. The server builds the poster HTML with the same stylesheet and opens it in headless Chrome (Puppeteer).
  3. The column flow is split into one sheet per page, then printed at the paper size of the format.
  4. 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.