O General Transit Feed Specification (GTFS) é um formato normalizado de dados abertos para documentar informações sobre transportes públicos, incluindo: itinerários, horários, localizações de paragens, padrões de calendário, viagens e possíveis transbordos. As operadoras de transporte público são responsáveis por manter os dados actualizados1.

Os dados GTFS são utilizados em várias aplicações, como o Google Maps, City Mapper, entre outros, para fornecer direcções de transportes públicos. Podem ser disponibilizadas para uma cidade, uma região ou mesmo um país inteiro, dependendo da operadora de TP.

Os dados encontram-se num ficheiro .zip que inclui vários ficheiros .txt (um para cada tipo de informação) com relações tabulares (campos comuns). Os mesmos podem ser facilmente editados em qualquer computador.

Estrutura de um ficheiro GTFS. Fonte: trilliumtransit.com

Campos e identificador comum dos ficheiros GTFS. Fonte: Trafiklab.se

Campos e identificador comum dos ficheiros GTFS. Fonte: Trafiklab.se

Os GTFS podem ser estáticos (ou agendados / schedule) - por exemplo para viagens tabeladas, ou em tempo real (real time) - inclui mais informação, tal como posicionamento em tempo real.

6.1 GTFS-Schedule

O GTFS Schedule é uma especificação que define um formato comum para informações estáticas sobre transportes públicos (agendadas, ou que não alteram dinamicamente).

Cada ficheiro descreve informação sobre transportes, como paragens, percursos, viagens, etc. Na sua forma mais simples, um conjunto de dados GTFS é composto por 7 ficheiros:

  • agency.txt
  • routes.txt
  • trips.txt
  • stops.txt
  • stop_times.txt
  • calendar.txt
  • calendar_dates.txt

6.2 GTFS-Realtime

Os GTFS Realtime é uma norma que permite às operadoras de transportes públicos fornecer informações actualizadas sobre as horas de chegada e partida, alertas de serviço e posição dos veículos, permitindo aos utilizadores planear as suas viagens com maior detalhe, incluindo por exemplo os seguintes tipos de informação:

  • Actualizações de viagens - atrasos, cancelamentos, alteração de itinerários

  • Alertas de serviço - paragens deslocadas, acontecimentos imprevistos que afectem uma estação, um itinerário ou toda a rede

  • Posições dos veículos - informações sobre os veículos, incluindo a localização e o nível de ocupação do interior do veículo

Um exemplo de GTFS-Realtime são os dados da Carris Metropolitana.

Planeador de percursos em tempo real da Carris Metropolitana.

Posicionamento em tempo real de autocarros Carris com passagem no Saldanha. Fonte: Google Maps

6.3 Fontes online de dados GTFS

Os seguintes websites incluem vários GTFS de operadoras, a nível mundial. Uma vantagem destes agregadores de dados é que muitas vezes também disponibilizam versões por data - útil para análises do ano X.

Algumas operadoras de Transporte Público nacionais também disponibilizam os seus dados abertos nos websites. (Ver NAP Portugal)

Podemos juntar vários GTFS.zip num único MegaGTFS.zip

6.4 Pacotes de R

Existem alguns bons pacotes R para ler e manipular dados GTFS, tais como:

Atenção que os pacotes podem partilhar os mesmos nomes de funções, pelo que é importante utilizar um delas de cada vez.

6.5 Exemplo em R

6.5.1 Metro Lisboa

Vamos importar os dados de GTFS diretamente do website do Metro de Lisboa

Código
## Pacotes
library(tidyverse)
library(tidytransit)
library(mapview)

# Ler dados GTFS
METRO = read_gtfs("https://github.com/rosamfelix/PGmob360/releases/download/2024.11/metro_gtfs.zip")
summary(METRO)
tidygtfs object
files        agency, routes, stop_times, trips, fare_attributes, fare_rules, frequencies, transfers, calendar, calendar_dates, feed_info, stops
agency       Metropolitano de Lisboa, E.P.E.
service      from 2023-08-07 to 2031-12-31
uses         stop_times (no frequencies)
# routes        4
# trips      2518
# stop_ids     74
# stop_names   50
# shapes        0

