Spaces:
Sleeping
Sleeping
| # -*- coding: utf-8 -*- | |
| import gradio as gr | |
| import pandas as pd | |
| import geopandas as gpd | |
| from shapely.geometry import Point # Importar Point para a conversão | |
| import folium | |
| from folium.plugins import MeasureControl | |
| import html | |
| from rapidfuzz import process | |
| # --- CARREGAMENTO E PRÉ-PROCESSAMENTO DE DADOS --- | |
| try: | |
| # Carrega os eixos (ruas) e projeta para o sistema de coordenadas padrão (WGS84) | |
| gdf_eixos = gpd.read_file("Eixos.shp", engine="fiona") | |
| gdf_eixos_proj = gdf_eixos.to_crs("EPSG:4326") | |
| print("Shapefile de Eixos carregado com sucesso.") | |
| # 1. Criar o dicionário de mapeamento unificado: NOME -> CDLOG | |
| mapa_nome_para_cdlog = {} | |
| df_mapeamento = gdf_eixos_proj[['NMIDELOG', 'NMIDEABR', 'CDLOG']].dropna(subset=['CDLOG']) | |
| for _, row in df_mapeamento.iterrows(): | |
| cdlog = row['CDLOG'] | |
| if pd.notna(row['NMIDELOG']): | |
| mapa_nome_para_cdlog[row['NMIDELOG']] = cdlog | |
| if pd.notna(row['NMIDEABR']): | |
| mapa_nome_para_cdlog[row['NMIDEABR']] = cdlog | |
| # 2. Criar a lista de busca unificada com todos os nomes únicos | |
| lista_nomes_ruas_oficiais = list(mapa_nome_para_cdlog.keys()) | |
| print(f"{len(lista_nomes_ruas_oficiais)} nomes de ruas únicos (de NMIDELOG e NMIDEABR) pré-processados para busca.") | |
| except Exception as e: | |
| print(f"ERRO CRÍTICO: Não foi possível carregar ou processar o Shapefile 'Eixos.shp'.") | |
| print(f"Verifique se o caminho de rede está correto e acessível.") | |
| print(f"Erro original: {e}") | |
| exit() | |
| # --- FUNÇÕES DE PROCESSAMENTO (BACK-END) --- | |
| def encontrar_melhor_correspondencia(nome_rua_usuario, score_minimo=85): | |
| """ | |
| Encontra a melhor correspondência para um nome de rua na lista oficial. | |
| A comparação é feita em CAIXA ALTA para ser insensível ao caso. | |
| """ | |
| if not nome_rua_usuario or pd.isna(nome_rua_usuario): | |
| return None, None, 0 | |
| nome_rua_upper = str(nome_rua_usuario).upper() | |
| melhor_match = process.extractOne(nome_rua_upper, lista_nomes_ruas_oficiais) | |
| if melhor_match and melhor_match[1] >= score_minimo: | |
| nome_encontrado = melhor_match[0] | |
| score = melhor_match[1] | |
| cdlog_encontrado = mapa_nome_para_cdlog[nome_encontrado] | |
| return int(cdlog_encontrado), nome_encontrado, score | |
| return None, None, melhor_match[1] if melhor_match else 0 | |
| def geocodificar_lote_por_nome(df, coluna_nome_rua, coluna_num): | |
| """ | |
| Função principal que processa o DataFrame inteiro. | |
| """ | |
| if df is None: | |
| return None, None, None | |
| resultados_finais = [] | |
| falhas_geocodificacao = [] | |
| for index, row in df.iterrows(): | |
| nome_rua_original = row[coluna_nome_rua] | |
| numero = pd.to_numeric(row[coluna_num], errors='coerce') | |
| dados_linha = row.to_dict() | |
| cdlog, nome_correspondido, score = encontrar_melhor_correspondencia(nome_rua_original) | |
| dados_linha['CDLOG_ENCONTRADO'] = cdlog | |
| dados_linha['NM_CORRESPONDIDO'] = nome_correspondido | |
| dados_linha['SCORE_SIMILARIDADE'] = score | |
| if not cdlog: | |
| falhas_geocodificacao.append({**dados_linha, 'MOTIVO_FALHA': 'Nome da rua não encontrado com similaridade aceitável'}) | |
| resultados_finais.append({**dados_linha, 'x': None, 'y': None}) | |
| continue | |
| if pd.isna(numero): | |
| falhas_geocodificacao.append({**dados_linha, 'MOTIVO_FALHA': 'Número do imóvel inválido ou vazio'}) | |
| resultados_finais.append({**dados_linha, 'x': None, 'y': None}) | |
| continue | |
| numero = int(numero) | |
| segmentos = gdf_eixos_proj[gdf_eixos_proj['CDLOG'] == cdlog] | |
| cond = (segmentos['NRPARINI'] <= numero) & (segmentos['NRPARFIN'] >= numero) if numero % 2 == 0 else (segmentos['NRIMPINI'] <= numero) & (segmentos['NRIMPFIN'] >= numero) | |
| segmento_valido = segmentos[cond] | |
| if segmento_valido.empty: | |
| falhas_geocodificacao.append({**dados_linha, 'MOTIVO_FALHA': 'Número do imóvel fora do intervalo da rua encontrada'}) | |
| resultados_finais.append({**dados_linha, 'x': None, 'y': None}) | |
| continue | |
| linha_eixo = segmento_valido.iloc[0] | |
| geom = linha_eixo.geometry | |
| ini, fim = (linha_eixo['NRPARINI'], linha_eixo['NRPARFIN']) if numero % 2 == 0 else (linha_eixo['NRIMPINI'], linha_eixo['NRIMPFIN']) | |
| if pd.isna(ini) or pd.isna(fim) or fim == ini: frac = 0.5 | |
| else: frac = (numero - ini) / (fim - ini) | |
| frac = max(0, min(1, frac)) | |
| ponto = geom.interpolate(geom.length * frac) | |
| dados_linha['x'] = ponto.x | |
| dados_linha['y'] = ponto.y | |
| resultados_finais.append(dados_linha) | |
| df_resultado = pd.DataFrame(resultados_finais) | |
| df_falhas = pd.DataFrame(falhas_geocodificacao) | |
| output_path = "dados_geocodificados.xlsx" | |
| df_resultado.to_excel(output_path, index=False) | |
| return df_resultado, df_falhas, output_path | |
| # --- FUNÇÃO DO MAPA CORRIGIDA --- | |
| def gerar_mapa_interpolado(df_interpolado): | |
| """Gera o mapa HTML a partir do DataFrame com coordenadas.""" | |
| if df_interpolado is None: | |
| return "<div>Mapa não gerado. Processe os dados primeiro.</div>" | |
| df_valido = df_interpolado.dropna(subset=['x', 'y']).copy() | |
| if df_valido.empty: | |
| return "<div>Nenhum ponto válido para exibir no mapa.</div>" | |
| # --- INÍCIO DA CORREÇÃO --- | |
| # 1. Converter o DataFrame do pandas para um GeoDataFrame do geopandas | |
| gdf_valido = gpd.GeoDataFrame( | |
| df_valido, | |
| geometry=gpd.points_from_xy(df_valido.x, df_valido.y), | |
| crs="EPSG:4326" # Definir o sistema de coordenadas (importante!) | |
| ) | |
| # 2. Calcular o centro do mapa de forma eficiente | |
| # Pega os limites geográficos de todos os pontos (min_x, min_y, max_x, max_y) | |
| bounds = gdf_valido.total_bounds | |
| center_y = (bounds[1] + bounds[3]) / 2 | |
| center_x = (bounds[0] + bounds[2]) / 2 | |
| m = folium.Map(location=[center_y, center_x], zoom_start=13, tiles='CartoDB positron') | |
| # --- FIM DA CORREÇÃO --- | |
| MeasureControl(primary_length_unit='meters', secondary_length_unit='kilometers').add_to(m) | |
| # O resto da função permanece igual | |
| for _, row in gdf_valido.iterrows(): | |
| conteudo_popup = "<br>".join([f"<b>{col}</b>: {row[col]}" for col in gdf_valido.columns if col != 'geometry']) | |
| folium.CircleMarker( | |
| location=[row['y'], row['x']], | |
| radius=5, color='#007BFF', fill=True, fill_color='#007BFF', | |
| fill_opacity=0.8, popup=folium.Popup(conteudo_popup, max_width=350) | |
| ).add_to(m) | |
| mapa_html = m.get_root().render() | |
| mapa_html_escapado = html.escape(mapa_html) | |
| return f"<iframe srcdoc=\"{mapa_html_escapado}\" width='100%' height='1000px' style='border:none;'></iframe>" | |
| # --- Funções de Interface (Gradio) --- | |
| def carregar_abas(arquivo_excel): | |
| if arquivo_excel is None: return gr.update(choices=[]), None | |
| xls = pd.ExcelFile(arquivo_excel.name) | |
| abas = xls.sheet_names | |
| return gr.update(choices=abas, value=abas[0] if abas else None), abas[0] if abas else None | |
| def listar_colunas(arquivo_excel, aba_selecionada): | |
| if arquivo_excel is None or aba_selecionada is None: | |
| return gr.update(choices=[]), gr.update(choices=[]), None, "", None | |
| df = pd.read_excel(arquivo_excel.name, sheet_name=aba_selecionada) | |
| colunas = df.columns.tolist() | |
| return (gr.update(choices=colunas), gr.update(choices=colunas), df, | |
| f"O DataFrame possui {df.shape[0]} linhas e {df.shape[1]} colunas.", df) | |
| def reset_app(): | |
| return None, gr.update(choices=[], value=None), "", None, None, None, None, None, None, None | |
| # --- INTERFACE GRÁFICA (GRADIO) --- | |
| with gr.Blocks(theme=gr.themes.Soft(), title="Geocodificador Simplificado") as demo: | |
| estado_df = gr.State() | |
| gr.Markdown("# Geocodificador de Endereços") | |
| gr.Markdown("Faça o upload de uma planilha Excel, selecione as colunas com **NOME da rua** e **NÚMERO**, e o aplicativo encontrará as coordenadas.") | |
| with gr.Tabs(): | |
| with gr.TabItem("Processo de Geocodificação"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| arquivo = gr.File(label="1. Selecione um arquivo Excel", file_types=[".xlsx"]) | |
| dropdown_abas = gr.Dropdown(label="2. Selecione uma aba da planilha", interactive=True) | |
| dropdown_nome_rua = gr.Dropdown(label="3. Selecione a coluna com o NOME da rua", interactive=True) | |
| dropdown_num = gr.Dropdown(label="4. Selecione a coluna com o NÚMERO do imóvel", interactive=True) | |
| btn_processar = gr.Button("5. Geocodificar Endereços", variant="primary") | |
| with gr.Column(scale=3): | |
| linhas_colunas_output = gr.Textbox(label="Dimensões da Tabela", interactive=False) | |
| tabela_output = gr.Dataframe(label="Pré-visualização dos Dados") | |
| gr.Markdown("---") | |
| gr.Markdown("### Resultados") | |
| with gr.Row(): | |
| tabela_interpolada = gr.Dataframe(label="Dados com Coordenadas e Verificação de Endereço", interactive=False) | |
| falhas_output = gr.Dataframe(label="Relatório de Endereços Não Encontrados", interactive=False) | |
| arquivo_excel_output = gr.File(label="Baixar resultado completo como Excel", interactive=False) | |
| with gr.TabItem("Mapa de Resultados"): | |
| mapa_html = gr.HTML(label="Visualização Geográfica dos Dados") | |
| btn_clear = gr.Button("Limpar Tudo e Recomeçar", variant="stop") | |
| # --- LÓGICA DE EVENTOS DA INTERFACE --- | |
| arquivo.change(fn=carregar_abas, inputs=arquivo, outputs=[dropdown_abas, dropdown_abas]) | |
| dropdown_abas.change( | |
| fn=listar_colunas, | |
| inputs=[arquivo, dropdown_abas], | |
| outputs=[dropdown_nome_rua, dropdown_num, estado_df, linhas_colunas_output, tabela_output] | |
| ) | |
| btn_processar.click( | |
| fn=geocodificar_lote_por_nome, | |
| inputs=[estado_df, dropdown_nome_rua, dropdown_num], | |
| outputs=[tabela_interpolada, falhas_output, arquivo_excel_output] | |
| ).then( | |
| fn=gerar_mapa_interpolado, | |
| inputs=tabela_interpolada, | |
| outputs=mapa_html | |
| ) | |
| btn_clear.click(fn=reset_app, inputs=None, outputs=[ | |
| arquivo, dropdown_abas, linhas_colunas_output, dropdown_nome_rua, dropdown_num, | |
| tabela_output, tabela_interpolada, falhas_output, arquivo_excel_output, mapa_html | |
| ]) | |
| # --- INICIAR A APLICAÇÃO --- | |
| if __name__ == "__main__": | |
| demo.launch(debug=True) |