Parser plakatów zestawień pociągów PKP Intercity (PDF)
  • JavaScript 64.6%
  • CSS 23.8%
  • HTML 9.1%
  • Python 2.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-06 10:29:41 +02:00
data Add PKP Intercity poster parser 2026-09-06 10:26:28 +02:00
docs/superpowers Add PKP Intercity poster parser 2026-09-06 10:26:28 +02:00
src/pyicparser Add PKP Intercity poster parser 2026-09-06 10:26:28 +02:00
tests Add PKP Intercity poster parser 2026-09-06 10:26:28 +02:00
Zestawienia pociągów - PKP Intercity_files Add PKP Intercity poster parser 2026-09-06 10:26:28 +02:00
.gitignore Add PKP Intercity poster parser 2026-09-06 10:26:28 +02:00
.python-version Add PKP Intercity poster parser 2026-09-06 10:26:28 +02:00
main.py Add PKP Intercity poster parser 2026-09-06 10:26:28 +02:00
pyproject.toml Add PKP Intercity poster parser 2026-09-06 10:26:28 +02:00
README.md Expand project documentation 2026-09-06 10:29:41 +02:00
uv.lock Add PKP Intercity poster parser 2026-09-06 10:26:28 +02:00
Zestawienia pociągów - PKP Intercity.html Add PKP Intercity poster parser 2026-09-06 10:26:28 +02:00

pyicparser

Parser plakatów zestawień pociągów PKP Intercity (PDF). Wyciąga dane o odjazdach, składach, numerach wagonów i usługach pokładowych.

Projekt jest eksperymentalny i nie jest oficjalnym produktem PKP Intercity.

Instalacja

Wymagany jest Python 3.14 lub nowszy oraz uv.

uv sync

Aby zainstalować również zależności developerskie:

uv sync --extra dev

Użycie

Parsowanie PDF

uv run pyicparser parse <plik.pdf>
uv run pyicparser parse <plik.pdf> --format json
uv run pyicparser parse <plik.pdf> --format json --output wynik.json
uv run pyicparser parse <plik.pdf> --engine mupdf   # silnik PyMuPDF (eksperymentalny)

Pobieranie PDFów z intercity.pl

uv run pyicparser pull all                          # pobierz wszystkie stacje
uv run pyicparser pull all --station "Iława"        # filtruj po stacji
uv run pyicparser pull all --force                  # re-download nawet jeśli już jest
uv run pyicparser pull status                       # stan pobranych plików
uv run pyicparser pull status --format json

PDFy trafiają do data/, manifest do data/manifest.json.

Pobieranie wymaga dostępu do internetu. Opcja pull all może pobrać kilka dużych plików PDF, dlatego warto użyć --station, jeśli potrzebny jest tylko jeden rozkład.

Przykład wyniku JSON

{
  "station": "Katowice",
  "valid_from": "2026-03-08",
  "valid_to": "2026-06-13",
  "trains": [
    {
      "departure_time": "08:15",
      "category": "IC",
      "number": "1234",
      "name": "PRZYKŁAD",
      "destinations": ["Warszawa Centralna"],
      "platform": "3",
      "track": "2",
      "wagons": []
    }
  ]
}

Nie wszystkie pola są pokazane w przykładzie. Pełny schemat opisano poniżej.

Dane wyjściowe

Pole trains zawiera listę obiektów:

Pole Typ Opis
departure_time str Godzina odjazdu (HH:MM)
category str Kategoria (EIP, EIC, IC, TLK, EC, EN)
number str Numer pociągu
name str Nazwa handlowa
destinations list[str] Stacje docelowe
platform str Peron
track str Tor
routes list[str] Trasy odcinkowe (A → B)
wagons list[Wagon] Wagony z numerami i cechami
wagon_sequence_raw str Surowy ciąg wagonów
has_couchette bool Kuszetki
has_wheelchair bool Miejsce dla wózka
has_bicycle bool Przewóz rowerów
has_combo_wagon bool Wagon COMBO
has_power_outlets bool Gniazdka elektryczne
is_emu / is_dmu bool Zespół elektryczny/spalinowy
international bool Komunikacja międzynarodowa

Wagon:

Pole Typ Opis
number str Numer wagonu
features list[str] Cechy: couchette, seating, wheelchair, bicycle, direction_change, …
footnotes list[int] Numery przypisów

Silniki parsowania

plumber (domyślny)

Oparty o pdfplumber. Ekstrakcja znak po znaku, grupowanie w tokeny, detekcja kolumn przez strukturalne linie pionowe. Skuteczny dla obu wariantów PDF.