Este ficheiro inclui as transferências, mas não inclui as rotas. Neste caso não é relevante, elas podem ser depois geradas com a informação que já existe.

Podemos ver a localização das 50 estações do Metro:

Código
ESTACOES_metro = stops_as_sf(METRO$stops) # converter texto para geometria
mapview(ESTACOES_metro)

Desafio: Ver quanto tempo demora a alcançar cada estação a partir da Baixa

  1. num dia útil em hora de ponta
  2. num domingo de noite

Dia útil em hora de ponta

Filtramos os dados para o dia de hoje, quarta-feira, entre as 7h30 e as 9h00, e calculamos a duração da viagem mais rápida entre a estação Baixa-Chiado e todas as outras.

Código
stop_times_metro = filter_stop_times(METRO, "2024-11-20", "07:30:00", "09:00:00")
duracao_viagem = travel_times(stop_times_metro, "Baixa / Chiado") # Baixa / Chiado é a estação de metro da Baixa

summary(duracao_viagem$travel_time/60) # resumo em minutos
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   6.483  13.450  12.923  18.929  26.583 

Sem contar com o tempo de espera na Baixa-Chiado, é possível alcançar qualquer estação da rede em menos de 26.5 minutos, em hora de ponta.

Domingo de noite

Filtramos os dados para o próximo Domingo entre as 21h30 e as 22h30, e novamente calculamos a duração da viagem mais rápida entre a estação Baixa-Chiado e todas as outras

Código
stop_times_metro = filter_stop_times(METRO, "2024-11-24", "21:30:00", "22:30:00")
duracao_viagem = travel_times(stop_times_metro, "Baixa / Chiado") 

summary(duracao_viagem$travel_time/60) # resumo em minutos
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   6.321  16.008  14.619  22.042  32.317 

Num Domingo de noite, é possível alcançar qualquer estação da rede em menos de 32.3 minutos.

Visualizar dados num mapa

Podemos criar um gráfico que mostre o tempo que cada estação demora a alcançar, a partir da Baixa-Chiado.

Juntamos primeiro os tempos estimados aos dados georreferenciados das estações

Código
duracao_viagem_BC_HP = ESTACOES_metro |> 
  left_join(duracao_viagem, by = c("stop_id" = "to_stop_id")) |>  # juntar os dados às estaçoes
  filter(!is.na(travel_time)) # remover as plataformas não usadas

E “colorimos” o tempo de viagem que demora (no mínímo) a alcançar cada estação

Código
##| code-summary: "Mostrar código"
ggplot(duracao_viagem_BC_HP) +
  geom_sf(aes(color = travel_time/60)) +
  ggtitle("Alcance desde a estação Baixa-Chiado (Metro)",
          subtitle = "às 7h30 de Quarta, 20 Nov 2024") +
  labs(color = "Tempo de viagem [min]") +
  geom_sf(data = LisboaGEO, # limite Lisboa
          fill = "transparent",
          color = "grey30") +
  theme_bw()

6.5.2 Carris Lisboa

Podemos fazer o mesmo exercício com os dados da Carris. O ficheiro original não inclui o transfers.txt, e foi gerado automaticamente um com a função gtfsrouter::gtfs_transfer_table()

Código
# Ler dados GTFS
CARRIS = read_gtfs("https://github.com/rosamfelix/PGmob360/releases/download/2024.11/carris_gtfs.zip")
summary(CARRIS)
tidygtfs object
files        agency, routes, stop_times, trips, shapes, transfers, calendar, calendar_dates, stops
agency       Carris
service      from 2024-11-01 to 2024-12-15
uses         stop_times (no frequencies)
# routes       172
# trips      31827
# stop_ids    2313
# stop_names  1116
# shapes       305

Este ficheiro já inclui as transferências, e também as rotas.

Podemos ver a localização das 2313 estações da Carris:

Código
ESTACOES_carris = stops_as_sf(CARRIS$stops) # converter texto para geometria
mapview(ESTACOES_carris)

E as 1739 rotas (inclui variantes, idas e voltas):

