Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP][onibus_gps] Novo readme #45

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions dbt_project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,19 @@ vars:
shapes_version: "YYYY-MM-DD"
frequencies_version: "YYYY-MM-DD"
# valor_subsidio: "`rj-smtr-dev.projeto_subsidio_sppo.valor_subsidio`"

### Subsídio SPPO Pré-Produção (Ônibus) ###
gtfs_agency: "`rj-smtr-dev.gtfs.agency`"
gtfs_calendar: "`rj-smtr-dev.gtfs.calendar`"
gtfs_calendar_dates: "`rj-smtr-dev.gtfs.calendar_dates`"
gtfs_feed_info: "`rj-smtr-dev.gtfs.feed_info`"
gtfs_frequencies: "`rj-smtr-dev.gtfs.frequencies`"
gtfs_quadro: "`rj-smtr-dev.gtfs.quadro`"
gtfs_routes: "`rj-smtr-dev.gtfs.routes`"
gtfs_shapes: "`rj-smtr-dev.gtfs.shapes`"
gtfs_stop_times: "`rj-smtr-dev.gtfs.stop_times`"
gtfs_stops: "`rj-smtr-dev.gtfs.stops`"
gtfs_trips: "`rj-smtr-dev.gtfs.trips`"

### Realocação SPPO ###
data_inicio_realocacao: "2022-11-15"
Expand Down
93 changes: 93 additions & 0 deletions models/br_rj_riodejaneiro_onibus_gps/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Tratamento de Registros de GPS SPPO

* Versão: 1.0.0
* Data de início: 18/10/2022

![Relacionamento entre etapas do processo](imgs/relationship.png)

## Introdução

A Secretaria Municipal de Transportes (SMTR) da Prefeitura da Cidade do Rio de Janeiro recebe das empresas operadoras os dados brutos do GPS embarcado em cada um dos veículos operantes no Sistema de Transporte Público por Ônibus (SPPO). Esses dados são transmitidos pelas empresas operadoras a cada 30s, capturados pela SMTR a cada 60s e são armazenados como dado bruto e dado tratado.

