Skip to content

Repository files navigation

swiss-netex

CLI for Swiss national NeTEx timetable packages (opentransportdata.swiss): download, inspect, and filter by operator/line.

This is not a full NeTEx model library. It prepares smaller operator-scoped packages from the national SKI NeTEx feed.

Requirements

  • Ruby ≥ 4.0

Installation

gem install swiss-netex

Or from source:

git clone https://github.com/gatemedia/swiss-netex.git
cd swiss-netex
mise run bootstrap   # or: bundle install
bundle exec exe/swiss-netex version

Quick start

Download (or reuse cache) and filter one operator in a single step:

swiss-netex extract -y 2026 -O 151 -o tl.zip

Stdout prints the output path. Progress goes to stderr.

swiss-netex help
swiss-netex help extract   # full flags for any command
swiss-netex version

Commands

download

Resolve and stream the latest national NeTEx zip. Resource downloads are public (no API token on the happy path). Prints the file path on stdout.

swiss-netex download
swiss-netex download --year 2026
swiss-netex download -y 2026 -o ./cache
swiss-netex download --dataset timetablenetex_2026 --force
swiss-netex download --cache-dir ./cache
  • Default dataset: highest published timetablenetex_YYYY on the public catalog
  • Override with --year YYYY or --dataset (slug or full page URL)
  • Default cache: ~/.cache/swiss-netex
  • Cache key is the remote filename (export timestamp embedded). A positive-size local file with that name that also opens as a zip is a cache hit — OTD signed URLs reject HEAD, so remote size is not re-checked. Use --force to refresh.

operators

List operators from the RESOURCE frame. Scans RESOURCE only — not TIMETABLE shards — so listing stays cheap even on a full national package. Omit --from to download (or reuse cache) first — same flags as download / extract.

swiss-netex operators -y 2026
swiss-netex operators --from ./cache/PROD_NETEX_….zip
swiss-netex operators -y 2026 | cut -f1,4

Columns: go, operator_id, sboid, short_name, name.

lines

List lines from the SERVICE frame (SERVICE only — not TIMETABLE). Optional --operator / --line narrow the list (same alias rules as filter). Omit --from to download (or reuse cache) first.

swiss-netex lines -y 2026
swiss-netex lines -y 2026 -O 151
swiss-netex lines --from ./cache/PROD_NETEX_….zip -O 151 -l m1

Columns: line_id, public_code, slnid, operator_ref, name.

filter

Filter a local package (zip or extracted directory) by operator and optional line. Writes a smaller zip with transitive frame contents and a README.txt.

swiss-netex filter --from ./cache/PROD_NETEX_….zip -O 151 -o tl.zip
swiss-netex filter --from ./cache/PROD_NETEX_….zip \
  -O 151 -O 55 -l 1 -o filtered.zip --verbose
swiss-netex filter --from ./package-dir/ -O 151 -o ./out/
  • --operator / -O is repeatable (union). Aliases: GO number, ch:1:Operator:*, SBOID, ShortName
  • Optional --line / -l (repeatable): PublicCode, id, SLNID, ShortName, Name
  • Keeps matching SERVICE lines and TIMETABLE ServiceJourneys, then the transitive closure in RESOURCE / SERVICE / SITE / SERVICECALENDAR / COMMON
  • Empty timetable shards are dropped (original national shard indices kept)
  • Without --allow-empty, exits non-zero when no lines or journeys match
  • --verbose prints per-shard and per-frame keep/drop counters on stderr
  • Output zip includes README.txt (filters, counts, Swiss-profile notes)

extract

Download (when --from is omitted) then filter. The common one-shot workflow.

swiss-netex extract -O 151 -o tl.zip
swiss-netex extract -y 2026 -O 151 -o tl.zip
swiss-netex extract --from ./cache/PROD_NETEX_….zip -O 151 -o tl.zip

Same filter flags as filter, plus download flags when no --from is given. operators and lines share the same package-source rules: omit --from to download/cache; with --from, --dataset / --year / --force / --cache-dir are rejected.

Output and conventions

Stream Content
stdout Machine-usable result (file path, or TSV/table for discovery)
stderr Progress, status, verbose counters, errors

Discovery commands (operators, lines): raw TSV when piped / non-TTY; aligned columns on an interactive terminal (same cells, two-space padding).

-o path rules (filter / extract):

-o value Result
tl.zip as-is
tl (no extension) tl.zip
tl.tar as-is (explicit non-zip extension left alone)
existing directory or trailing / dir/<source>_op-….zip

Exit codes: 0 ok, 1 runtime/domain error, 64 usage/parse error.

Memory and speed

National packages are large (~hundreds of MB compressed, tens of GB uncompressed). filter / extract stream frames and never load the full national set into a DOM:

  • short-lived worker processes on platforms with fork
  • parent byte pre-scan skips empty TIMETABLE shards
  • process pool for matching shards and support frames
  • non-DOM COMMON scanner
Env Effect
SWISS_NETEX_TT_WORKERS=N Pool size (default: CPU count; 1 = serial)
SWISS_NETEX_TT_BATCH Shards per child (default: 1)
SWISS_NETEX_NO_FORK=1 Force in-process mode

Prefer a machine with ≥4 GB free RAM. Peak system RSS scales roughly with worker count × largest kept shard.

Swiss profile notes (filtered packages)

These are normal for SKI national exports and operator-scoped subsets — not filter bugs. The same points are written into each output README.txt.

Calendar / DayType

Journeys typically reference AvailabilityConditions that carry FromDate / ToDate / ValidDayBits. Many selections collect zero DayType / DayTypeRef entities. Importers should read validity from the kept ACs in SERVICECALENDAR, not assume a DayType model.

COMMON connections

JourneyMeeting and InterchangeRule are kept only when every tracked journey, stop, and line ref still resolves inside the package (no dangling partner LineRef). Cross-operator interchanges drop unless you include both operators (e.g. -O 151 -O 55). An empty connection section is valid.

TIMETABLE shard names

Only shards with kept journeys are written. Filenames keep the national TIMETABLE_{i}_{n}_… indices, so numbering can have gaps (e.g. 45–69 plus 113). Consumers should glob timetable members, not assume 1…N contiguous.

Data license

The tool is MIT. Timetable data remains under OpenTransportData terms. Do not commit national dumps into this repository.

Development

This project uses mise for the local toolchain.

mise trust          # once, if prompted
mise install        # Ruby from mise.toml / .ruby-version
mise run bootstrap  # bundle install
mise run test       # bundle exec rake test
mise run lint       # bundle exec rake rubocop
mise run ci         # tests + rubocop
mise run swiss-netex help
mise run swiss-netex version
mise run build      # gem package into pkg/

Without mise:

bundle install
bundle exec rake        # test + rubocop
bundle exec rake test
bundle exec rake rubocop

Release

  1. Bump SwissNetex::VERSION and add a CHANGELOG.md section for it

  2. Publish from a clean tree:

    VERSION=X.Y.Z bundle exec rake publish

    That dates the CHANGELOG section, runs tests + RuboCop + gem build, commits the dated changelog if needed, tags vX.Y.Z, and pushes the branch and tag. Do not use Bundler's rake release (that gem-pushes with an API key).

  3. GitHub Actions then tests the tag, creates a GitHub Release from that CHANGELOG section, and publishes to RubyGems via trusted publishing.

License

MIT © GateMedia — see LICENSE.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages