ReciclaMapa: um mapa de coleta que não envelhece, apoiado em OpenStreetMap
Consulta ao Overpass com três espelhos e failover, cache por área visível, trinta e uma traduções de tag e um painel de impacto com constantes declaradas. A arquitetura completa do projeto.

O ReciclaMapa responde uma pergunta banal que quase nenhuma cidade brasileira responde bem: onde eu descarto isto? Ele mostra os pontos de coleta próximos, o que cada um aceita e converte o descarte registrado em métricas de impacto.
O projeto recebeu menção honrosa no SINPETE da UFAL em 2024. Eu fui o responsável pelo desenvolvimento; o trabalho foi apresentado por Júlia Raíssa, Pedro Miguel e Hexley Felipe, do Colégio Santíssima. O crédito da banca é deles, e está certo que seja.
Este texto descreve a plataforma e, principalmente, a decisão técnica que definiu se ela ia sobreviver ao semestre em que foi feita.
O destino padrão de todo mapa de coleta
O caminho óbvio para resolver o problema é levantar os pontos de coleta, cadastrar em um banco e exibir num mapa. Funciona no dia da apresentação. Seis meses depois um ponto fechou, dois mudaram de endereço e um novo abriu, e a base virou desinformação bem apresentada.
Todo mapa mantido manualmente por uma equipe pequena morre por desatualização. É o resultado padrão, não a exceção, e para um projeto escolar feito por estudantes que vão se formar e seguir a vida, o prazo de validade é ainda mais curto.
A alternativa: não ser dono do dado
O ReciclaMapa apoia os pontos de coleta na base do OpenStreetMap, mantida pela comunidade. A troca é direta:
| Base própria | Base comunitária |
|---|---|
| Controle total do esquema | Esquema definido por convenção externa |
| Cobertura só do que a equipe levantou | Cobertura de quem já mapeou a região |
| Manutenção é responsabilidade da equipe | Manutenção é distribuída |
| Envelhece sozinha | Se atualiza sem intervenção do projeto |
Abre-se mão de controle e ganha-se longevidade. Existe um custo real: se ninguém mapeou os pontos de coleta de uma cidade no OSM, o mapa fica vazio ali. A resposta a isso não é abandonar a base, é contribuir com ela, e aí o benefício sai do projeto e vai para todo mundo que usa OSM.
O que a base comunitária exige do código
Não ser dono do dado transfere manutenção, mas cria três obrigações novas. Elas são a maior parte do código do projeto.
Traduzir a convenção alheia
O OSM guarda material como tag booleana: recycling:glass=yes, recycling:cooking_oil=yes, recycling:car_batteries=yes. A consulta pede tudo marcado como amenity=recycling dentro da área visível, e o app varre as tags de cada elemento, fica só com as que valem yes e converte cada chave em rótulo em português.
São trinta e uma traduções mantidas à mão, cobrindo de vidro e papelão a óleo de motor, pneus, lâmpadas, baterias automotivas e resíduos verdes. Para o que ainda não foi mapeado há uma saída genérica que troca sublinhado por espaço e capitaliza, porque a convenção do OSM cresce e o app não pode ficar mudo diante de uma tag nova.
O mesmo vale para o tipo do ponto: contêiner de coleta, centro de reciclagem e ponto de descarte têm rótulo próprio, e o resto passa pela mesma normalização. O nome exibido tenta name:pt, depois name, depois o operador, e só então cai num rótulo padrão.
Não depender de um servidor só
A consulta vai para o Overpass, que é infraestrutura comunitária mantida por voluntários e cai. São três espelhos, tentados em ordem, com dez segundos de teto por tentativa do lado do cliente e vinte declarados na própria consulta:
const ordered = [...ENDPOINTS.slice(preferred), ...ENDPOINTS.slice(0, preferred)]
for (const endpoint of ordered) {
try {
const points = await requestEndpoint(endpoint, query, signal)
preferred = ENDPOINTS.indexOf(endpoint)
return points
} catch (error) {
if (signal.aborted) throw error
lastError = error
}
}O detalhe que importa é o preferred. O espelho que respondeu vira o primeiro da próxima tentativa, então uma queda prolongada custa uma rodada de tentativas, não uma por consulta. E o cancelamento é propagado: se o usuário arrastar o mapa no meio de uma busca, o AbortSignal interrompe a requisição em vez de deixá-la terminar para um enquadramento que já não existe.
Não consultar o que já foi consultado
Arrastar o mapa dispara uma requisição por movimento. A chave de cache é a área visível arredondada mais o nível de zoom, então micromovimentos caem na mesma chave e não geram tráfego. Ficam vinte e quatro áreas em memória, e a mais antiga sai quando entra a vigésima quinta.
Há ainda um teto de dois mil resultados por consulta, declarado na própria linguagem do Overpass. Sem ele, um enquadramento muito aberto pede uma região inteira e devolve um volume que o navegador não desenha e o servidor comunitário não deveria ter gastado.
Feito para o bolso, não para a mesa
Reciclagem é decidida em pé, com a sacola na mão, na calçada. O uso primário nunca ia ser desktop, e isso puxou três exigências.
Um toque até a resposta. A pergunta é sempre a mesma, então o app não pede configuração antes de responder: abre no mapa, com os pontos da região visível.
Tolerância a rede ruim. O momento da consulta e o momento de boa conexão raramente coincidem, então o app registra um service worker e a casca continua abrindo offline.
Legibilidade com muitos pontos. Em zoom aberto o mapa acumula centenas de marcadores sobrepostos, o que vira mancha em vez de informação. Agrupá-los por proximidade foi o que manteve a tela utilizável no celular sem tirar nenhum ponto do resultado.
O que o mapa mostra quando você toca num ponto
O ponto de coleta não é só um alfinete. Cada um traz o nome, o tipo, quem opera, o horário de funcionamento quando o OSM tem essa informação, e a lista de materiais aceitos já traduzida.
Isso é o que separa “existe um ponto ali” de “vale a pena ir até lá”. Quem está com uma garrafa de óleo de cozinha na mão não quer saber onde há reciclagem em geral: quer saber onde há reciclagem que aceita óleo de cozinha, e se está aberto agora.
A montagem desse detalhe precisa tolerar dado incompleto, porque base comunitária é irregular por natureza. O nome cai em cascata de name:pt para name, depois para o operador, e só então para um rótulo padrão. Horário e operador são opcionais e simplesmente não aparecem quando faltam, em vez de exibirem um campo vazio. Elemento sem coordenada utilizável é descartado antes de virar marcador, e a deduplicação por identificador impede que o mesmo ponto apareça duas vezes quando a consulta pega nó e área do mesmo lugar.
A consulta pede out center, o que faz o Overpass devolver o centroide de áreas mapeadas como polígono. Sem isso, um ecoponto desenhado como área em vez de ponto simplesmente não apareceria no mapa, e essa é uma diferença de convenção que varia de cidade para cidade.
O painel de impacto, com as constantes à vista
É tentador exibir “X toneladas evitadas” com um cálculo frouxo por trás. O painel mostra pontos de reciclagem, total reciclado, materiais e CO₂ evitado, e o critério adotado foi declarar as constantes em vez de escondê-las: uma média de quilos por ponto e um fator de conversão de quilo para CO₂ equivalente, ambos no código, ambos ajustáveis.
Isso muda o que o número significa. Não é uma medição, é uma estimativa com premissa explícita, e quem quiser conferir a premissa consegue. A função do painel é dar retorno visível a um comportamento que normalmente não tem nenhum, não produzir mais um contador motivacional inflado.
As preferências de exibição ficam no navegador: unidade métrica ou imperial e formato numérico completo ou compacto, guardados em localStorage e sincronizados entre abas. São escolhas de apresentação, não dados de usuário, e por isso não precisam de conta nem de servidor.
A pilha
| Camada | Tecnologia | Papel |
|---|---|---|
| Aplicação | Next.js 16 e React 19 | Páginas e renderização |
| Mapa | Leaflet e Leaflet MarkerCluster | Mapa e agrupamento de marcadores |
| Dados | Overpass API sobre OpenStreetMap | Pontos de coleta |
| Offline | Service worker | Casca da aplicação sem rede |
| Estado local | localStorage com useSyncExternalStore |
Preferências entre abas |
| Estilo | Tailwind CSS 4 | Interface responsiva |
Não há banco de dados nem autenticação. O projeto inteiro é uma camada de leitura sobre uma base pública, e essa é a razão de ele continuar funcionando sem ninguém cuidando dele.
Próximos passos
- Contribuir de volta ao OSM. O caminho natural é permitir que o usuário aponte um ponto ausente e que isso vire uma sugestão de edição na base comunitária, em vez de um cadastro paralelo.
- Filtro por material antes da consulta. Hoje o filtro acontece depois que os pontos chegam; empurrá-lo para a consulta reduziria o tráfego em regiões densas.
- Cobertura declarada. Deixar explícito quando uma região tem poucos pontos por falta de mapeamento, e não por falta de coleta, é informação honesta que o mapa hoje não dá.
Onde está
O ReciclaMapa está em reciclamapa.vercel.app.
É o mais simples dos projetos que já entreguei, e é o que carrega a lição mais transferível: a decisão que mais afeta a vida útil de um sistema costuma ser sobre de onde vem o dado, não sobre como ele é exibido. Escolher OSM não deixou o projeto mais bonito nem mais rápido de construir. Deixou-o vivo depois que a equipe original seguiu adiante.
Continue lendo
- 9 min
Mundaú Sentinela: monitoramento hidrológico cívico da bacia por dentro
Nove pontos monitorados, previsão de 48 horas a partir do GloFAS, relatos cidadãos moderados, memória de cheias desde 1988 e projeção climática até 2050. A arquitetura completa da plataforma. - 15 min
Minha evolução: de scripts em Lua a aplicativos de desktop em Rust
O caminho de 2017 até hoje, tecnologia por tecnologia e decisão por decisão: HTML, EJS, Next.js, Electron, Tauri, Capacitor, os bancos que uso e por quê, e o papel exato que a IA ocupa no meu trabalho. - 14 min
jStudio: aplicativo de desktop em Tauri e Rust para o Roblox Studio
Ponte local com o plugin do Studio, agente com onze ferramentas e revisão obrigatória, cliente MCP embutido e um pipeline que reenvia animação, áudio, imagem e mesh pela Open Cloud.