Skip to content

Desktop application finding fastest rail connections using GTFS data and Dijkstra's algorithm. Features ticket price calculation, interactive map visualization, and station timetables. Built with Python and CustomTkinter

License

Notifications You must be signed in to change notification settings

Antek-N/RailNetworkGraph

Repository files navigation

RailNetworkGraph

RailNetworkGraph is a desktop application (built with CustomTkinter) that finds the fastest railway connections based on GTFS data.
It calculates route distance, ticket price, and visualizes the path on an interactive map.

The application supports:

  • Finding the fastest route (including transfers) using Dijkstra’s algorithm on a time-expanded graph.
  • Calculating ticket prices based on the physical railway track distance, not as-the-crow-flies.
  • Displaying an interactive map (Matplotlib) with stations, connections, and region borders.
  • Showing a timetable (nearest departures) when a station is clicked.

License: CC0-1.0 (see LICENSE for details)


⚙️ Technologies

Runtime

  • Python 3.12–3.14
  • CustomTkinter – GUI
  • Pandas – GTFS data processing
  • Matplotlib / GeoPandas – Interactive map plotting
  • NetworkX – Railway graph analysis (in distance_counter module)

Production / Development

  • Poetry – Dependency and package management
  • requirements.txt / requirements-dev.txt – pip installation
  • mkdocs – Documentation (docs/, mkdocs.yml)
  • pre-commit – Auto-formatting and linting
  • CI/CD – GitHub workflows (.github/)
  • logging_config – Colored logs with environment detection
  • PyInstaller.exe build (RailNetworkGraph.spec)
  • mypy – Static type checking
  • Black – Code formatting
  • Ruff – Linting and style enforcement

🧠 How It Works

1. Data Loading

  • On startup, the app loads GTFS timetable data.
  • It builds two graphs:
    • Time-Expanded Graph: Nodes represent departure events (e.g. (Station A, Train 123, 08:15)), used by Dijkstra to find the fastest route.
    • Station Graph: Nodes represent stations, used for map visualization and simple pathfinding (BFS).

2. User Input

  • The user provides origin station, destination station, and start time.
  • Optionally selects a discount.

3. Route Finding ("Search" Button)

  • SubmitHandler launches PathFinderDijkstra.
  • The algorithm finds the fastest path on the time-expanded graph, minimizing travel + transfer times.

4. Analysis (Distance & Price)

  • Retrieves physical distance by summing pre-computed segment lengths (railway_segment_lengths.txt).
  • Calculates ticket price using distance (base_price_table.txt) and applies discount (discounts_percentages.txt).

5. Presentation

  • TicketPopup displays route, duration, distance, and price.
  • The route is highlighted in red on the map.
  • Clicking any station opens StationInfoPopup with nearest departures.

🗂️ Project Structure

## 🗂️ Project Structure