Código
ROTAS_carris = shapes_as_sf(CARRIS$shapes) # converter texto para geometria
mapview(ROTAS_carris, zcol = "shape_id") # visualizar

Vamos fazer o mesmo desafio, mas com origem nas paragens do Cais do Sodré.

Dia útil em hora de ponta

Filtramos os dados para o dia de hoje, quarta-feira, entre as 7h30 e as 9h00, e calculamos a duração da viagem mais rápida entre a estação Baixa-Chiado e todas as outras.

Código
stop_times_carris1 = filter_stop_times(CARRIS, "2024-11-20", "07:30:00", "09:00:00")
duracao_viagem1 = travel_times(stop_times_carris1, "Cais Sodré", stop_dist_check = FALSE)

nrow(duracao_viagem1)
[1] 1114
Código
summary(duracao_viagem1$travel_time/60) # resumo em minutos
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   0.00   19.00   28.85   30.67   40.04   82.00 

Sem contar com o tempo de espera na Baixa-Chiado, é possível alcançar 1114 estações da rede (48%) em menos de 82 minutos (1h22), em hora de ponta - no intervalo de 1h30 definido.

Outras estatísticas
Código
hist(duracao_viagem1$travel_time/60, breaks = 20) # histograma
abline(v = mean(duracao_viagem1$travel_time/60), col = "red") # linha de média
abline(v = median(duracao_viagem1$travel_time/60), col = "blue") # linha de mediana

Código
round(prop.table(table(duracao_viagem1$transfers))*100, 1) # percentagem de transferências

   0    1    2    3    4 
19.1 43.0 24.0 11.3  2.6 

19% das paragens são alcançáveis diretamente (sem transferência de autocarro). 62% são alcançáveis com 1 transferência no máximo, e 2.6% das paragens necessitam de 5 autocarros para ser alcançadas (4 transferências).

Domingo de noite

Filtramos os dados para o próximo Domingo entre as 22h30 e as 23h30, e novamente calculamos a duração da viagem mais rápida entre as paragens de autocarro do Cais do Sodré e todas as outras

Código
stop_times_carris2 = filter_stop_times(CARRIS, "2024-11-24", "22:00:00", "23:30:00")
duracao_viagem2 = travel_times(stop_times_carris2, "Cais Sodré", stop_dist_check = FALSE)

nrow(duracao_viagem2)
[1] 912
Código
summary(duracao_viagem2$travel_time/60) # resumo em minutos
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   0.00   16.51   24.98   26.53   34.28   67.00 

Num Domingo de noite, é possível alcançar 912 estações da rede (39%) em menos de 67 minutos.

Visualizar dados num mapa

Podemos criar dois mapas que comparem o tempo que cada paragem demora a alcançar, a partir ddo Cais do Sodré, para os períodos definidos.

Juntamos primeiro os tempos estimados aos dados georreferenciados das estações

Código
# hora de ponta
duracao_viagem_CS_HP = ESTACOES_carris |> 
  left_join(duracao_viagem1, by = c("stop_id" = "to_stop_id")) |>  # juntar os dados 
  filter(transfers >= 1) |> # remover viagens com mais de 1 transferência
  filter(!is.na(travel_time)) |>  # remover as plataformas não usadas
  filter(travel_time < 60*60) |> # remover viagens com mais de 60 minutos
  filter(!stop_id %in% c(19001,14601,14602)) # remover as pagarens em Almada

# domingo de noite
duracao_viagem_CS_Dom = ESTACOES_carris |> 
  left_join(duracao_viagem2, by = c("stop_id" = "to_stop_id")) |>  # juntar os dados 
  filter(transfers >= 1) |> # remover viagens com mais de 1 transferência
  filter(!is.na(travel_time)) |>  # remover as plataformas não usadas
  filter(travel_time < 60*60) |>  # remover viagens com mais de 60 minutos
  mutate(intervalo = case_when( # criar uma variável com o intervalo de tempo
    travel_time < 15*60 ~ "até 15 min",
    travel_time < 30*60 ~ "até 30 min",
    travel_time < 45*60 ~ "até 45 min",
    TRUE ~ "até 60 min" # tudo o resto..
  ))

