Spaces:
Running
Running
| from __future__ import annotations | |
| import math | |
| import numpy as np | |
| import pandas as pd | |
| JITTER_MERCADO_PASSO_METROS = 5.0 | |
| JITTER_MERCADO_MAX_RAIO_METROS = 28.0 | |
| def aplicar_jitter_dados_mercado( | |
| df_mapa: pd.DataFrame, | |
| lat_col: str, | |
| lon_col: str, | |
| lat_plot_col: str = "__mesa_lat_plot__", | |
| lon_plot_col: str = "__mesa_lon_plot__", | |
| *, | |
| precisao: int = 7, | |
| passo_metros: float = JITTER_MERCADO_PASSO_METROS, | |
| max_raio_metros: float = JITTER_MERCADO_MAX_RAIO_METROS, | |
| ) -> pd.DataFrame: | |
| """ | |
| Aplica jitter visual determinístico apenas em pontos de mercado sobrepostos. | |
| As coordenadas originais permanecem nas colunas de entrada; as coordenadas | |
| deslocadas são gravadas em lat_plot_col/lon_plot_col. | |
| """ | |
| df_plot = df_mapa.copy() | |
| df_plot[lat_plot_col] = pd.to_numeric(df_plot[lat_col], errors="coerce").astype(float) | |
| df_plot[lon_plot_col] = pd.to_numeric(df_plot[lon_col], errors="coerce").astype(float) | |
| if len(df_plot) <= 1: | |
| return df_plot | |
| chave_lat = df_plot[lat_col].round(precisao) | |
| chave_lon = df_plot[lon_col].round(precisao) | |
| grupos = df_plot.groupby([chave_lat, chave_lon], sort=False) | |
| metros_por_grau_lat = 111_320.0 | |
| lat_plot_pos = int(df_plot.columns.get_loc(lat_plot_col)) | |
| lon_plot_pos = int(df_plot.columns.get_loc(lon_plot_col)) | |
| for _, idx_labels in grupos.indices.items(): | |
| posicoes = np.asarray(idx_labels, dtype=int) | |
| if posicoes.size <= 1: | |
| continue | |
| base_lat = float(df_plot.iat[int(posicoes[0]), lat_plot_pos]) | |
| base_lon = float(df_plot.iat[int(posicoes[0]), lon_plot_pos]) | |
| if not np.isfinite(base_lat) or not np.isfinite(base_lon): | |
| continue | |
| seed_val = int((abs(base_lat) * 1_000_000.0) + (abs(base_lon) * 1_000_000.0) * 3.0) % 360 | |
| angulo_base = math.radians(seed_val) | |
| cos_lat = max(abs(math.cos(math.radians(base_lat))), 1e-6) | |
| metros_por_grau_lon = metros_por_grau_lat * cos_lat | |
| for pos, pos_idx in enumerate(posicoes): | |
| if pos == 0: | |
| continue | |
| pos_ring = pos - 1 | |
| ring = 1 | |
| while pos_ring >= (6 * ring): | |
| pos_ring -= 6 * ring | |
| ring += 1 | |
| slots_ring = max(6 * ring, 1) | |
| angulo = angulo_base + (2.0 * math.pi * (pos_ring / slots_ring)) | |
| raio_m = min(ring * passo_metros, max_raio_metros) | |
| delta_lat = (raio_m * math.sin(angulo)) / metros_por_grau_lat | |
| delta_lon = (raio_m * math.cos(angulo)) / metros_por_grau_lon | |
| df_plot.iat[int(pos_idx), lat_plot_pos] = base_lat + delta_lat | |
| df_plot.iat[int(pos_idx), lon_plot_pos] = base_lon + delta_lon | |
| return df_plot | |