""" Visualização interativa do resultado OAMRP — Esquadrão Arara C-105. Gera um mapa HTML (Folium) com: - Marcadores para cada base operacional - Rotas por aeronave (cor distinta por EVAM) - Missões não cumpridas em cinza tracejado - Popup com detalhes de cada missão Uso: python software/visualizar_resultado.py \ --resultado db/processed/resultado_oamrp_sintetico.csv \ --saida db/processed/mapa_rotas.html """ import argparse import csv from pathlib import Path import folium from folium import plugins BASE_DIR = Path(__file__).resolve().parents[1] RESULTADO_PADRAO = BASE_DIR / "db" / "processed" / "resultado_oamrp_sintetico.csv" MAPA_PADRAO = BASE_DIR / "db" / "processed" / "mapa_rotas.html" # Coordenadas das bases (lat, lon) BASES_COORDS: dict[str, tuple[float, float]] = { "SBMN": (-3.15, -59.99), "SBBE": (-1.38, -48.48), "SBBV": ( 2.84, -60.69), "SBSN": (-2.42, -54.79), "SBPV": (-8.71, -63.90), "SBTS": (-4.25, -69.94), "SBTT": (-3.38, -64.72), "SBUA": (-0.15, -67.05), "SWBC": (-0.98, -62.92), "SBMY": (-5.81, -61.28), } # Nomes legíveis das bases BASES_NOMES = { "SBMN": "Manaus / Ponta Pelada (Hub)", "SBBE": "Belém", "SBBV": "Boa Vista", "SBSN": "Santarém", "SBPV": "Porto Velho", "SBTS": "Tabatinga", "SBTT": "Tefé", "SBUA": "São Gabriel da Cachoeira", "SWBC": "Barcelos", "SBMY": "Manicoré", } # Paleta de cores por aeronave CORES_AERONAVE = { "2800": "#1f77b4", # azul "2803": "#2ca02c", # verde "2809": "#d62728", # vermelho "2811": "#ff7f0e", # laranja } COR_NAO_CUMPRIDA = "#aaaaaa" COR_HUB = "#d62728" COR_BASE = "#1f77b4" def _cor_prio(prio: str) -> str: mapa = {"1": "red", "2": "orange", "3": "yellow", "4": "lightblue", "5": "lightgray"} return mapa.get(str(prio), "white") def carregar_resultado(caminho: Path) -> list[dict]: with caminho.open(newline="", encoding="utf-8-sig") as f: return list(csv.DictReader(f)) def adicionar_bases(mapa: folium.Map) -> None: for icao, (lat, lon) in BASES_COORDS.items(): cor = COR_HUB if icao == "SBMN" else COR_BASE icone = folium.Icon(color="red" if icao == "SBMN" else "blue", icon="plane", prefix="fa") folium.Marker( location=[lat, lon], tooltip=f"{icao}
{BASES_NOMES.get(icao, '')}", popup=folium.Popup(f"{icao}
{BASES_NOMES.get(icao, '')}", max_width=200), icon=icone, ).add_to(mapa) def gerar_mapa(resultado: Path, saida: Path) -> None: linhas = carregar_resultado(resultado) mapa = folium.Map( location=[-3.5, -62.0], zoom_start=5, tiles="CartoDB positron", ) # Grupos por aeronave + grupo para não cumpridas aeronaves_vistas = sorted({r["aeronave"] for r in linhas if r["aeronave"]}) grupos: dict[str, folium.FeatureGroup] = {} for k in aeronaves_vistas: grupos[k] = folium.FeatureGroup(name=f"EVAM {k}", show=True) grupos["nao_cumprida"] = folium.FeatureGroup(name="Não cumpridas", show=True) cumpridas = [r for r in linhas if r["status"] == "cumprida"] nao_cumpridas = [r for r in linhas if r["status"] != "cumprida"] # Rotas cumpridas for r in cumpridas: orig = r["orig"] dest = r["dest"] if orig not in BASES_COORDS or dest not in BASES_COORDS: continue lat_o, lon_o = BASES_COORDS[orig] lat_d, lon_d = BASES_COORDS[dest] k = r["aeronave"] cor = CORES_AERONAVE.get(k, "#333333") # Linha de rota folium.PolyLine( locations=[[lat_o, lon_o], [lat_d, lon_d]], color=cor, weight=2.5, opacity=0.8, tooltip=f"EVAM {k} | {orig}→{dest} | OM {r['om']} | prio {r['prioridade']}", popup=folium.Popup( f"EVAM {k} — Missão #{r['ordem']}
" f"{orig} → {dest}
" f"OM: {r['om']} | Prio: {r['prioridade']}
" f"Partida: {r['partida_utc'][:16].replace('T',' ')}Z
" f"Chegada: {r['chegada_utc'][:16].replace('T',' ')}Z
" f"Duração: {r['dur_h']}h", max_width=250, ), ).add_to(grupos[k]) # Marcador de origem (pequeno círculo) folium.CircleMarker( location=[lat_o, lon_o], radius=4, color=cor, fill=True, fill_color=_cor_prio(r["prioridade"]), fill_opacity=0.9, tooltip=f"{orig} | prio {r['prioridade']}", ).add_to(grupos[k]) # Missões não cumpridas (linha tracejada cinza) for r in nao_cumpridas: orig = r["orig"] dest = r["dest"] if orig not in BASES_COORDS or dest not in BASES_COORDS: continue lat_o, lon_o = BASES_COORDS[orig] lat_d, lon_d = BASES_COORDS[dest] folium.PolyLine( locations=[[lat_o, lon_o], [lat_d, lon_d]], color=COR_NAO_CUMPRIDA, weight=1.5, opacity=0.5, dash_array="6 4", tooltip=f"NÃO CUMPRIDA | {orig}→{dest} | OM {r['om']} | prio {r['prioridade']}", ).add_to(grupos["nao_cumprida"]) # Adiciona grupos ao mapa for g in grupos.values(): g.add_to(mapa) adicionar_bases(mapa) folium.LayerControl(collapsed=False).add_to(mapa) # Legenda HTML legenda_html = """
Esquadrão Arara — OAMRP v3

Aeronaves:
""" for k, cor in CORES_AERONAVE.items(): legenda_html += ( f' ' f'EVAM {k}
' ) legenda_html += """
Prioridade (preenchimento):
Prio 1 (urgente)
Prio 2
Prio 3
Prio 4–5

Não cumprida
""" mapa.get_root().html.add_child(folium.Element(legenda_html)) saida.parent.mkdir(parents=True, exist_ok=True) mapa.save(str(saida)) print(f" Mapa salvo: {saida}") _resumir_resultado(linhas) def _resumir_resultado(linhas: list[dict]) -> None: total = len(linhas) cumpridas = sum(1 for r in linhas if r["status"] == "cumprida") print(f" Missões: {cumpridas}/{total} cumpridas") aeronaves = {} for r in linhas: if r["aeronave"]: aeronaves.setdefault(r["aeronave"], 0) aeronaves[r["aeronave"]] += 1 for k, c in sorted(aeronaves.items()): print(f" EVAM {k}: {c} missões") def main() -> None: parser = argparse.ArgumentParser(description="Visualização OAMRP — Arara C-105") parser.add_argument("--resultado", type=Path, default=RESULTADO_PADRAO) parser.add_argument("--saida", type=Path, default=MAPA_PADRAO) args = parser.parse_args() gerar_mapa(resultado=args.resultado, saida=args.saida) if __name__ == "__main__": main()