Cada transmissão é representada geograficamente através de um ponto com latitude e longitude ([EPSG:4978 - WGS 84](https://epsg.io/4978)) e informações complementares, como número de ordem do veículo e a linha associada. Através dessa rotina, a cada minuto, os dados já capturados e armazenados são visualizados através da tabela [`sppo_registros`](https://www.data.rio/documents/transporte-rodoviário-gps-dos-ônibus).

Tabela `sppo_registros`
![Tabela sppo_registros](imgs/rj-smtr-dev.br_rj_riodejaneiro_onibus_gps.sppo_registros.png)

Eventualmente há erros na associação entre o veículo e a linha informada através do GPS. Dessa forma, as empresas operadoras podem informar uma realocação. A realocação é uma retificação, pela empresa operadora, da associação de linha a veículo e apenas pode ser realizada no limite máximo de 60 (sessenta) minutos conforme art. 2º, § 3º da Resolução SMTR Nº 3552/2022 ou legislação superveniente.

Nesse sentido, os dados de realocação são capturados a cada 10 minutos e são armazenados como dado bruto e dado tratado. Através dessa rotina, os dados já capturados e armazenados são visualizados através da tabela `sppo_realocacao`.

Tabela `sppo_realocacao`
![Tabela sppo_realocacao](imgs/rj-smtr-dev.br_rj_riodejaneiro_onibus_gps.sppo_realocacao.png)

Na sequência, será descrita a rotina de materialização dos registros de GPS, que ocorre no minuto 6 de cada hora.

## Etapas

### 1. Filtragem e tratamento básico de registros de realocação

Esta primeira etapa ([`sppo_aux_registros_realocacao`](https://github.com/prefeitura-rio/queries-rj-smtr/blob/master/models/br_rj_riodejaneiro_onibus_gps/sppo_aux_registros_realocacao.sql)) visa fazer o tratamento básico e filtragem dos dados de realocação através das subetapas descritas a seguir.

1. Filtra realocações válidas dentro do intervalo de GPS avaliado (máximo de 60 minutos);
2. Filtra apenas a realocação mais recente, caso existe mais de uma realocação para o mesmo período.

### 2. Filtragem e tratamento básico de registros de GPS

Esta etapa ([`sppo_aux_registros_filtrada`](https://github.com/prefeitura-rio/queries-rj-smtr/blob/master/models/br_rj_riodejaneiro_onibus_gps/sppo_aux_registros_filtrada.sql)) visa fazer o tratamento básico e filtragem desses dados através das subetapas descritas a seguir.

1. Seleciona apenas registros que estão no interior de uma caixa que contém a área do município de Rio de Janeiro;
2. Filtra apenas os registros do último minuto (remove registros que tem diferença maior que 1 minuto entre o `timestamp_captura` e `timestamp_gps`);
3. Muda o nome de variáveis para o padrão do projeto (`id_veiculo > ordem`, por exemplo).

**Observações:**
* A opção pela caixa, a despeito de um polígono com os limites municipais, se deu em razão de linhas com itinerário próximo a esses limites. Nesse caso, o GPS poderia indicar um ponto um pouco fora do limite, que seria perdido.

### 3. Verificação de descumprimento de itinerário

Nesta etapa ([`sppo_aux_registros_flag_trajeto_correto`](https://github.com/prefeitura-rio/queries-rj-smtr/blob/master/models/br_rj_riodejaneiro_onibus_gps/sppo_aux_registros_flag_trajeto_correto.sql)) é verificado se o veículo está dentro do trajeto correto dado o traçado (shape) em relação à linha associada na transmissão. O itinerário é cadastrado na aplicação da SMTR de gerenciamento de mobilidade urbana do município do Rio de Janeiro (SIGMOB). São observadas as seguintes subetapas:

1. Verifica se os pontos transmitidos pelo GPS encontram-se dentro de um _buffer_ de `tamanho_buffer_metros` em relação ao traçado definido no SIGMOB. Caso o ponto esteja no interior do _buffer_, atribui-se **verdadeiro** ao parâmetro `flag_trajeto_correto`. Caso contrário, é atribuído **falso**;
2. Calcula um histórico do parâmetro `flag_trajeto_correto` nos últimos 10 minutos de registros de cada veículo;
3. Identifica se a linha informada no registro capturado existe nas definições presentes no SIGMOB;
4. Verifica a `data_versao` de shape na tabela `shapes_geom`;
5. Agregação com `LOGICAL_OR` para evitar duplicação de registros.

**Observações:**
* Definiu-se que o veículo é considerado fora do trajeto definido se a cada 10 minutos, ele não esteve dentro do traçado planejado pelo menos uma vez;
* Definiu-se na tabela `shapes_geom` a coluna `data_versao` para identificar qual versão do shape no SIGMOB será utilizada;
* Como não é possível identificar o itinerário que o veículo está realizando no passo 2, os resultados de intersecções são dobrados, devido ao fato de cada linha apresentar dois itinerários possíveis (ida/volta). Portanto, ao final, é necessário realizar uma agregação `LOGICAL_OR` ao qual atribui-se **verdadeiro** caso o veículo esteja dentro do traçado de algum dos itinerários possíveis para a linha associada.

**Variáveis:**
```
tamanho_buffer_metros: 500
```

### 4. Identificação de veículos parados em terminais ou garagens conhecidas

Nesta etapa ([`sppo_aux_registros_parada`](https://github.com/prefeitura-rio/queries-rj-smtr/blob/master/models/br_rj_riodejaneiro_onibus_gps/sppo_aux_registros_parada.sql)) é verificado se o veículo está parado em terminais ou garagens conhecidas. São observadas as seguintes subetapas:

1. Selecionam-se os terminais, criando uma geometria do tipo ponto para cada;
2. Selecionam-se os polígonos das garagens;
2. Calcula-se as distâncias dos veículos em relação aos terminais conhecidos, selecionando apenas a menor distância em relação à posição do veículo;
3. Caso o veículo esteja a uma distância menor que `distancia_limiar_parada` de um terminal, será considerado como parado no terminal com menor distância.
4. Caso o veiculo esteja no interior do polígono de uma das garagens, ele será considerado como parado dentro de uma garagem.

**Variáveis:**
```
distancia_limiar_parada: 250
```

### 5. Identificação de _status_ de movimentação do veículo

Nesta etapa ([`sppo_aux_registros_velocidade`](https://github.com/prefeitura-rio/queries-rj-smtr/blob/master/models/br_rj_riodejaneiro_onibus_gps/sppo_aux_registros_velocidade.sql)) é verificado o _status_ de movimentação do veículo (parado ou em movimento). Isso é realizado através da estimativa das velocidades do veículo nos últimos 10 minutos contados a partir da `timestamp_gps` atual. São observadas as seguintes subetapas:

1. Considera-se a mínima distância cartesiana entre o ponto mais antigo e do ponto atual nos últimos 10 minutos de operação;
2. Divide-se essa distância pela diferença de tempo entre a `timestamp_gps` atual e a `timestamp_gps` do ponto mais antigo da janela em segundos;
3. Multiplica-se o resultado da divisão pelo fator `3.6` para converter de m/s para km/h. O resultado final é arrendondado sem casas decimais.
4. Após o calculo da velocidade, define-se a coluna `status_movimento`. Veículos abaixo da `velocidade_limiar_parado`, são considerados como `parado`. Caso contrário, são considerados `andando`.

**Variáveis:**
```
velocidade_limiar_parado: 3
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
138 changes: 138 additions & 0 deletions models/projeto_subsidio_sppo_preprod/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Subsídio SPPO

* Versão: 1.0.0
* Data de início: 01/06/2022

<!-- <img width="1283" alt="image"
src="https://user-images.githubusercontent.com/20743819/172705939-b4afdb7d-f11f-454e-9dd1-68c1f447ca47.png"> -->

<!-- ## Descrição -->

## Etapas

### 1. Atualização de viagens planejadas

<img width="995" alt="image"
src="https://user-images.githubusercontent.com/20743819/179116129-8f8d56d2-97c8-4e5b-b490-12c58d39bc80.png">

Os serviços e trajetos considerados no subsídio são
atualizados pela equipe de Planejamento,
podendo ser incluídas novas linhas, alteradas rotas ou
mesmo a quilometragem determinada. Essa
rotina acontece a cada quinzena de apuração do subsídio.

Como resultado, obtemos para cada dia de apuração o quadro planejado por
trajeto, conforme exemplo abaixo. Nele, consolidamos:

1. A distância planejada para cada viagem
(`distancia_planejada`, ou extensão do trajeto) e total por dia (`distancia_total_planejada`)

<img width="813" alt="image"
src="https://user-images.githubusercontent.com/20743819/179118003-d97f632c-810b-4f0f-a4ec-825e5d25f01a.png">

2. A geometria do trajeto (`shape`), assim como seu ponto inicial
(`start_pt`) e final (`end_pt`),
que serão usados para identificar as posições de GPS na próxima
etapa.

<img width="1250" alt="image" src="https://user-images.githubusercontent.com/20743819/179119285-063df325-f1d9-4736-b37b-9e0eeaf3b8a7.png">

**Observações**:

* Os trajetos circulares têm seu shape dividido em ida e volta, apa
possibilitar a identificação da viagem. Nestes casos, a coluna
`shape_id_planejado` recebe o id "teórico" e `shape_id` recebe o ID
de ida ou volta, trocando-se 11º caractere do ID [ex: 866 -
`O0866AAA0ACDU01` (teórico) x `O0866AAA0AIDU01` (ida)].

* O `tipo_dia` determina a quilometragem total planejada para aquele dia, conforme o [Plano Operacional](https://transportes.prefeitura.rio/subsidio/). Caso o dia seja um feriado, o `tipo_dia` considerado será de Domingo. Caso seja ponto facultativo, será usado `Sabado`.

* Nesta versão, os horários de `inicio_periodo` e `fim_periodo` são desconsiderados e consideramos o planejado para o dia inteiro.

### 2. Cálculo de viagens realizadas

<img width="1026" alt="image"
src="https://user-images.githubusercontent.com/20743819/179116020-37472af3-0b3e-4e94-862a-9c3bbb01f088.png">

O cálculo é realizado cruzando sinais de GPS com o trajeto planejado de
cada serviço. Em resumo, identifica-se potenciais viagens a partir de posições
do GPS emitidas nos pontos inicial e final do trajeto, e depois valida-se a
viagem caso atinja os percentuais de conformidade mínimos de:

* **Cobertura de GPS**: 50% dos minutos entre o início e fim da viagem devem ter pelo menos 1 sinal de GPS;
* **Cobertura do trajeto**: 80% das posições de GPS devem ter sido identificadas dentro
do trajeto planejado (num raio de 500m);

O passo a passo do algoritmo está descrito abaixo.

> Vamos seguir um exemplo com o ônibus B63050 (`id_veiculo`) no
> serviço `349` ao longo da metodologia para facilitar a explicação.
> <img width="367" alt="Screen Shot 2022-07-14 at 21 17 15"
src="https://user-images.githubusercontent.com/20743819/179121947-3bc63cfd-9f81-4ce7-be05-887ee4b6fe61.png">

#### 2.1. Classificação das posições de GPS no trajeto (`aux_registros_status_trajeto`)

As posições de GPS dos ônibus são capturadas a cada minuto, e
posteriormente tratadas a cada hora na tabela
[`gps_sppo`](). A partir dos dados de GPS, sabemos para cada veículo
(`id_veiculo`, ou número de ordem) e datahora (`timestamp_gps`), qual era sua posição
(`latitude`, `longitude`) e o serviço no qual estava operando (`servico`)
naquele momento.

> Para o dia 24/06, recebemos as seguintes informações por GPS do B63050 de 6:15 às 6:16:
<img width="491" alt="image"
src="https://user-images.githubusercontent.com/20743819/179122550-1d502871-7b4f-4b1e-bd7f-0a5959dad565.png">

Cruzamos essa tabela de posições de GPS com o trajeto (`shape`) da
`viagem_planejada` pela data e serviço para classificar cada
posição como:

* `start`: veículo estava no ponto inicial do trajeto (num raio de 500m)
* `end`: ponto final do trajeto (num raio de 500m)
* `middle`: meio do trajeto (num raio de 500m)
* `out`: fora do trajeto (num raio de 500m)

Nesta etapa, as posições são
duplicadas para os trajetos de ida (`I`) e volta (`V`)
pois ainda não temos como dizer qual sentido o veículo está operando.

> Uma vez classificado o `status_viagem`, obtemos para o mesmo intervalo
> de 6:15 às 6:16:
> <img width="819" alt="image" src="https://user-images.githubusercontent.com/20743819/179124464-9f08c16b-0272-4941-a7f8-45f8d5fe5394.png">

#### 2.2. Identificação de início e fim de viagens (`aux_viagem_inicio_fim`, `aux_viagem_circular`)

Uma vez classificadas as posições, buscamos os pares de início e fim que
possivelmente formam uma viagem.

Para isso, identificamos a "movimentação" do veículo: qual é seu
`status_viagem` naquele momento e qual era seu `status_viagem` imediatamente
anterior.
Classificamos, então, como início da viagem (`datetime_partida`) o momento em que o veículo
sai do ponto inicial e entra no trajeto, isto é, movimentação =
`startmiddle`. Da mesma forma, o fim da viagem é classificado como o
momento em que o veículo chega no ponto final a partir do trajeto, isto
é, movimentação = `middleend`.

> Na seção anterior vimos que o B63050 esteve no ponto inicial
(`start`) do trajeto de volta (`V`) às 6:15:26 e logo em seguida, às
6:15:56, esteve no meio do trajeto (`middle`). Logo, 6:15:26 é
potencialmente o início de uma viagem no serviço 349. Para afirmarmos isso, deve haver posteriormente uma movimentação `middleend`,
que é observada às 7:20:57. Realizando esse processo ao longo do dia
24/06, obtemos as seguintes possíveis viagens do B63050:
> <img width="898" alt="image"
> src="https://user-images.githubusercontent.com/20743819/179125623-af731b85-0f9d-4d00-bada-93ca5997dea8.png">

Realizamos um tratamento final nessa etapa para juntar as viagens circulares, separadas
nos trajetos de ida (`I`) e volta (`V`), na tabela `aux_viagem_circular`.
O início de uma viagem circular (`datetime_partida`) corresponde ao
início do trajeto de ida e o final da viagem (`datetime_chegada`) ao
final do trajeto de volta.

#### 2.3. Classificação das posições de GPS nas viagens (`registros_status_viagem`)

#### 2.4. Cálculo dos percentuais de conformidade da viagem (`viagem_conformidade`, `viagem_completa`)

### 3. Sumarização de viagens

<img width="1341" alt="image" src="https://user-images.githubusercontent.com/20743819/179116232-fb73d399-068c-4a79-8165-733c01f597ea.png">
47 changes: 47 additions & 0 deletions models/projeto_subsidio_sppo_preprod/aux_data_versao.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
-- 1. Realiza join de datas com a tabela calendar, agregando data_versao e service_id
WITH aux_calendar AS (
SELECT
c.timestamp_captura,
data,
c.service_id
FROM UNNEST(GENERATE_DATE_ARRAY("2022-06-01", "2022-12-31")) AS data
LEFT JOIN
{{ var("gtfs_calendar") }} AS c
ON
data BETWEEN c.start_date AND c.end_date
AND
CASE
WHEN EXTRACT(DAYOFWEEK FROM data) = 1 AND c.sunday = 1 THEN TRUE
WHEN EXTRACT(DAYOFWEEK FROM data) = 2 AND c.monday = 1 THEN TRUE
WHEN EXTRACT(DAYOFWEEK FROM data) = 3 AND c.tuesday = 1 THEN TRUE
WHEN EXTRACT(DAYOFWEEK FROM data) = 4 AND c.wednesday = 1 THEN TRUE
WHEN EXTRACT(DAYOFWEEK FROM data) = 5 AND c.thursday = 1 THEN TRUE
WHEN EXTRACT(DAYOFWEEK FROM data) = 6 AND c.friday = 1 THEN TRUE
WHEN EXTRACT(DAYOFWEEK FROM data) = 7 AND c.saturday = 1 THEN TRUE
ELSE FALSE
END
),
-- 2. Realiza join de datas com a tabela calendar_dates, agregando service_id de exceções (feriados)
aux_calendar_dates AS (
SELECT
c.* EXCEPT(service_id),
CASE
WHEN d.service_id IS NOT NULL THEN d.service_id
ELSE c.service_id
END AS service_id
FROM
aux_calendar AS c
LEFT JOIN
{{ var("gtfs_calendar_dates") }} AS d
ON
d.date = c.data
AND
d.timestamp_captura = c.timestamp_captura
AND
d.exception_type = "1"
)

SELECT
*
FROM aux_calendar_dates
WHERE timestamp_captura IS NOT NULL
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
-- 1. Seleciona sinais de GPS registrados no período
with gps as (
select
g.* except(longitude, latitude),
substr(id_veiculo, 2, 3) as id_empresa,
ST_GEOGPOINT(longitude, latitude) posicao_veiculo_geo
from
`rj-smtr.br_rj_riodejaneiro_veiculos.gps_sppo` g -- {{ ref('gps_sppo') }} g
where (
data between date_sub(date("{{ var("run_date") }}"), interval 1 day) and date("{{ var("run_date") }}")
)
-- Limita range de busca do gps de D-2 às 00h até D-1 às 3h
and (
timestamp_gps between datetime_sub(datetime_trunc("{{ var("run_date") }}", day), interval 1 day)
and datetime_add(datetime_trunc("{{ var("run_date") }}", day), interval 3 hour)
)
and status != "Parado garagem"
),
-- 2. Classifica a posição do veículo em todos os shapes possíveis de
-- serviços de uma mesma empresa
status_viagem as (
select
g.data,
g.id_veiculo,
g.id_empresa,
g.timestamp_gps,
timestamp_trunc(g.timestamp_gps, minute) as timestamp_minuto_gps,
g.posicao_veiculo_geo,
TRIM(g.servico, " ") as servico_informado,
s.servico as servico_realizado,
s.shape_id,
s.sentido_shape,
s.shape_id_planejado,
s.trip_id,
s.trip_id_planejado,
s.sentido,
s.start_pt,
s.end_pt,
s.distancia_planejada,
ifnull(g.distancia,0) as distancia,
case
when ST_DWITHIN(g.posicao_veiculo_geo, start_pt, {{ var("buffer") }})
then 'start'
when ST_DWITHIN(g.posicao_veiculo_geo, end_pt, {{ var("buffer") }})
then 'end'
when ST_DWITHIN(g.posicao_veiculo_geo, shape, {{ var("buffer") }})
then 'middle'
else 'out'
end status_viagem
from
gps g
inner join (
select
*
from
{{ ref("viagem_planejada") }}
where
data between date_sub(date("{{ var("run_date") }}"), interval 1 day) and date("{{ var("run_date") }}")
) s
on
g.data = s.data
and g.servico = s.servico
)
select
*,
'{{ var("version") }}' as versao_modelo
from
status_viagem
Loading