advanced
geofence
prioridade
postgis

Prioridade de Geofences Empilhados

A escada de prioridade de 6 niveis que ordena zonas de politica municipal, zonas do operador e padroes do sistema -- e como o PostGIS retorna a regra mais restrita em cada ponto.

Equipe Levy FleetsMay 18, 202612 min read

Prioridade de Geofences Empilhados

Quando geofences de politica municipal sobrepoem zonas de operador, alguem precisa ganhar. O Levy usa uma escada de prioridade de 6 niveis, materializada como uma coluna inteira priority em cada zona, e resolvida no momento de leitura via um RPC PostGIS que retorna todas as zonas em um ponto ordenadas por prioridade descendente.

A escada de 6 niveis

Maior prioridade ganha. A primeira regra nao-nula para cada rule_type se torna a regra ativa naquele ponto.

NivelPrioridadeFonteExemplos
11000Politica municipal prohibited_areas + speed_limit"Sem pilotar na Pearl Street", "5 km/h na praca"
2950Politica municipal parking_zones + equity_zones"Estacionar so em corredores pintados", "implantar >= 20% na zona X"
3700Zonas no-ride definidas pelo operadorSeu proprio no-go ao redor de propriedade privada
4500Zonas slow definidas pelo operadorSeu proprio limite de velocidade em area de pedestres
5300Zonas de estacionamento definidas pelo operadorSuas proprias definicoes de corredor
6100Padroes do sistemaRegras de velocidade e estacionamento de fallback no nivel da subconta

Os valores numericos sao deliberadamente espacados em 50-200 para que operadores possam inserir uma zona de prioridade intermediaria personalizada se precisarem (por exemplo, uma zona temporaria de evento VIP em prioridade 800). Hoje a UI nao expoe edicao de prioridade personalizada -- toda zona de operador recebe seu padrao de nivel -- mas a coluna permite.

O resolvedor de conflitos

src/lib/compliance/conflict-resolver.ts expoe duas funcoes:

FuncaoO que faz
resolveStack(zones)Recebe uma lista de zonas em um ponto, retorna a regra ativa por rule_type (um limite de velocidade, um flag no-ride, uma regra de estacionamento).
detectOperatorOverrides(operatorZones, policyGeofences)Retorna as zonas de operador atualmente sombreadas por uma politica de prioridade maior.

resolveStack() e a funcao que alimenta current_speed_limit_kph em respostas MDS e a ordem de prioridade do geofencing_zones.json GBFS. Tambem e a funcao que o motor de cruzamento de zona chama em cada atualizacao de telemetria GPS durante uma viagem ativa.

detectOperatorOverrides() alimenta o banner de conflito no dashboard de compliance.

O RPC zones_containing_point

Migracao 20270601004000_07_zones_priority_source.sql envia uma funcao Postgres:

CREATE FUNCTION zones_containing_point(
  p_subaccount_id uuid,
  p_lat double precision,
  p_lng double precision
)
RETURNS SETOF zones
LANGUAGE sql STABLE
AS $$
  SELECT *
  FROM zones
  WHERE subaccount_id = p_subaccount_id
    AND deleted_at IS NULL
    AND ST_Contains(geom, ST_SetSRID(ST_MakePoint(p_lng, p_lat), 4326))
  ORDER BY priority DESC, created_at ASC;
$$;

A coluna geom e auto-populada da coluna geojson via gatilho. Toda zona, seja escrita pelo operador ou espelhada de uma politica, tem uma geom nao-nula, entao a query PostGIS e a resposta canonica para "quais zonas contem este ponto?".

stackForPoint(point)

Em JS, src/lib/compliance/zone-stack.ts embala o RPC e adicionalmente consulta policy_geofences (que atualmente usa pointInPolygon em JS porque a coluna PostGIS nessa tabela fica nula aguardando a migracao ST_GeomFromGeoJSON). A lista combinada e entao entregue a resolveStack().

const stack = await stackForPoint({
  subaccountId,
  lat: vehicle.last_lat,
  lng: vehicle.last_lng,
});
// stack: [
//   { source: 'city', priority: 1000, rule_type: 'speed', rule_value: 8, ... },
//   { source: 'operator', priority: 500, rule_type: 'speed', rule_value: 10, ... },
//   { source: 'operator', priority: 300, rule_type: 'parking', ... },
// ]