mupdf (eksperymentalny)

Oparty o PyMuPDF. Ekstrakcja na poziomie spanów tekstowych. Mniej dokładny — może tracić pociągi na stronie 2, zalecany tylko do testów.

Jak działa parser

Domyślny pipeline składa się z kilku etapów:

  1. pdfplumber odczytuje znaki i ich pozycje na stronie.
  2. Moduł layoutu wykrywa kolumny oraz bloki odpowiadające pojedynczym pociągom.
  3. Tokenizer grupuje znaki w fragmenty tekstu.
  4. Ekstrakcja interpretuje informacje o pociągu, trasie i składzie.
  5. Parser składu rozpoznaje numery wagonów, ich cechy i przypisy.
  6. Wynik jest walidowany przez modele Pydantic i prezentowany jako tabela albo JSON.

Struktura PDFów PKP IC — uwagi implementacyjne

PKP IC używa co najmniej dwóch różnych generatorów PDF:

Wariant A (duże stacje, np. Katowice)

  • Ikony w dedykowanej czcionce Intercity24 (glyph font)
  • Czyste pozycje znaków, bez nakładek
  • Linie strukturalne kolumn mają top < 0

Wariant B (mniejsze stacje, np. Iława Główna)

  • Ikony w zwykłej ArialMT (te same znaki: =, <, _, `)
  • Podwójnie renderowane znaki — te same glify 1–5pt obok siebie
  • Numery wagonów na granicach segmentów mogą się nakładać (dwa 2-cyfrowe w ~7pt)
  • Wiersz cech wagonu (np. _<} = kuszetka/wózek/rower) może być oddzielnym wierszem nad wierszem numerów

Zastosowane poprawki

  1. Deduplikacja znaków: page.dedupe_chars(tolerance=3, extra_attrs=("size",)) przed parsowaniem
  2. Detekcja kolumn: filtr top < 0 odrzuca linie schematu składu (zaczynają się od top=0)
  3. Tokenizer: wykrywanie nowej linii przez y-jump + ujemny x-gap
  4. Sortowanie znaków: zaokrąglenie top do 0.5pt eliminuje błędy kolejności przez float micro-precision
  5. Regex kategorii: dopuszcza prefiks * (*IC, *EIC na kolejnych stronach)
  6. Detekcja ikon: sprawdza zarówno czcionkę Intercity24 jak i znane znaki ikon w ArialMT
  7. Nakładające się cyfry wagonów: pomiń bold digit jeśli gap < -1pt (duplikat renderowania)
  8. Ikony przed pierwszym wagonem: wiersz cech w nagłówku diagramu — ikony akumulowane przed pierwszym numerem wagonu są przypisywane do pierwszego wagonu

Znane ograniczenia

  • Nakładające się numery wagonów na granicach segmentów (dwa numery w ~7pt) — nierozwiązywalne z samych danych znakowych
  • Niektóre PDFy pomijają ikony kuszetki/miejsc dla konkretnych pociągów (defekt w źródle)
  • Cyfry przypisów mogą wchodzić w nazwy pociągów (np. BALTIC EXPRESS5I)
  • Niniejszy moduł nie jest oficjalnym produktem PKP Intercity

Struktura projektu

src/pyicparser/
  cli.py          # CLI (Typer): parse, pull all/status
  parser.py       # główny parser (pdfplumber)
  engine_mupdf.py # alternatywny silnik (PyMuPDF)
  layout.py       # detekcja kolumn i bloków wierszy
  extraction.py   # ekstrakcja danych pociągu z bloku znaków
  composition.py  # parsowanie ciągu tokenów wagonu
  tokenizer.py    # grupowanie znaków w tokeny
  constants.py    # ICON_MAP, ICON_CHARS
  models.py       # modele Pydantic: Train, Wagon
  pull.py         # scraping i pobieranie PDFów z intercity.pl
  display.py      # Rich output
  metadata.py     # metadane PDFa

Zależności

  • pdfplumber — ekstrakcja znaków z PDF
  • pymupdf — alternatywny silnik
  • httpx — pobieranie PDFów (SSL TLS 1.2+, custom headers)
  • typer + rich — CLI i formatowanie
  • pydantic — modele danych

Development

Uruchomienie testów:

uv sync --extra dev
uv run pytest

Przykładowe sprawdzenie lokalnego PDF-a:

uv run pyicparser parse data/Katowice_20260308-20260613_20260401.pdf --format json

Katalog data/ zawiera przykładowe PDF-y i manifest pobrania. Są to dane wejściowe do testów ręcznych, a nie część oficjalnej bazy rozkładów.