Repetidoras no MMAR - Radioamador
Foi introduzido no MMAR - Radioamador a possibilidade de consultar repetidoras a partir da localização geográfica, bastando a gente digitar uma coordenada e o raio de busca. O único ponto de atenção é que apesar de muito funcional e rápido, algumas repetidoras devem estar com o cadastro errado, até porque tem coordenadas no meio do Atlântico Norte.
Para consultar basta clicar em Consultar Repetidora na aba Opções e inserir a coordenada geografica. Dica: você pode copiar diretamente do Google Maps e ao colar ele quebrará automaticamente entre latitude e longitude:

Plotando em um mapa as localizações
Ver a localização geográfica já é muito útil, mas é muito mais interessante visualizar os pontos em um mapa. Para isso é possível utilizar um script para renderizar no mapa a localização e o resultado você pode ver abaixo:
Abrir mapa de repetidoras em tela cheia
O script de geração deste mapa, baseado no arquivo CSV que pode ser recuperado na consulta do MMAR - Radioamador, é este logo abaixo e para executa-lo basta rodar o comando uv run plot.py no mesmo diretório onde estiver o CSV com o nome repetidoras.csv:
# /// script
# dependencies = [
# "pandas",
# "folium",
# "datetime"
# ]
# ///
import pandas as pd
import folium
from folium.plugins import MarkerCluster, Search
from datetime import datetime
# 1. Carregar dados
df = pd.read_csv('repetidoras.csv', sep=';')
# 2. Criar mapa (sem tiles padrão: as camadas são adicionadas abaixo)
mapa = folium.Map(
location=[-15.7801, -47.9292],
zoom_start=4,
max_zoom=13,
tiles=None
)
# 2.1 Camadas de fundo (seletor de mapas para visualizar estados/cidades)
# Voyager é o padrão: mostra nomes de cidades e vias principais mantendo leveza.
folium.TileLayer('CartoDB voyager', name='Voyager (cidades e vias)').add_to(mapa)
folium.TileLayer('OpenStreetMap', name='OpenStreetMap (ruas detalhadas)').add_to(mapa)
folium.TileLayer('CartoDB positron', name='Claro (CartoDB Positron)').add_to(mapa)
folium.TileLayer(
tiles='https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
attr='Esri, Maxar, Earthstar Geographics',
name='Satélite (Esri)'
).add_to(mapa)
# 2.2 Sobreposição de rótulos (nomes de cidades e vias) — útil sobre o satélite
folium.TileLayer(
tiles='https://{s}.basemaps.cartocdn.com/rastertiles/voyager_only_labels/{z}/{x}/{y}{r}.png',
attr='© OpenStreetMap, © CARTO',
name='Rótulos (cidades e vias)',
overlay=True,
control=True,
show=False
).add_to(mapa)
# 3. Adicionar a data de referência (Texto flutuante)
data_hoje = datetime.now().strftime("%d/%m/%Y")
texto_data = f"""
<div style="position: fixed;
bottom: 50px; left: 50px; width: 200px; height: 30px;
background-color: white; border:2px solid grey; z-index:9999; font-size:14px;
padding: 5px; text-align: center;"> Gerado em: {data_hoje}
</div>
"""
mapa.get_root().html.add_child(folium.Element(texto_data))
# 3.1 Deixar as bolhas de agrupamento semitransparentes (ver cidades por baixo)
css_clusters = """
<style>
.marker-cluster { background-clip: padding-box; }
.marker-cluster-small { background-color: rgba(110, 204, 57, 0.45); }
.marker-cluster-small div { background-color: rgba(110, 204, 57, 0.75); }
.marker-cluster-medium { background-color: rgba(240, 194, 12, 0.45); }
.marker-cluster-medium div { background-color: rgba(240, 194, 12, 0.75); }
.marker-cluster-large { background-color: rgba(241, 128, 23, 0.45); }
.marker-cluster-large div { background-color: rgba(241, 128, 23, 0.75); }
/* Responsivo: compacta busca e seletor de camadas em telas pequenas/verticais */
@media (max-width: 600px) {
.leaflet-control-search .search-input { width: 130px; font-size: 12px; }
.leaflet-control-search { margin-right: 6px !important; }
.leaflet-control-layers-expanded {
max-width: 150px;
max-height: 40vh;
overflow-y: auto;
font-size: 12px;
padding: 6px 8px;
line-height: 1.3;
}
.leaflet-control-layers label { margin-bottom: 2px; }
.leaflet-top.leaflet-right { max-width: 55vw; }
}
</style>
"""
mapa.get_root().html.add_child(folium.Element(css_clusters))
# 4. Cluster de marcadores: abre os marcadores sobrepostos (mesmo endereço) ao clicar
marker_cluster = MarkerCluster(
name="Repetidoras",
options={
# No zoom máximo o clique "abre" (spiderfy) os marcadores empilhados
"spiderfyOnMaxZoom": True,
"zoomToBoundsOnClick": True,
# Distância maior entre as "pernas" do spiderfy facilita separar repetidoras
"spiderfyDistanceMultiplier": 2,
# Agrupa apenas marcadores bem próximos, reduzindo aglomeração
"maxClusterRadius": 40,
},
).add_to(mapa)
# 4.1 Camada GeoJSON usada pela busca (propriedades são pesquisáveis no JS)
features = []
for _, linha in df.iterrows():
info = f"<b>{linha['Indicativo']}</b><br>Freq: {linha['Frequência de Operação']}"
# Marcador visual (dentro do cluster)
folium.Marker(
location=[linha['Latitude'], linha['Longitude']],
popup=folium.Popup(info, max_width=300),
icon=folium.Icon(color='blue', icon='tower', prefix='fa')
).add_to(marker_cluster)
# Feature para busca por indicativo
features.append({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [linha['Longitude'], linha['Latitude']],
},
"properties": {
"Indicativo": str(linha['Indicativo']),
"popup": info,
},
})
geojson_busca = folium.GeoJson(
{"type": "FeatureCollection", "features": features},
name="Busca por indicativo",
show=False, # camada apenas para busca; não polui o mapa
# Marcador invisível: o plugin Search exige a camada visível, mas não queremos
# bolinhas cobrindo o mapa. O popup continua funcionando ao localizar.
marker=folium.CircleMarker(
radius=0, opacity=0, fill=False, fill_opacity=0, stroke=False
),
popup=folium.GeoJsonPopup(fields=["popup"], labels=False),
).add_to(mapa)
# 5. Busca centraliza e dá zoom moderado, abrindo o popup
Search(
layer=geojson_busca,
search_label='Indicativo',
geom_type='Point',
placeholder="Buscar Indicativo...",
collapsed=False,
search_zoom=12,
).add_to(mapa)
# 6. Controle de camadas (alternar mapas de fundo)
folium.LayerControl(collapsed=False).add_to(mapa)
mapa.save('mapa_repetidoras.html')
print(f"Mapa gerado com sucesso! Data de referência: {data_hoje}")