const active = resolveStack(stack);
// active.speed.rule_value === 8 // cidade ganha
// active.parking existe // estacionamento do operador ainda se aplica (cidade nao tem regra de estacionamento aqui)

Deteccao de conflito

detectOperatorOverrides() e chamado pelo dashboard do operador para mostrar o banner de alerta. Retorna, para a subconta dada, todas as zonas de operador cuja geometria intersecta uma linha policy_geofences ativa de prioridade maior e o mesmo rule_type.

Campo no resultadoO que significa
operator_zoneA zona sombreada
shadowing_ruleA regra de Policy MDS que a sobrepoe
affected_areaA intersecao de poligono (para a previa do mapa)
rule_typeO tipo de regra onde o conflito existe (speed, no_ride, parking)

Na UI, o operador pode:

  • Deletar a zona sombreada -- se agora e redundante com a politica municipal.
  • Editar para encaixar ao lado -- se a zona do operador existe por uma razao diferente (por exemplo, uma slow zone que envolve a zona no-go da cidade, fornecendo contexto para pilotos).
  • Reconhecer -- descartar o alerta sem mudar nada. O override permanece; o banner apenas para de incomodar.

Lendo a escada em clientes moveis

O app movel do cliente le o geofencing_zones.json GBFS 3.0 (ordenado por prioridade descendente) e trata o primeiro feature correspondente como a regra ativa. Isso e o que validadores GBFS esperam, e corresponde a semantica da escada: a regra municipal vem primeiro porque tem a prioridade mais alta.

Se seu cliente movel implementa sua propria resolucao de zona, a mesma regra se aplica -- ordene por prioridade descendente, pegue o primeiro feature correspondente para cada rule_type.

Cenarios

Cenario 1: slow zone municipal sobrepoe slow zone do operador

ZonaFontePrioridadeLimite de velocidade
Cidade "Pedestre do centro"Cidade10008 km/h
Operador "Plaza"Operador50010 km/h

Resultado: os 8 km/h da cidade ganham na sobreposicao. Fora da zona municipal mas dentro da zona do operador, o limite de 10 km/h se aplica. A zona do operador aparece no banner de conflito como "sombreada por Cidade Pedestre do centro".

Cenario 2: no-go municipal dentro de zona de estacionamento do operador

ZonaFontePrioridadeTipo
Cidade "Canteiro de obras"Cidade1000no_ride
Operador "Corredor Main Street"Operador300parking

Resultado: as regras sao de tipos diferentes, entao ambas se aplicam. Dentro do canteiro de obras, o veiculo nao pode pilotar (cidade no_ride ganha para no_ride rule_type). Fora do canteiro de obras mas dentro da zona do operador, estacionamento e permitido (estacionamento do operador e a unica regra parking).

Cenario 3: politica municipal expira

Quando policy_geofences.is_active vira false (porque active_until passou), o RPC zones_containing_point para de retornar essas linhas. A zona do operador embaixo volta ao topo da escada automaticamente. Sem limpeza manual; sem atualizacao do log de auditoria; o banner de conflito limpa no proximo carregamento de pagina.

Cenario 4: duas politicas municipais se sobrepoem

Cidades as vezes publicam duas politicas com geometria sobreposta (por exemplo, uma slow zone permanente mais uma zona no-ride temporaria de evento). Ambas estao na prioridade 1000 (nivel cidade), mas o resolvedor ordena empates por severidade de rule_type: no_ride supera speed, speed supera parking. A regra mais restrita se aplica.

Se o mesmo rule_type colide na mesma prioridade, ordenamos por start_date descendente -- a politica ativada mais recentemente ganha. Isso lida com "cidade atualizou seu limite de velocidade de slow zone de 10 para 8 km/h" de forma limpa.

O que voce pode mudar

BotaoPadraoNotas
Prioridade de zona de operadorPadrao de nivel (700/500/300)Valores personalizados exigem atualizacao direta do DB hoje.
Reconhecer um confliton/aAcao do dashboard; o override permanece mas o banner para de incomodar.
Deletar a zona sombreadan/aAcao do dashboard; zonas municipais espelhadas nao sao delete-aveis da UI do operador.

Zonas municipais nao podem ser editadas pelo operador. Pertencem ao feed de politica; o unico modo de muda-las e pedir a cidade para atualizar seu feed.

Proximos passos