Разбираемся в GTFS: анатомия городского транспорта
Публикации

Разбираемся в GTFS: анатомия городского транспорта

Вы стоите на остановке в минус двадцать, мобильный показывает «автобус через 3 минуты», и вы думаете — откуда он это знает? Неужели кто-то сидит на диспетчерском пункте и вручную обновляет данные в Google Maps? Спойлер: нет. За этой магией стоит целая экосистема под кодовым названием GTFS. Давайте разбираться, как она устроена изнутри.

История начинается в 2005 году в Портленде, штат Орегон. Крис Харрелсон из Google и пара айтишников из местного транспортного агентства TriMet сидели и думали: почему так сложно сделать удобную навигацию по общественному транспорту? Проблема была не в алгоритмах построения маршрутов — их полно. Проблема была в данных. Каждое транспортное агентство хранило расписания в своём формате: кто-то в Excel, кто-то в PDF, кто-то вообще только на бумажках в диспетчерской.

Решение оказалось до смешного простым: давайте договоримся о формате. Не каком-то сложном XML с кучей namespace'ов, а простом, как таблица в блокноте. CSV-файлы, разделённые запятыми, которые открываются даже в Notepad. Так родился GTFS — General Transit Feed Specification.

Анатомия фида: что внутри ZIP-архива

Скачайте любой GTFS-фид — например, с сайта городского транспорта Санкт-Петербурга — и вы увидите примерно такую картину:

Давайте пройдёмся по каждому файлу и посмотрим, как они связаны между собой.

agency.txt: паспорт перевозчика

Самый простой файл. Обычно там одна строка с названием, сайтом и часовым поясом. Критически важен именно timezone — без него ваше «8:00 утра» превратится в «8:00 по Гринвичу», и навигатор будет врать на 3 часа.

routes.txt: логические маршруты

Это не конкретные автобусы, а «легенды карты». Например, маршрут «15» с названием «Центр — Аэропорт». У каждого маршрута есть тип: автобус, трамвай, метро. Всего предусмотрено 12 типов на все случаи жизни.

trips.txt: рейсы в чистом виде

Теперь начинается магия. Один маршрут может иметь десятки рейсов в день. Утренний, дневной, вечерний, экспресс, следующий через садовое кольцо. Каждый рейс привязан к календарю — например, «по будням» или «только по воскресеньям».

Здесь есть скрытая жемчужина — поле block_id. Оно говорит, что после прибытия в конечную точку этот же автобус (физически тот же гаражный номер) пойдёт по другому маршруту. Диспетчеры называют это «блоком», отсюда и название. Это важно для точного расчёта пересадок: если вы выходите из автобуса, который продолжит движение как другой маршрут, система может предложить вам «пересесть», фактически не выходя из салона.

stop_times.txt: расписание с точностью до секунды

Самый большой файл в фиде. Для петербургского транспорта это более 3,5 миллионов строк — каждая остановка каждого рейса с временем прибытия и отправления.

Формат времени — часы:минуты:секунды, и он может быть больше 24! Да, если автобус отправляется в 25:30:00, это означает 1:30 ночи следующего дня. GTFS так устроен, чтобы ночные рейсы не разрывались на два дня.

Интересный нюанс: время прибытия и отправления разные. Первые 30 секунд — время на посадку-высадку. На практике многие агентства ставят одинаковое время в обе колонки, что немного врёт о реальности, но упрощает жизнь разработчикам.

stops.txt: географическая привязка

Здесь живут координаты. Каждая остановка имеет широту и долготу. Интересно, что остановка «в одну сторону» и «в другую» — это разные записи с разными координатами, даже если физически это одна платформа. Это позволяет точно определить, на какой стороне улицы вы стоите.

Есть иерархия: отдельная остановка может быть частью станции (например, разные выходы из метро). Тогда используется поле parent_station для группировки.

calendar.txt: когда всё это работает

Бинарная матрица дней недели: 1 — работаем, 0 — выходной. Плюс диапазон дат действия. Но что делать с праздниками? Для этого есть calendar_dates.txt — он позволяет добавить исключения: «25 декабря не работаем, хотя это среда» или «работаем 8 марта, хотя это суббота».

shapes.txt: как рисовать линии на карте

Без этого файла навигатор нарисует прямую линию от остановки к остановке через три квартала и реку. Shapes содержит последовательность точек — тысячи координат для одного маршрута. По ним строится реальная линия на карте, повторяющая изгибы улиц.

Как это всё связано: реляционная база на минималках

Вся магия GTFS — в связях между таблицами. Это не просто набор файлов, а полноценная реляционная база данных, только без SQL.

Центр всей системы — таблица trips. Она связывает маршруты с расписанием и календарём. Чтобы найти ближайший автобус до работы, приложение делает примерно следующее:

  1. Ищет ближайшие остановки по координатам
  2. Находит все проходящие через них рейсы с временем позже текущего
  3. Проверяет, работает ли этот рейс сегодня (по календарю)
  4. Сортирует по времени прибытия и показывает вам результат

Всё это происходит за миллисекунды, потому что CSV — это просто текст, и современные библиотеки оптимизированы под эти запросы.

GTFS используется в Яндекс.Картах, 2GIS, приложениях типа «Мой автобус». Но прежде всего GTFS — золотая жила для urban data science. Можно строить изохроны — зоны, достижимые за N минут от точки. Можно анализировать, соблюдаются ли заявленные интервалы движения («каждые 10 минут» на самом деле каждые 8 или 15?). Можно сравнивать плановое и фактическое время в пути. Можно строить тепловые карты загруженности по часам, проводить анализ доступности транспорта в разных районах.  

Подводные камни, о которых молчат документации

  • Не все поездки в trips.txt — это пассажирские рейсы. Иногда там есть «перегоны в депо», которые видны в фиде, но на остановках не останавливаются. Они помечены специальными флагами, но не все агентства это делают честно.
  • Если автобус отправляется в 01:30 ночи, это может быть записано как 25:30:00 относительно предыдущего дня. Это логично для построения расписания (рейс начался вчера), но ломает стандартные парсеры дат. Будьте готовы к часам больше 24.
  • GTFS Static — это план на будущее. Он обновляется раз в сутки или при изменении расписания. Если автобус сломался или встал в пробке — статический фид об этом не знает. Для реального времени существует отдельное расширение GTFS Realtime, но это уже другая история…