RailNetworkGraph/
├─ .gitignore                                  # Git ignore rules
├─ .pre-commit-config.yaml                     # pre-commit hooks (Black, Ruff, mypy, etc.)
├─ LICENSE                                     # License (CC0-1.0, see exceptions inside)
├─ mkdocs.yml                                  # MkDocs site configuration
├─ poetry.lock                                 # Poetry lockfile
├─ pyproject.toml                              # Poetry project config (deps, tools)
├─ README.md                                   # Project readme
├─ requirements.txt                            # runtime dependencies
├─ RailNetworkGraph.spec                       # PyInstaller build specification
│
├─ .github/
│  └─ workflows/
│     ├─ build.yml                             # Build workflow (package/test build)
│     ├─ ci.yml                                # CI workflow (lint, tests)
│     └─ release.yml                           # Release workflow (PyInstaller, publish artifacts)
│
├─ docs/                                       # Documentation (MkDocs site content)
│  ├─ index.md                                 # Project introduction (homepage)
│  ├─ css/
│  │  ├─ mkdocstrings.css                      # Styling for mkdocstrings plugin
│  │  └─ theme-variants.css                    # Additional theme variants
│  └─ gen_ref_pages/                           # Scripts for generating API reference pages
│     ├─ config.py
│     ├─ context.py
│     ├─ generate.py
│     ├─ gen_ref_pages.py
│     ├─ helpers.py
│     └─ traverse.py
│
└─ src/
   └─ rail_network_graph/
      ├─ app.py                                # Main application (data loading, graph init, GUI setup)
      ├─ config.py                             # Project configuration (paths, constants, settings)
      ├─ logging_config.py                     # logging config (colors, ANSI detection)
      ├─ __main__.py                           # Entry point (python -m rail_network_graph)
      │
      ├─ assets/
      │  ├─ data/
      │  │  ├─ gtfs_data/
      │  │  │  ├─ routes.txt                   # GTFS routes file (names)
      │  │  │  ├─ stops.txt                    # GTFS stops file (locations, names, parent stations)
      │  │  │  ├─ stop_times.txt               # GTFS stop times file (schedule)
      │  │  │  └─ trips.txt                    # GTFS trips file (links stop_times to routes)
      │  │  ├─ railway_distances/
      │  │  │  └─ railway_segment_lengths.txt  # Output file: station-pair distances (km)
      │  │  ├─ tickets_price/
      │  │  │  ├─ base_price_table.txt         # Distance-to-price lookup table
      │  │  │  └─ discounts_percentages.txt    # Discount types and percentages
      │  │  └─ voivodeship_border/
      │  │     └─ dolnoslaskie.geojson         # GeoJSON file for the region map boundary
      │  └─ img/
      │     └─ icon.ico                        # Windows icon
      │
      ├─ data_processing/
      │  ├─ data_loader.py                     # Loads raw GTFS .txt files into pandas DataFrames
      │  └─ data_processor.py                  # Processes raw GTFS data (merging, mapping stops to stations)
      │
      ├─ distance_counter/
      │  ├─ distance_counter.py                # Main script to run the distance calculation pipeline
      │  ├─ graph_snapper.py                   # Snaps station coordinates (lat/lon) to the nearest railway graph node
      │  ├─ railway_graph_builder.py           # Builds NetworkX graph from railway GeoJSON line data
      │  ├─ railway_route_calculator.py        # Calculates shortest path distances (km) on the railway graph
      │  └─ results_exporter.py                # Exports calculated distances to a .txt file
      │
      ├─ graphs/
      │  ├─ base_graph.py                      # Base class for a simple directed graph
      │  └─ stations_graph.py                  # Graph of station-to-station connections based on GTFS trip sequences
      │
      ├─ GUI/
      │  ├─ gui_creator.py                     # Main GUI builder, initializes and connects all UI components
      │  ├─ counter_panel/
      │  │  ├─ input_panel.py                  # UI for station/time inputs and discount selection
      │  │  ├─ submit_handler.py               # Logic for the 'Search' button (runs pathfinder, gets price, updates UI)
      │  │  └─ ticket_popup.py                 # Popup window to display the final route, time, and price
      │  ├─ map/
      │  │  ├─ map_canvas.py                   # The interactive Matplotlib canvas (handles zoom, pan, click)
      │  │  └─ map_plotter.py                  # Logic for drawing stations, connections, and highlights on the map
      │  └─ station_info/
      │     ├─ station_info_logic.py           # Fetches connection data/timetable for a clicked station
      │     └─ station_info_popup.py           # Popup window to display timetable/info for a single station
      │
      ├─ path_finder/
      │  ├─ cleaner.py                         # Cleans the raw path result (removes cycles, collapses duplicates)
      │  ├─ graph.py                           # Builds the time-expanded graph for Dijkstra (nodes = departure events)
      │  ├─ models.py                          # Data models (e.g., StopRow) for pathfinding
      │  ├─ path_finder.py                     # Main Dijkstra algorithm implementation on the time-expanded graph
      │  └─ time_utils.py                      # Utility functions for handling time (HH:MM:SS <-> minutes)
      │
      └─ utils/
         ├─ distance_calculator.py             # Calculates total route distance using pre-computed segment lengths
         └─ price_calculator.py                # Calculates ticket price based on distance and pricing table


🔧 Installation

Option A — pip

Users (runtime only):

python -m venv .venv
source .venv/bin/activate      # Linux/macOS
.venv\Scripts\activate         # Windows

pip install --upgrade pip
pip install -r requirements.txt
pip install -e .

Developers (runtime + dev):

pip install -r requirements.txt -r requirements-dev.txt
pip install -e .

Option B — Poetry

Users (without dev):

poetry install --without dev
poetry run rail_network_graph

Developers (with dev):

poetry install
poetry run rail_network_graph

📚 Documentation

Built with mkdocs.

mkdocs serve      # local preview (http://127.0.0.1:8000)
mkdocs build      # build into site/

🏗️ Build Executable

To build a Windows .exe with PyInstaller:

pyinstaller RailNetworkGraph.spec

The resulting binary will be in dist/.


🛠️ Development Tools

This project uses several tools to keep the codebase clean and consistent:

Type checking

mypy src/

Linting

ruff check src/

Auto-formatting

black src/

Run all pre-commit hooks locally

pre-commit run --all-files

▶️ Running the App

From source

python -m rail_network_graph

With Poetry

poetry run rail_network_graph

After installation

rail_network_graph

💻 Usage

  1. Launch the app (rail_network_graph).
  2. Enter Station 1 (origin), Station 2 (destination), and Start time (HH:MM:SS).
  3. Optionally, select a discount.
  4. Click Search.
  5. View route details (time, price, distance) in the popup window.
  6. See the route highlighted on the map.
  7. (Optional) Click any station on the map to view its nearest departures.

🖼️ Screenshots

GIF: Main GIF

Main application screen:
Main application screen:

Search result (ticket popup):
Search result (ticket popup):

Station info (on-click):
Station info (on-click):


📜 License

Released under CC0-1.0 (public domain).
You may copy, modify, distribute, and use it commercially without asking for permission.

About

Desktop application finding fastest rail connections using GTFS data and Dijkstra's algorithm. Features ticket price calculation, interactive map visualization, and station timetables. Built with Python and CustomTkinter

Topics

Resources

License

Stars

Watchers

Forks

Languages