E “colorimos” o tempo de viagem que demora (no mínímo) a alcançar cada estação

Código
ggplot(duracao_viagem_CS_HP) +
  geom_sf(aes(color = travel_time/60))+
  ggtitle("Alcance desde o Cais do Sodré (Carris)",
          subtitle = "às 7h30 de Quarta, 20 Nov 2024 - máx 1 transf") +
  labs(color = "Tempo de viagem [min]") +
  geom_sf(data = LisboaGEO,
          fill = "transparent",
          color = "grey30") +
  theme_bw()

Código
ggplot(duracao_viagem_CS_Dom) +
  geom_sf(aes(color = intervalo))+ # mudar para escala discreta
  scale_color_manual(values = c("até 15 min" = "#119da4",
                                "até 30 min" = "#0c7489", 
                                "até 45 min" = "#13505B",
                                "até 60 min" = "#040404")) +
  ggtitle("Alcance desde o Cais do Sodré (Carris)",
          subtitle = "às 22h00 de Domingo, 24 Nov 2024 - máx 1 transf") +
  labs(color = "Tempo de viagem [min]") +
  geom_sf(data = LisboaGEO,
          fill = "transparent",
          color = "grey30") +
  theme_bw()

Como podemos visualmnente comparar, em hora de ponta é possível chegar de autocarro a bem mais paragens que num Domingo de noite.

TPC

Experimente importar, explorar e visualizar os dados GTFS da Carris Metropolitana.

6.6 NeTEX

Outro formato semelhande ao GTFS é o NeTEX (Network Timetable EXchange). Este também é usado por algumas operadoras de TP em Portugal.

O NeTEx está dividido em seis partes, cada uma abrangendo um subconjunto funcional do Transmodelo CEN para Informação sobre Transportes Públicos:

  1. Parte 1: Descreve a topologia da rede de transportes públicos (CEN/TS 16614-1:2014)
  2. Parte 2: Descreve os horários agendados (CEN/TS 16614-2:2014)
  3. Parte 3: Informação tarifária (CEN/TS 16614-3:2015)
  4. Parte 4: Perfil Europeu de Informação de Passageiros - EPIP (CEN/TS 16614-4:2017)
  5. Parte 5: Formato de intercâmbio de modos alternativos (CEN/TS 16614-5:2021)
  6. Parte 6: Perfil Europeu de Acessibilidade às Informações de Passageiros - EPIAP (CEN/TS 16614-6:2024)

Os dados NeTEX usam o formato .xml, o que pode tornar a informação mais difícil de ler ou editar.

6.7 GBFS

Para dados de sistemas de mobilidade partilhada e micromobilidade, há ainda o General Bicycle Feed Specification (GBFS).

Tal como os GTFS, os GBFS são dados normalizados com toda a informação sobre os sistemas partilhados, e incluem informações sobre os veículos (bicicletas, trotinetas, motociclos e automóveis), docas, preços, entre outros:

  • Localização e disponibilidade de veículos, estações e docas

  • Caraterísticas do veículo - tipo de energia, distância que ainda pode ser percorrida

  • Preços do serviço e condições de aluguer

  • Áreas delimitadas geograficamente para regras relacionadas com velocidade permitida, estacionamento e zonas proibidas

Os dados GBFS são utilizados por aplicações de planeamento de viagens e de Mobilidade como Serviço (MaaS), para fornecer as informações de que os utilizadores necessitam para utilizar a mobilidade partilhada.

As APIs públicas do GBFS facilitam a integração de serviços de mobilidade partilhada com os transportes públicos, permitindo que os utilizadores façam intermodalidade - ligações entre a primeira e a última milha.

Além disso, o GBFS fornece aos municípios e operadoras uma forma padronizada de recolher, analisar e comparar dados gerados pelos sistemas de mobilidade partilhada.


  1. Dados de operadora de transportes que não estejam atualizados poderão levar a uma péssima experiência ao utilizador, uma vez que a realidade não corresponde às expetativas criadas pelos dados (desatualizados).↩︎