Spaces:
Running
Running
Guilherme Silberfarb Costa commited on
Commit ·
440f380
1
Parent(s): 2b65468
alteracoes nos mapas
Browse files- backend/app/api/elaboracao.py +11 -1
- backend/app/api/visualizacao.py +4 -0
- backend/app/core/elaboracao/charts.py +13 -3
- backend/app/core/map_jitter.py +2 -2
- backend/app/core/map_sizing.py +33 -0
- backend/app/core/visualizacao/app.py +33 -9
- backend/app/core/visualizacao/map_payload.py +42 -11
- backend/app/services/elaboracao_service.py +40 -4
- backend/app/services/pesquisa_service.py +13 -1
- backend/app/services/trabalhos_tecnicos_service.py +26 -2
- backend/app/services/visualizacao_service.py +23 -1
- frontend/src/api.js +9 -3
- frontend/src/components/ElaboracaoTab.jsx +109 -7
- frontend/src/components/LeafletMapFrame.jsx +65 -7
- frontend/src/components/MapFrame.jsx +67 -15
- frontend/src/components/PesquisaTab.jsx +53 -1
- frontend/src/components/RepositorioTab.jsx +53 -1
- frontend/src/mapPointSizing.js +23 -0
- frontend/src/styles.css +78 -2
backend/app/api/elaboracao.py
CHANGED
|
@@ -173,6 +173,8 @@ class UpdateMapaPayload(SessionPayload):
|
|
| 173 |
variavel_mapa: str | None = None
|
| 174 |
modo_mapa: str | None = None
|
| 175 |
escala_extremo_abs: float | None = None
|
|
|
|
|
|
|
| 176 |
|
| 177 |
|
| 178 |
class ColunaDataMercadoPayload(SessionPayload):
|
|
@@ -564,7 +566,13 @@ def export_base(session_id: str, filtered: bool = True) -> FileResponse:
|
|
| 564 |
@router.post("/map/update")
|
| 565 |
def map_update(payload: UpdateMapaPayload) -> dict[str, Any]:
|
| 566 |
session = session_store.get(payload.session_id)
|
| 567 |
-
return elaboracao_service.atualizar_mapa(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 568 |
|
| 569 |
|
| 570 |
@router.post("/map/popup")
|
|
@@ -581,6 +589,8 @@ def residuos_map_update(payload: UpdateMapaPayload) -> dict[str, Any]:
|
|
| 581 |
payload.variavel_mapa,
|
| 582 |
payload.modo_mapa,
|
| 583 |
payload.escala_extremo_abs,
|
|
|
|
|
|
|
| 584 |
)
|
| 585 |
|
| 586 |
|
|
|
|
| 173 |
variavel_mapa: str | None = None
|
| 174 |
modo_mapa: str | None = None
|
| 175 |
escala_extremo_abs: float | None = None
|
| 176 |
+
ponto_raio_min: float | None = None
|
| 177 |
+
ponto_raio_max: float | None = None
|
| 178 |
|
| 179 |
|
| 180 |
class ColunaDataMercadoPayload(SessionPayload):
|
|
|
|
| 566 |
@router.post("/map/update")
|
| 567 |
def map_update(payload: UpdateMapaPayload) -> dict[str, Any]:
|
| 568 |
session = session_store.get(payload.session_id)
|
| 569 |
+
return elaboracao_service.atualizar_mapa(
|
| 570 |
+
session,
|
| 571 |
+
payload.variavel_mapa,
|
| 572 |
+
payload.modo_mapa,
|
| 573 |
+
ponto_raio_min=payload.ponto_raio_min,
|
| 574 |
+
ponto_raio_max=payload.ponto_raio_max,
|
| 575 |
+
)
|
| 576 |
|
| 577 |
|
| 578 |
@router.post("/map/popup")
|
|
|
|
| 589 |
payload.variavel_mapa,
|
| 590 |
payload.modo_mapa,
|
| 591 |
payload.escala_extremo_abs,
|
| 592 |
+
ponto_raio_min=payload.ponto_raio_min,
|
| 593 |
+
ponto_raio_max=payload.ponto_raio_max,
|
| 594 |
)
|
| 595 |
|
| 596 |
|
backend/app/api/visualizacao.py
CHANGED
|
@@ -27,6 +27,8 @@ class ExibirPayload(SessionPayload):
|
|
| 27 |
class MapaPayload(SessionPayload):
|
| 28 |
variavel_mapa: str | None = None
|
| 29 |
trabalhos_tecnicos_modelos_modo: str | None = None
|
|
|
|
|
|
|
| 30 |
|
| 31 |
|
| 32 |
class SecaoPayload(SessionPayload):
|
|
@@ -146,6 +148,8 @@ def map_update(payload: MapaPayload, request: Request) -> dict[str, Any]:
|
|
| 146 |
session,
|
| 147 |
payload.variavel_mapa,
|
| 148 |
trabalhos_tecnicos_modelos_modo=payload.trabalhos_tecnicos_modelos_modo,
|
|
|
|
|
|
|
| 149 |
api_base_url=str(request.base_url).rstrip("/"),
|
| 150 |
popup_auth_token=getattr(request.state, "auth_token", None),
|
| 151 |
)
|
|
|
|
| 27 |
class MapaPayload(SessionPayload):
|
| 28 |
variavel_mapa: str | None = None
|
| 29 |
trabalhos_tecnicos_modelos_modo: str | None = None
|
| 30 |
+
ponto_raio_min: float | None = None
|
| 31 |
+
ponto_raio_max: float | None = None
|
| 32 |
|
| 33 |
|
| 34 |
class SecaoPayload(SessionPayload):
|
|
|
|
| 148 |
session,
|
| 149 |
payload.variavel_mapa,
|
| 150 |
trabalhos_tecnicos_modelos_modo=payload.trabalhos_tecnicos_modelos_modo,
|
| 151 |
+
ponto_raio_min=payload.ponto_raio_min,
|
| 152 |
+
ponto_raio_max=payload.ponto_raio_max,
|
| 153 |
api_base_url=str(request.base_url).rstrip("/"),
|
| 154 |
popup_auth_token=getattr(request.state, "auth_token", None),
|
| 155 |
)
|
backend/app/core/elaboracao/charts.py
CHANGED
|
@@ -24,6 +24,7 @@ from app.core.map_layers import (
|
|
| 24 |
add_popup_pagination_handlers,
|
| 25 |
add_zoom_responsive_circle_markers,
|
| 26 |
)
|
|
|
|
| 27 |
|
| 28 |
# ============================================================
|
| 29 |
# CONSTANTES DE ESTILO
|
|
@@ -963,6 +964,8 @@ def criar_mapa(
|
|
| 963 |
cor_stops: list[float] | None = None,
|
| 964 |
cor_tick_values: list[float] | None = None,
|
| 965 |
cor_tick_labels: list[str] | None = None,
|
|
|
|
|
|
|
| 966 |
):
|
| 967 |
"""
|
| 968 |
Cria mapa Folium com os dados.
|
|
@@ -1087,7 +1090,7 @@ def criar_mapa(
|
|
| 1087 |
colormap.add_to(m)
|
| 1088 |
|
| 1089 |
# Escala de tamanho proporcional
|
| 1090 |
-
raio_min, raio_max =
|
| 1091 |
tamanho_func = None
|
| 1092 |
if tamanho_col and tamanho_col in df_mapa.columns:
|
| 1093 |
t_min = df_mapa[tamanho_col].min()
|
|
@@ -1102,7 +1105,8 @@ def criar_mapa(
|
|
| 1102 |
camada_indices = folium.FeatureGroup(name="Índices", show=False) if mostrar_indices else None
|
| 1103 |
lat_plot_col = "__mesa_lat_plot__"
|
| 1104 |
lon_plot_col = "__mesa_lon_plot__"
|
| 1105 |
-
|
|
|
|
| 1106 |
df_plot_pontos = _aplicar_jitter_sobrepostos(
|
| 1107 |
df_mapa,
|
| 1108 |
lat_col=lat_real,
|
|
@@ -1307,13 +1311,19 @@ def criar_mapa(
|
|
| 1307 |
|
| 1308 |
# Controles
|
| 1309 |
folium.LayerControl().add_to(m)
|
| 1310 |
-
plugins.Fullscreen().add_to(m)
|
| 1311 |
plugins.MeasureControl(
|
|
|
|
| 1312 |
primary_length_unit='meters',
|
| 1313 |
secondary_length_unit='kilometers',
|
| 1314 |
primary_area_unit='sqmeters',
|
| 1315 |
secondary_area_unit='hectares'
|
| 1316 |
).add_to(m)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1317 |
add_zoom_responsive_circle_markers(m)
|
| 1318 |
add_popup_pagination_handlers(m)
|
| 1319 |
|
|
|
|
| 24 |
add_popup_pagination_handlers,
|
| 25 |
add_zoom_responsive_circle_markers,
|
| 26 |
)
|
| 27 |
+
from app.core.map_sizing import normalizar_faixa_raio_pontos
|
| 28 |
|
| 29 |
# ============================================================
|
| 30 |
# CONSTANTES DE ESTILO
|
|
|
|
| 964 |
cor_stops: list[float] | None = None,
|
| 965 |
cor_tick_values: list[float] | None = None,
|
| 966 |
cor_tick_labels: list[str] | None = None,
|
| 967 |
+
ponto_raio_min: float | None = None,
|
| 968 |
+
ponto_raio_max: float | None = None,
|
| 969 |
):
|
| 970 |
"""
|
| 971 |
Cria mapa Folium com os dados.
|
|
|
|
| 1090 |
colormap.add_to(m)
|
| 1091 |
|
| 1092 |
# Escala de tamanho proporcional
|
| 1093 |
+
raio_min, raio_max = normalizar_faixa_raio_pontos(ponto_raio_min, ponto_raio_max)
|
| 1094 |
tamanho_func = None
|
| 1095 |
if tamanho_col and tamanho_col in df_mapa.columns:
|
| 1096 |
t_min = df_mapa[tamanho_col].min()
|
|
|
|
| 1105 |
camada_indices = folium.FeatureGroup(name="Índices", show=False) if mostrar_indices else None
|
| 1106 |
lat_plot_col = "__mesa_lat_plot__"
|
| 1107 |
lon_plot_col = "__mesa_lon_plot__"
|
| 1108 |
+
usar_jitter_pontos = bool(tamanho_col and tamanho_col in df_mapa.columns)
|
| 1109 |
+
if usar_jitter_pontos and not modo_calor and not modo_superficie:
|
| 1110 |
df_plot_pontos = _aplicar_jitter_sobrepostos(
|
| 1111 |
df_mapa,
|
| 1112 |
lat_col=lat_real,
|
|
|
|
| 1311 |
|
| 1312 |
# Controles
|
| 1313 |
folium.LayerControl().add_to(m)
|
|
|
|
| 1314 |
plugins.MeasureControl(
|
| 1315 |
+
position='topright',
|
| 1316 |
primary_length_unit='meters',
|
| 1317 |
secondary_length_unit='kilometers',
|
| 1318 |
primary_area_unit='sqmeters',
|
| 1319 |
secondary_area_unit='hectares'
|
| 1320 |
).add_to(m)
|
| 1321 |
+
plugins.Fullscreen(
|
| 1322 |
+
position="topright",
|
| 1323 |
+
title="Abrir mapa em tela cheia",
|
| 1324 |
+
title_cancel="Sair da tela cheia",
|
| 1325 |
+
force_separate_button=True,
|
| 1326 |
+
).add_to(m)
|
| 1327 |
add_zoom_responsive_circle_markers(m)
|
| 1328 |
add_popup_pagination_handlers(m)
|
| 1329 |
|
backend/app/core/map_jitter.py
CHANGED
|
@@ -28,8 +28,8 @@ def aplicar_jitter_dados_mercado(
|
|
| 28 |
deslocadas são gravadas em lat_plot_col/lon_plot_col.
|
| 29 |
"""
|
| 30 |
df_plot = df_mapa.copy()
|
| 31 |
-
df_plot[lat_plot_col] = pd.to_numeric(df_plot[lat_col], errors="coerce")
|
| 32 |
-
df_plot[lon_plot_col] = pd.to_numeric(df_plot[lon_col], errors="coerce")
|
| 33 |
|
| 34 |
if len(df_plot) <= 1:
|
| 35 |
return df_plot
|
|
|
|
| 28 |
deslocadas são gravadas em lat_plot_col/lon_plot_col.
|
| 29 |
"""
|
| 30 |
df_plot = df_mapa.copy()
|
| 31 |
+
df_plot[lat_plot_col] = pd.to_numeric(df_plot[lat_col], errors="coerce").astype(float)
|
| 32 |
+
df_plot[lon_plot_col] = pd.to_numeric(df_plot[lon_col], errors="coerce").astype(float)
|
| 33 |
|
| 34 |
if len(df_plot) <= 1:
|
| 35 |
return df_plot
|
backend/app/core/map_sizing.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
from typing import Any
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
PONTO_RAIO_MIN_PADRAO = 3.0
|
| 8 |
+
PONTO_RAIO_MAX_PADRAO = 18.0
|
| 9 |
+
PONTO_RAIO_MIN_LIMITE = 1.0
|
| 10 |
+
PONTO_RAIO_MAX_LIMITE = 64.0
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def _normalizar_numero(value: Any, fallback: float) -> float:
|
| 14 |
+
try:
|
| 15 |
+
number = float(value)
|
| 16 |
+
except (TypeError, ValueError):
|
| 17 |
+
return float(fallback)
|
| 18 |
+
if not math.isfinite(number):
|
| 19 |
+
return float(fallback)
|
| 20 |
+
return float(number)
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def normalizar_faixa_raio_pontos(
|
| 24 |
+
raio_min: Any = None,
|
| 25 |
+
raio_max: Any = None,
|
| 26 |
+
) -> tuple[float, float]:
|
| 27 |
+
min_norm = _normalizar_numero(raio_min, PONTO_RAIO_MIN_PADRAO)
|
| 28 |
+
max_norm = _normalizar_numero(raio_max, PONTO_RAIO_MAX_PADRAO)
|
| 29 |
+
min_norm = min(max(min_norm, PONTO_RAIO_MIN_LIMITE), PONTO_RAIO_MAX_LIMITE)
|
| 30 |
+
max_norm = min(max(max_norm, PONTO_RAIO_MIN_LIMITE), PONTO_RAIO_MAX_LIMITE)
|
| 31 |
+
if min_norm > max_norm:
|
| 32 |
+
min_norm, max_norm = max_norm, min_norm
|
| 33 |
+
return float(min_norm), float(max_norm)
|
backend/app/core/visualizacao/app.py
CHANGED
|
@@ -23,6 +23,7 @@ from app.core.map_layers import (
|
|
| 23 |
add_trabalhos_tecnicos_markers,
|
| 24 |
add_zoom_responsive_circle_markers,
|
| 25 |
)
|
|
|
|
| 26 |
|
| 27 |
|
| 28 |
COR_PRINCIPAL = "#FF8C00"
|
|
@@ -724,6 +725,8 @@ def criar_mapa(
|
|
| 724 |
popup_endpoint=None,
|
| 725 |
popup_auth_token=None,
|
| 726 |
avaliandos_tecnicos=None,
|
|
|
|
|
|
|
| 727 |
):
|
| 728 |
lat_real = None
|
| 729 |
lon_real = None
|
|
@@ -799,7 +802,7 @@ def criar_mapa(
|
|
| 799 |
)
|
| 800 |
colormap.add_to(mapa)
|
| 801 |
|
| 802 |
-
raio_min, raio_max =
|
| 803 |
tamanho_func = None
|
| 804 |
tamanho_key = None
|
| 805 |
if tamanho_col and tamanho_col != "Visualização Padrão" and tamanho_col in df_mapa.columns:
|
|
@@ -819,13 +822,23 @@ def criar_mapa(
|
|
| 819 |
camada_indices = folium.FeatureGroup(name="Índices", show=False) if mostrar_indices else None
|
| 820 |
lat_plot_key = "__mesa_lat_plot__"
|
| 821 |
lon_plot_key = "__mesa_lon_plot__"
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
|
| 826 |
-
lat_plot_col=lat_plot_key,
|
| 827 |
-
lon_plot_col=lon_plot_key,
|
| 828 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 829 |
|
| 830 |
tooltip_col = None
|
| 831 |
tooltip_key = None
|
|
@@ -916,18 +929,29 @@ def criar_mapa(
|
|
| 916 |
camada_indices.add_to(mapa)
|
| 917 |
|
| 918 |
if avaliandos_tecnicos:
|
| 919 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 920 |
add_trabalhos_tecnicos_markers(camada_trabalhos_tecnicos, avaliandos_tecnicos)
|
| 921 |
camada_trabalhos_tecnicos.add_to(mapa)
|
| 922 |
|
| 923 |
folium.LayerControl().add_to(mapa)
|
| 924 |
-
plugins.Fullscreen().add_to(mapa)
|
| 925 |
plugins.MeasureControl(
|
|
|
|
| 926 |
primary_length_unit="meters",
|
| 927 |
secondary_length_unit="kilometers",
|
| 928 |
primary_area_unit="sqmeters",
|
| 929 |
secondary_area_unit="hectares",
|
| 930 |
).add_to(mapa)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 931 |
add_zoom_responsive_circle_markers(mapa)
|
| 932 |
add_popup_pagination_handlers(mapa)
|
| 933 |
|
|
|
|
| 23 |
add_trabalhos_tecnicos_markers,
|
| 24 |
add_zoom_responsive_circle_markers,
|
| 25 |
)
|
| 26 |
+
from app.core.map_sizing import normalizar_faixa_raio_pontos
|
| 27 |
|
| 28 |
|
| 29 |
COR_PRINCIPAL = "#FF8C00"
|
|
|
|
| 725 |
popup_endpoint=None,
|
| 726 |
popup_auth_token=None,
|
| 727 |
avaliandos_tecnicos=None,
|
| 728 |
+
ponto_raio_min=None,
|
| 729 |
+
ponto_raio_max=None,
|
| 730 |
):
|
| 731 |
lat_real = None
|
| 732 |
lon_real = None
|
|
|
|
| 802 |
)
|
| 803 |
colormap.add_to(mapa)
|
| 804 |
|
| 805 |
+
raio_min, raio_max = normalizar_faixa_raio_pontos(ponto_raio_min, ponto_raio_max)
|
| 806 |
tamanho_func = None
|
| 807 |
tamanho_key = None
|
| 808 |
if tamanho_col and tamanho_col != "Visualização Padrão" and tamanho_col in df_mapa.columns:
|
|
|
|
| 822 |
camada_indices = folium.FeatureGroup(name="Índices", show=False) if mostrar_indices else None
|
| 823 |
lat_plot_key = "__mesa_lat_plot__"
|
| 824 |
lon_plot_key = "__mesa_lon_plot__"
|
| 825 |
+
usar_jitter_pontos = bool(
|
| 826 |
+
tamanho_col
|
| 827 |
+
and tamanho_col != "Visualização Padrão"
|
| 828 |
+
and tamanho_col in df_mapa.columns
|
|
|
|
|
|
|
| 829 |
)
|
| 830 |
+
if usar_jitter_pontos:
|
| 831 |
+
df_plot_pontos = _aplicar_jitter_sobrepostos(
|
| 832 |
+
df_mapa,
|
| 833 |
+
lat_col=lat_key,
|
| 834 |
+
lon_col=lon_key,
|
| 835 |
+
lat_plot_col=lat_plot_key,
|
| 836 |
+
lon_plot_col=lon_plot_key,
|
| 837 |
+
)
|
| 838 |
+
else:
|
| 839 |
+
df_plot_pontos = df_mapa.copy()
|
| 840 |
+
df_plot_pontos[lat_plot_key] = df_plot_pontos[lat_key]
|
| 841 |
+
df_plot_pontos[lon_plot_key] = df_plot_pontos[lon_key]
|
| 842 |
|
| 843 |
tooltip_col = None
|
| 844 |
tooltip_key = None
|
|
|
|
| 929 |
camada_indices.add_to(mapa)
|
| 930 |
|
| 931 |
if avaliandos_tecnicos:
|
| 932 |
+
trabalhos_tecnicos_show = not (
|
| 933 |
+
tamanho_col
|
| 934 |
+
and tamanho_col != "Visualização Padrão"
|
| 935 |
+
and tamanho_col in df_mapa.columns
|
| 936 |
+
)
|
| 937 |
+
camada_trabalhos_tecnicos = folium.FeatureGroup(name="Trabalhos tecnicos", show=trabalhos_tecnicos_show)
|
| 938 |
add_trabalhos_tecnicos_markers(camada_trabalhos_tecnicos, avaliandos_tecnicos)
|
| 939 |
camada_trabalhos_tecnicos.add_to(mapa)
|
| 940 |
|
| 941 |
folium.LayerControl().add_to(mapa)
|
|
|
|
| 942 |
plugins.MeasureControl(
|
| 943 |
+
position="topright",
|
| 944 |
primary_length_unit="meters",
|
| 945 |
secondary_length_unit="kilometers",
|
| 946 |
primary_area_unit="sqmeters",
|
| 947 |
secondary_area_unit="hectares",
|
| 948 |
).add_to(mapa)
|
| 949 |
+
plugins.Fullscreen(
|
| 950 |
+
position="topright",
|
| 951 |
+
title="Abrir mapa em tela cheia",
|
| 952 |
+
title_cancel="Sair da tela cheia",
|
| 953 |
+
force_separate_button=True,
|
| 954 |
+
).add_to(mapa)
|
| 955 |
add_zoom_responsive_circle_markers(mapa)
|
| 956 |
add_popup_pagination_handlers(mapa)
|
| 957 |
|
backend/app/core/visualizacao/map_payload.py
CHANGED
|
@@ -13,7 +13,9 @@ from app.core.elaboracao.charts import (
|
|
| 13 |
_mascara_dentro_poligono,
|
| 14 |
_normalizar_stops_cor,
|
| 15 |
)
|
|
|
|
| 16 |
from app.core.map_layers import build_trabalhos_tecnicos_marker_payloads
|
|
|
|
| 17 |
from app.core.visualizacao.app import COR_PRINCIPAL, formatar_monetario
|
| 18 |
|
| 19 |
|
|
@@ -264,6 +266,8 @@ def build_elaboracao_map_payload(
|
|
| 264 |
cor_tick_labels: list[str] | None = None,
|
| 265 |
bairros_geojson_url: str = "/api/visualizacao/map/bairros.geojson",
|
| 266 |
popup_source: str | None = "mercado",
|
|
|
|
|
|
|
| 267 |
) -> dict[str, Any] | None:
|
| 268 |
modo_normalizado = str(modo or "pontos").strip().lower()
|
| 269 |
|
|
@@ -358,7 +362,7 @@ def build_elaboracao_map_payload(
|
|
| 358 |
except (TypeError, ValueError):
|
| 359 |
pass
|
| 360 |
|
| 361 |
-
raio_min, raio_max =
|
| 362 |
tamanho_func = None
|
| 363 |
if tamanho_col and tamanho_col in df_mapa.columns:
|
| 364 |
serie_tamanho = pd.to_numeric(df_mapa[tamanho_col], errors="coerce")
|
|
@@ -376,10 +380,15 @@ def build_elaboracao_map_payload(
|
|
| 376 |
mostrar_indices = not modo_calor and not modo_superficie and len(df_mapa) <= 800
|
| 377 |
lat_plot_col = "__mesa_lat_plot__"
|
| 378 |
lon_plot_col = "__mesa_lon_plot__"
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
df_plot_pontos
|
| 382 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 383 |
else:
|
| 384 |
df_plot_pontos = df_mapa.copy()
|
| 385 |
df_plot_pontos[lat_plot_col] = df_plot_pontos[lat_real]
|
|
@@ -586,8 +595,10 @@ def build_elaboracao_map_payload(
|
|
| 586 |
else:
|
| 587 |
market_points: list[dict[str, Any]] = []
|
| 588 |
indices_markers: list[dict[str, Any]] = []
|
|
|
|
|
|
|
| 589 |
grupos_coord = df_plot_pontos.groupby(
|
| 590 |
-
[df_plot_pontos[
|
| 591 |
sort=False,
|
| 592 |
).indices
|
| 593 |
for marker_ordem, posicoes_raw in enumerate(grupos_coord.values()):
|
|
@@ -756,6 +767,8 @@ def build_visualizacao_map_payload(
|
|
| 756 |
col_y: str | None = None,
|
| 757 |
avaliandos_tecnicos: list[dict[str, Any]] | None = None,
|
| 758 |
bairros_geojson_url: str = "/api/visualizacao/map/bairros.geojson",
|
|
|
|
|
|
|
| 759 |
) -> dict[str, Any] | None:
|
| 760 |
lat_real = _detectar_coluna(df, _LAT_ALIASES)
|
| 761 |
lon_real = _detectar_coluna(df, _LON_ALIASES)
|
|
@@ -788,6 +801,7 @@ def build_visualizacao_map_payload(
|
|
| 788 |
cor_col_resolvida = cor_col
|
| 789 |
if tamanho_col and tamanho_col != "Visualização Padrão" and not cor_col_resolvida:
|
| 790 |
cor_col_resolvida = tamanho_col
|
|
|
|
| 791 |
|
| 792 |
colormap = None
|
| 793 |
cor_key = None
|
|
@@ -813,7 +827,7 @@ def build_visualizacao_map_payload(
|
|
| 813 |
"colors": ["#2ecc71", "#a8e06c", "#f1c40f", "#e67e22", "#e74c3c"],
|
| 814 |
}
|
| 815 |
|
| 816 |
-
raio_min, raio_max =
|
| 817 |
tamanho_func = None
|
| 818 |
tamanho_key = None
|
| 819 |
if tamanho_col and tamanho_col != "Visualização Padrão" and tamanho_col in df_mapa.columns:
|
|
@@ -835,9 +849,23 @@ def build_visualizacao_map_payload(
|
|
| 835 |
show_indices = False
|
| 836 |
lat_plot_key = "__mesa_lat_plot__"
|
| 837 |
lon_plot_key = "__mesa_lon_plot__"
|
| 838 |
-
|
| 839 |
-
|
| 840 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 841 |
|
| 842 |
tooltip_col = None
|
| 843 |
tooltip_key = None
|
|
@@ -856,8 +884,10 @@ def build_visualizacao_map_payload(
|
|
| 856 |
raio_padrao = 4.0 if total_pontos_plot <= 2500 else 3.0
|
| 857 |
|
| 858 |
market_points: list[dict[str, Any]] = []
|
|
|
|
|
|
|
| 859 |
grupos_coord = df_plot_pontos.groupby(
|
| 860 |
-
[df_plot_pontos[
|
| 861 |
sort=False,
|
| 862 |
).indices
|
| 863 |
for marker_ordem, posicoes_raw in enumerate(grupos_coord.values()):
|
|
@@ -969,5 +999,6 @@ def build_visualizacao_map_payload(
|
|
| 969 |
"bairros_geojson_url": bairros_geojson_url,
|
| 970 |
"legend": legend,
|
| 971 |
"market_points": market_points,
|
|
|
|
| 972 |
"trabalhos_tecnicos_points": build_trabalhos_tecnicos_marker_payloads(avaliandos_tecnicos),
|
| 973 |
}
|
|
|
|
| 13 |
_mascara_dentro_poligono,
|
| 14 |
_normalizar_stops_cor,
|
| 15 |
)
|
| 16 |
+
from app.core.map_jitter import aplicar_jitter_dados_mercado
|
| 17 |
from app.core.map_layers import build_trabalhos_tecnicos_marker_payloads
|
| 18 |
+
from app.core.map_sizing import normalizar_faixa_raio_pontos
|
| 19 |
from app.core.visualizacao.app import COR_PRINCIPAL, formatar_monetario
|
| 20 |
|
| 21 |
|
|
|
|
| 266 |
cor_tick_labels: list[str] | None = None,
|
| 267 |
bairros_geojson_url: str = "/api/visualizacao/map/bairros.geojson",
|
| 268 |
popup_source: str | None = "mercado",
|
| 269 |
+
ponto_raio_min: float | None = None,
|
| 270 |
+
ponto_raio_max: float | None = None,
|
| 271 |
) -> dict[str, Any] | None:
|
| 272 |
modo_normalizado = str(modo or "pontos").strip().lower()
|
| 273 |
|
|
|
|
| 362 |
except (TypeError, ValueError):
|
| 363 |
pass
|
| 364 |
|
| 365 |
+
raio_min, raio_max = normalizar_faixa_raio_pontos(ponto_raio_min, ponto_raio_max)
|
| 366 |
tamanho_func = None
|
| 367 |
if tamanho_col and tamanho_col in df_mapa.columns:
|
| 368 |
serie_tamanho = pd.to_numeric(df_mapa[tamanho_col], errors="coerce")
|
|
|
|
| 380 |
mostrar_indices = not modo_calor and not modo_superficie and len(df_mapa) <= 800
|
| 381 |
lat_plot_col = "__mesa_lat_plot__"
|
| 382 |
lon_plot_col = "__mesa_lon_plot__"
|
| 383 |
+
usar_jitter_pontos = bool(tamanho_col and tamanho_col in df_mapa.columns)
|
| 384 |
+
if usar_jitter_pontos and not modo_calor and not modo_superficie:
|
| 385 |
+
df_plot_pontos = aplicar_jitter_dados_mercado(
|
| 386 |
+
df_mapa,
|
| 387 |
+
lat_col=lat_real,
|
| 388 |
+
lon_col=lon_real,
|
| 389 |
+
lat_plot_col=lat_plot_col,
|
| 390 |
+
lon_plot_col=lon_plot_col,
|
| 391 |
+
)
|
| 392 |
else:
|
| 393 |
df_plot_pontos = df_mapa.copy()
|
| 394 |
df_plot_pontos[lat_plot_col] = df_plot_pontos[lat_real]
|
|
|
|
| 595 |
else:
|
| 596 |
market_points: list[dict[str, Any]] = []
|
| 597 |
indices_markers: list[dict[str, Any]] = []
|
| 598 |
+
grupo_lat_col = lat_plot_col if usar_jitter_pontos else lat_real
|
| 599 |
+
grupo_lon_col = lon_plot_col if usar_jitter_pontos else lon_real
|
| 600 |
grupos_coord = df_plot_pontos.groupby(
|
| 601 |
+
[df_plot_pontos[grupo_lat_col].round(7), df_plot_pontos[grupo_lon_col].round(7)],
|
| 602 |
sort=False,
|
| 603 |
).indices
|
| 604 |
for marker_ordem, posicoes_raw in enumerate(grupos_coord.values()):
|
|
|
|
| 767 |
col_y: str | None = None,
|
| 768 |
avaliandos_tecnicos: list[dict[str, Any]] | None = None,
|
| 769 |
bairros_geojson_url: str = "/api/visualizacao/map/bairros.geojson",
|
| 770 |
+
ponto_raio_min: float | None = None,
|
| 771 |
+
ponto_raio_max: float | None = None,
|
| 772 |
) -> dict[str, Any] | None:
|
| 773 |
lat_real = _detectar_coluna(df, _LAT_ALIASES)
|
| 774 |
lon_real = _detectar_coluna(df, _LON_ALIASES)
|
|
|
|
| 801 |
cor_col_resolvida = cor_col
|
| 802 |
if tamanho_col and tamanho_col != "Visualização Padrão" and not cor_col_resolvida:
|
| 803 |
cor_col_resolvida = tamanho_col
|
| 804 |
+
trabalhos_tecnicos_show = not (tamanho_col and tamanho_col != "Visualização Padrão")
|
| 805 |
|
| 806 |
colormap = None
|
| 807 |
cor_key = None
|
|
|
|
| 827 |
"colors": ["#2ecc71", "#a8e06c", "#f1c40f", "#e67e22", "#e74c3c"],
|
| 828 |
}
|
| 829 |
|
| 830 |
+
raio_min, raio_max = normalizar_faixa_raio_pontos(ponto_raio_min, ponto_raio_max)
|
| 831 |
tamanho_func = None
|
| 832 |
tamanho_key = None
|
| 833 |
if tamanho_col and tamanho_col != "Visualização Padrão" and tamanho_col in df_mapa.columns:
|
|
|
|
| 849 |
show_indices = False
|
| 850 |
lat_plot_key = "__mesa_lat_plot__"
|
| 851 |
lon_plot_key = "__mesa_lon_plot__"
|
| 852 |
+
usar_jitter_pontos = bool(
|
| 853 |
+
tamanho_col
|
| 854 |
+
and tamanho_col != "Visualização Padrão"
|
| 855 |
+
and tamanho_col in df_mapa.columns
|
| 856 |
+
)
|
| 857 |
+
if usar_jitter_pontos:
|
| 858 |
+
df_plot_pontos = aplicar_jitter_dados_mercado(
|
| 859 |
+
df_mapa,
|
| 860 |
+
lat_col=lat_key,
|
| 861 |
+
lon_col=lon_key,
|
| 862 |
+
lat_plot_col=lat_plot_key,
|
| 863 |
+
lon_plot_col=lon_plot_key,
|
| 864 |
+
)
|
| 865 |
+
else:
|
| 866 |
+
df_plot_pontos = df_mapa.copy()
|
| 867 |
+
df_plot_pontos[lat_plot_key] = df_plot_pontos[lat_key]
|
| 868 |
+
df_plot_pontos[lon_plot_key] = df_plot_pontos[lon_key]
|
| 869 |
|
| 870 |
tooltip_col = None
|
| 871 |
tooltip_key = None
|
|
|
|
| 884 |
raio_padrao = 4.0 if total_pontos_plot <= 2500 else 3.0
|
| 885 |
|
| 886 |
market_points: list[dict[str, Any]] = []
|
| 887 |
+
grupo_lat_key = lat_plot_key if usar_jitter_pontos else lat_key
|
| 888 |
+
grupo_lon_key = lon_plot_key if usar_jitter_pontos else lon_key
|
| 889 |
grupos_coord = df_plot_pontos.groupby(
|
| 890 |
+
[df_plot_pontos[grupo_lat_key].round(7), df_plot_pontos[grupo_lon_key].round(7)],
|
| 891 |
sort=False,
|
| 892 |
).indices
|
| 893 |
for marker_ordem, posicoes_raw in enumerate(grupos_coord.values()):
|
|
|
|
| 999 |
"bairros_geojson_url": bairros_geojson_url,
|
| 1000 |
"legend": legend,
|
| 1001 |
"market_points": market_points,
|
| 1002 |
+
"trabalhos_tecnicos_show": trabalhos_tecnicos_show,
|
| 1003 |
"trabalhos_tecnicos_points": build_trabalhos_tecnicos_marker_payloads(avaliandos_tecnicos),
|
| 1004 |
}
|
backend/app/services/elaboracao_service.py
CHANGED
|
@@ -54,6 +54,7 @@ from app.core.elaboracao.formatadores import (
|
|
| 54 |
formatar_micronumerosidade_html,
|
| 55 |
formatar_outliers_anteriores_html,
|
| 56 |
)
|
|
|
|
| 57 |
from app.core.visualizacao import app as visualizacao_app
|
| 58 |
from app.core.visualizacao.map_payload import build_elaboracao_map_payload
|
| 59 |
from app.models.session import SessionState
|
|
@@ -3134,7 +3135,14 @@ def exportar_equacao(session: SessionState, mode: str) -> tuple[str, str]:
|
|
| 3134 |
return caminho, nome
|
| 3135 |
|
| 3136 |
|
| 3137 |
-
def atualizar_mapa(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3138 |
df = session.df_filtrado if session.df_filtrado is not None else session.df_original
|
| 3139 |
if df is None:
|
| 3140 |
raise HTTPException(status_code=400, detail="Carregue dados primeiro")
|
|
@@ -3144,9 +3152,27 @@ def atualizar_mapa(session: SessionState, var_mapa: str | None, modo_mapa: str |
|
|
| 3144 |
if tamanho_col is None:
|
| 3145 |
modo = "pontos"
|
| 3146 |
session.mapa_habilitado = True
|
| 3147 |
-
|
| 3148 |
-
|
| 3149 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3150 |
|
| 3151 |
|
| 3152 |
def carregar_popup_ponto_mapa(session: SessionState, row_id: int, source: str | None = None) -> dict[str, Any]:
|
|
@@ -3221,6 +3247,9 @@ def atualizar_mapa_residuos(
|
|
| 3221 |
var_mapa: str | None,
|
| 3222 |
modo_mapa: str | None = None,
|
| 3223 |
escala_extremo_abs: float | None = None,
|
|
|
|
|
|
|
|
|
|
| 3224 |
) -> dict[str, Any]:
|
| 3225 |
tabela_metricas = session.tabela_metricas_estado
|
| 3226 |
if tabela_metricas is None or tabela_metricas.empty:
|
|
@@ -3246,6 +3275,7 @@ def atualizar_mapa_residuos(
|
|
| 3246 |
if extremo_abs is None
|
| 3247 |
else f"Resíduo Pad. (escala fixa -{extremo_txt} a +{extremo_txt})"
|
| 3248 |
)
|
|
|
|
| 3249 |
|
| 3250 |
mapa_html = charts.criar_mapa(
|
| 3251 |
df,
|
|
@@ -3257,6 +3287,8 @@ def atualizar_mapa_residuos(
|
|
| 3257 |
cor_colors=["#2e7d32", "#f1c40f", "#ffffff", "#f1c40f", "#c62828"],
|
| 3258 |
cor_tick_values=ticks_valores,
|
| 3259 |
cor_tick_labels=ticks_labels,
|
|
|
|
|
|
|
| 3260 |
)
|
| 3261 |
mapa_payload = build_elaboracao_map_payload(
|
| 3262 |
df,
|
|
@@ -3269,6 +3301,8 @@ def atualizar_mapa_residuos(
|
|
| 3269 |
cor_tick_values=ticks_valores,
|
| 3270 |
cor_tick_labels=ticks_labels,
|
| 3271 |
popup_source="residuos",
|
|
|
|
|
|
|
| 3272 |
)
|
| 3273 |
return {
|
| 3274 |
"mapa_html": mapa_html,
|
|
@@ -3276,6 +3310,8 @@ def atualizar_mapa_residuos(
|
|
| 3276 |
"variavel_mapa": var_escolhida,
|
| 3277 |
"modo_mapa": modo,
|
| 3278 |
"escala_extremo_abs": extremo_abs,
|
|
|
|
|
|
|
| 3279 |
}
|
| 3280 |
|
| 3281 |
|
|
|
|
| 54 |
formatar_micronumerosidade_html,
|
| 55 |
formatar_outliers_anteriores_html,
|
| 56 |
)
|
| 57 |
+
from app.core.map_sizing import normalizar_faixa_raio_pontos
|
| 58 |
from app.core.visualizacao import app as visualizacao_app
|
| 59 |
from app.core.visualizacao.map_payload import build_elaboracao_map_payload
|
| 60 |
from app.models.session import SessionState
|
|
|
|
| 3135 |
return caminho, nome
|
| 3136 |
|
| 3137 |
|
| 3138 |
+
def atualizar_mapa(
|
| 3139 |
+
session: SessionState,
|
| 3140 |
+
var_mapa: str | None,
|
| 3141 |
+
modo_mapa: str | None = None,
|
| 3142 |
+
*,
|
| 3143 |
+
ponto_raio_min: float | None = None,
|
| 3144 |
+
ponto_raio_max: float | None = None,
|
| 3145 |
+
) -> dict[str, Any]:
|
| 3146 |
df = session.df_filtrado if session.df_filtrado is not None else session.df_original
|
| 3147 |
if df is None:
|
| 3148 |
raise HTTPException(status_code=400, detail="Carregue dados primeiro")
|
|
|
|
| 3152 |
if tamanho_col is None:
|
| 3153 |
modo = "pontos"
|
| 3154 |
session.mapa_habilitado = True
|
| 3155 |
+
ponto_raio_min_norm, ponto_raio_max_norm = normalizar_faixa_raio_pontos(ponto_raio_min, ponto_raio_max)
|
| 3156 |
+
mapa_html = charts.criar_mapa(
|
| 3157 |
+
df,
|
| 3158 |
+
tamanho_col=tamanho_col,
|
| 3159 |
+
modo=modo,
|
| 3160 |
+
ponto_raio_min=ponto_raio_min_norm,
|
| 3161 |
+
ponto_raio_max=ponto_raio_max_norm,
|
| 3162 |
+
)
|
| 3163 |
+
mapa_payload = build_elaboracao_map_payload(
|
| 3164 |
+
df,
|
| 3165 |
+
tamanho_col=tamanho_col,
|
| 3166 |
+
modo=modo,
|
| 3167 |
+
ponto_raio_min=ponto_raio_min_norm,
|
| 3168 |
+
ponto_raio_max=ponto_raio_max_norm,
|
| 3169 |
+
)
|
| 3170 |
+
return {
|
| 3171 |
+
"mapa_html": mapa_html,
|
| 3172 |
+
"mapa_payload": mapa_payload,
|
| 3173 |
+
"ponto_raio_min": ponto_raio_min_norm,
|
| 3174 |
+
"ponto_raio_max": ponto_raio_max_norm,
|
| 3175 |
+
}
|
| 3176 |
|
| 3177 |
|
| 3178 |
def carregar_popup_ponto_mapa(session: SessionState, row_id: int, source: str | None = None) -> dict[str, Any]:
|
|
|
|
| 3247 |
var_mapa: str | None,
|
| 3248 |
modo_mapa: str | None = None,
|
| 3249 |
escala_extremo_abs: float | None = None,
|
| 3250 |
+
*,
|
| 3251 |
+
ponto_raio_min: float | None = None,
|
| 3252 |
+
ponto_raio_max: float | None = None,
|
| 3253 |
) -> dict[str, Any]:
|
| 3254 |
tabela_metricas = session.tabela_metricas_estado
|
| 3255 |
if tabela_metricas is None or tabela_metricas.empty:
|
|
|
|
| 3275 |
if extremo_abs is None
|
| 3276 |
else f"Resíduo Pad. (escala fixa -{extremo_txt} a +{extremo_txt})"
|
| 3277 |
)
|
| 3278 |
+
ponto_raio_min_norm, ponto_raio_max_norm = normalizar_faixa_raio_pontos(ponto_raio_min, ponto_raio_max)
|
| 3279 |
|
| 3280 |
mapa_html = charts.criar_mapa(
|
| 3281 |
df,
|
|
|
|
| 3287 |
cor_colors=["#2e7d32", "#f1c40f", "#ffffff", "#f1c40f", "#c62828"],
|
| 3288 |
cor_tick_values=ticks_valores,
|
| 3289 |
cor_tick_labels=ticks_labels,
|
| 3290 |
+
ponto_raio_min=ponto_raio_min_norm,
|
| 3291 |
+
ponto_raio_max=ponto_raio_max_norm,
|
| 3292 |
)
|
| 3293 |
mapa_payload = build_elaboracao_map_payload(
|
| 3294 |
df,
|
|
|
|
| 3301 |
cor_tick_values=ticks_valores,
|
| 3302 |
cor_tick_labels=ticks_labels,
|
| 3303 |
popup_source="residuos",
|
| 3304 |
+
ponto_raio_min=ponto_raio_min_norm,
|
| 3305 |
+
ponto_raio_max=ponto_raio_max_norm,
|
| 3306 |
)
|
| 3307 |
return {
|
| 3308 |
"mapa_html": mapa_html,
|
|
|
|
| 3310 |
"variavel_mapa": var_escolhida,
|
| 3311 |
"modo_mapa": modo,
|
| 3312 |
"escala_extremo_abs": extremo_abs,
|
| 3313 |
+
"ponto_raio_min": ponto_raio_min_norm,
|
| 3314 |
+
"ponto_raio_max": ponto_raio_max_norm,
|
| 3315 |
}
|
| 3316 |
|
| 3317 |
|
backend/app/services/pesquisa_service.py
CHANGED
|
@@ -1729,10 +1729,22 @@ def _renderizar_mapa_modelos(
|
|
| 1729 |
if avaliandos_geo:
|
| 1730 |
camada_avaliando.add_to(mapa)
|
| 1731 |
|
| 1732 |
-
plugins.Fullscreen().add_to(mapa)
|
| 1733 |
add_zoom_responsive_circle_markers(mapa)
|
| 1734 |
layer_control = folium.LayerControl(collapsed=False)
|
| 1735 |
layer_control.add_to(mapa)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1736 |
_adicionar_interacao_hover_legenda_modelos(mapa, layer_control, modelos_plotados)
|
| 1737 |
if bounds:
|
| 1738 |
lat_values = [float(coord[0]) for coord in bounds]
|
|
|
|
| 1729 |
if avaliandos_geo:
|
| 1730 |
camada_avaliando.add_to(mapa)
|
| 1731 |
|
|
|
|
| 1732 |
add_zoom_responsive_circle_markers(mapa)
|
| 1733 |
layer_control = folium.LayerControl(collapsed=False)
|
| 1734 |
layer_control.add_to(mapa)
|
| 1735 |
+
plugins.MeasureControl(
|
| 1736 |
+
position="topright",
|
| 1737 |
+
primary_length_unit="meters",
|
| 1738 |
+
secondary_length_unit="kilometers",
|
| 1739 |
+
primary_area_unit="sqmeters",
|
| 1740 |
+
secondary_area_unit="hectares",
|
| 1741 |
+
).add_to(mapa)
|
| 1742 |
+
plugins.Fullscreen(
|
| 1743 |
+
position="topright",
|
| 1744 |
+
title="Abrir mapa em tela cheia",
|
| 1745 |
+
title_cancel="Sair da tela cheia",
|
| 1746 |
+
force_separate_button=True,
|
| 1747 |
+
).add_to(mapa)
|
| 1748 |
_adicionar_interacao_hover_legenda_modelos(mapa, layer_control, modelos_plotados)
|
| 1749 |
if bounds:
|
| 1750 |
lat_values = [float(coord[0]) for coord in bounds]
|
backend/app/services/trabalhos_tecnicos_service.py
CHANGED
|
@@ -1597,7 +1597,19 @@ def gerar_mapa_trabalhos(
|
|
| 1597 |
).add_to(mapa)
|
| 1598 |
|
| 1599 |
folium.LayerControl(collapsed=False).add_to(mapa)
|
| 1600 |
-
plugins.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1601 |
add_zoom_responsive_circle_markers(mapa)
|
| 1602 |
add_popup_pagination_handlers(mapa)
|
| 1603 |
|
|
@@ -1696,7 +1708,19 @@ def _criar_mapa_trabalho(nome_trabalho: str, imoveis: list[dict[str, Any]]) -> s
|
|
| 1696 |
).add_to(camada)
|
| 1697 |
camada.add_to(mapa)
|
| 1698 |
folium.LayerControl().add_to(mapa)
|
| 1699 |
-
plugins.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1700 |
add_zoom_responsive_circle_markers(mapa)
|
| 1701 |
add_popup_pagination_handlers(mapa)
|
| 1702 |
|
|
|
|
| 1597 |
).add_to(mapa)
|
| 1598 |
|
| 1599 |
folium.LayerControl(collapsed=False).add_to(mapa)
|
| 1600 |
+
plugins.MeasureControl(
|
| 1601 |
+
position="topright",
|
| 1602 |
+
primary_length_unit="meters",
|
| 1603 |
+
secondary_length_unit="kilometers",
|
| 1604 |
+
primary_area_unit="sqmeters",
|
| 1605 |
+
secondary_area_unit="hectares",
|
| 1606 |
+
).add_to(mapa)
|
| 1607 |
+
plugins.Fullscreen(
|
| 1608 |
+
position="topright",
|
| 1609 |
+
title="Abrir mapa em tela cheia",
|
| 1610 |
+
title_cancel="Sair da tela cheia",
|
| 1611 |
+
force_separate_button=True,
|
| 1612 |
+
).add_to(mapa)
|
| 1613 |
add_zoom_responsive_circle_markers(mapa)
|
| 1614 |
add_popup_pagination_handlers(mapa)
|
| 1615 |
|
|
|
|
| 1708 |
).add_to(camada)
|
| 1709 |
camada.add_to(mapa)
|
| 1710 |
folium.LayerControl().add_to(mapa)
|
| 1711 |
+
plugins.MeasureControl(
|
| 1712 |
+
position="topright",
|
| 1713 |
+
primary_length_unit="meters",
|
| 1714 |
+
secondary_length_unit="kilometers",
|
| 1715 |
+
primary_area_unit="sqmeters",
|
| 1716 |
+
secondary_area_unit="hectares",
|
| 1717 |
+
).add_to(mapa)
|
| 1718 |
+
plugins.Fullscreen(
|
| 1719 |
+
position="topright",
|
| 1720 |
+
title="Abrir mapa em tela cheia",
|
| 1721 |
+
title_cancel="Sair da tela cheia",
|
| 1722 |
+
force_separate_button=True,
|
| 1723 |
+
).add_to(mapa)
|
| 1724 |
add_zoom_responsive_circle_markers(mapa)
|
| 1725 |
add_popup_pagination_handlers(mapa)
|
| 1726 |
|
backend/app/services/visualizacao_service.py
CHANGED
|
@@ -23,6 +23,7 @@ from app.core.map_layers import (
|
|
| 23 |
add_zoom_responsive_circle_markers,
|
| 24 |
get_bairros_geojson,
|
| 25 |
)
|
|
|
|
| 26 |
from app.core.elaboracao.core import (
|
| 27 |
PERCENTUAL_RUIDO_TOL,
|
| 28 |
_migrar_pacote_v1_para_v2,
|
|
@@ -397,7 +398,19 @@ def _criar_mapa_knn_destaque(
|
|
| 397 |
if camada_avaliando is not None:
|
| 398 |
camada_avaliando.add_to(mapa)
|
| 399 |
folium.LayerControl().add_to(mapa)
|
| 400 |
-
plugins.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 401 |
add_zoom_responsive_circle_markers(mapa)
|
| 402 |
add_popup_pagination_handlers(mapa)
|
| 403 |
|
|
@@ -1414,6 +1427,8 @@ def atualizar_mapa(
|
|
| 1414 |
session: SessionState,
|
| 1415 |
variavel_mapa: str | None,
|
| 1416 |
trabalhos_tecnicos_modelos_modo: Any = None,
|
|
|
|
|
|
|
| 1417 |
api_base_url: str | None = None,
|
| 1418 |
popup_auth_token: str | None = None,
|
| 1419 |
) -> dict[str, Any]:
|
|
@@ -1427,6 +1442,7 @@ def atualizar_mapa(
|
|
| 1427 |
if variavel_mapa and variavel_mapa != "Visualização Padrão":
|
| 1428 |
tamanho_col = variavel_mapa
|
| 1429 |
|
|
|
|
| 1430 |
avaliandos_tecnicos, trabalhos_tecnicos, trabalhos_tecnicos_modelos_modo_norm = _carregar_trabalhos_tecnicos_visualizacao(
|
| 1431 |
session,
|
| 1432 |
trabalhos_tecnicos_modelos_modo,
|
|
@@ -1442,6 +1458,8 @@ def atualizar_mapa(
|
|
| 1442 |
col_y=info["nome_y"],
|
| 1443 |
avaliandos_tecnicos=avaliandos_tecnicos,
|
| 1444 |
bairros_geojson_url=bairros_geojson_url,
|
|
|
|
|
|
|
| 1445 |
)
|
| 1446 |
mapa_html = ""
|
| 1447 |
if mapa_payload is None:
|
|
@@ -1454,12 +1472,16 @@ def atualizar_mapa(
|
|
| 1454 |
popup_endpoint=popup_endpoint,
|
| 1455 |
popup_auth_token=popup_auth_token,
|
| 1456 |
avaliandos_tecnicos=avaliandos_tecnicos,
|
|
|
|
|
|
|
| 1457 |
)
|
| 1458 |
return {
|
| 1459 |
"mapa_html": mapa_html,
|
| 1460 |
"mapa_payload": mapa_payload,
|
| 1461 |
"trabalhos_tecnicos": trabalhos_tecnicos,
|
| 1462 |
"trabalhos_tecnicos_modelos_modo": trabalhos_tecnicos_modelos_modo_norm,
|
|
|
|
|
|
|
| 1463 |
}
|
| 1464 |
|
| 1465 |
|
|
|
|
| 23 |
add_zoom_responsive_circle_markers,
|
| 24 |
get_bairros_geojson,
|
| 25 |
)
|
| 26 |
+
from app.core.map_sizing import normalizar_faixa_raio_pontos
|
| 27 |
from app.core.elaboracao.core import (
|
| 28 |
PERCENTUAL_RUIDO_TOL,
|
| 29 |
_migrar_pacote_v1_para_v2,
|
|
|
|
| 398 |
if camada_avaliando is not None:
|
| 399 |
camada_avaliando.add_to(mapa)
|
| 400 |
folium.LayerControl().add_to(mapa)
|
| 401 |
+
plugins.MeasureControl(
|
| 402 |
+
position="topright",
|
| 403 |
+
primary_length_unit="meters",
|
| 404 |
+
secondary_length_unit="kilometers",
|
| 405 |
+
primary_area_unit="sqmeters",
|
| 406 |
+
secondary_area_unit="hectares",
|
| 407 |
+
).add_to(mapa)
|
| 408 |
+
plugins.Fullscreen(
|
| 409 |
+
position="topright",
|
| 410 |
+
title="Abrir mapa em tela cheia",
|
| 411 |
+
title_cancel="Sair da tela cheia",
|
| 412 |
+
force_separate_button=True,
|
| 413 |
+
).add_to(mapa)
|
| 414 |
add_zoom_responsive_circle_markers(mapa)
|
| 415 |
add_popup_pagination_handlers(mapa)
|
| 416 |
|
|
|
|
| 1427 |
session: SessionState,
|
| 1428 |
variavel_mapa: str | None,
|
| 1429 |
trabalhos_tecnicos_modelos_modo: Any = None,
|
| 1430 |
+
ponto_raio_min: float | None = None,
|
| 1431 |
+
ponto_raio_max: float | None = None,
|
| 1432 |
api_base_url: str | None = None,
|
| 1433 |
popup_auth_token: str | None = None,
|
| 1434 |
) -> dict[str, Any]:
|
|
|
|
| 1442 |
if variavel_mapa and variavel_mapa != "Visualização Padrão":
|
| 1443 |
tamanho_col = variavel_mapa
|
| 1444 |
|
| 1445 |
+
ponto_raio_min_norm, ponto_raio_max_norm = normalizar_faixa_raio_pontos(ponto_raio_min, ponto_raio_max)
|
| 1446 |
avaliandos_tecnicos, trabalhos_tecnicos, trabalhos_tecnicos_modelos_modo_norm = _carregar_trabalhos_tecnicos_visualizacao(
|
| 1447 |
session,
|
| 1448 |
trabalhos_tecnicos_modelos_modo,
|
|
|
|
| 1458 |
col_y=info["nome_y"],
|
| 1459 |
avaliandos_tecnicos=avaliandos_tecnicos,
|
| 1460 |
bairros_geojson_url=bairros_geojson_url,
|
| 1461 |
+
ponto_raio_min=ponto_raio_min_norm,
|
| 1462 |
+
ponto_raio_max=ponto_raio_max_norm,
|
| 1463 |
)
|
| 1464 |
mapa_html = ""
|
| 1465 |
if mapa_payload is None:
|
|
|
|
| 1472 |
popup_endpoint=popup_endpoint,
|
| 1473 |
popup_auth_token=popup_auth_token,
|
| 1474 |
avaliandos_tecnicos=avaliandos_tecnicos,
|
| 1475 |
+
ponto_raio_min=ponto_raio_min_norm,
|
| 1476 |
+
ponto_raio_max=ponto_raio_max_norm,
|
| 1477 |
)
|
| 1478 |
return {
|
| 1479 |
"mapa_html": mapa_html,
|
| 1480 |
"mapa_payload": mapa_payload,
|
| 1481 |
"trabalhos_tecnicos": trabalhos_tecnicos,
|
| 1482 |
"trabalhos_tecnicos_modelos_modo": trabalhos_tecnicos_modelos_modo_norm,
|
| 1483 |
+
"ponto_raio_min": ponto_raio_min_norm,
|
| 1484 |
+
"ponto_raio_max": ponto_raio_max_norm,
|
| 1485 |
}
|
| 1486 |
|
| 1487 |
|
frontend/src/api.js
CHANGED
|
@@ -347,16 +347,20 @@ export const api = {
|
|
| 347 |
confirmar_substituicao: confirmarSubstituicao,
|
| 348 |
}),
|
| 349 |
exportBase: (sessionId, filtered = true) => getBlob(`/api/elaboracao/export-base?session_id=${encodeURIComponent(sessionId)}&filtered=${String(filtered)}`),
|
| 350 |
-
updateElaboracaoMap: (sessionId, variavelMapa, modoMapa = 'pontos') => postJson('/api/elaboracao/map/update', {
|
| 351 |
session_id: sessionId,
|
| 352 |
variavel_mapa: variavelMapa,
|
| 353 |
modo_mapa: modoMapa,
|
|
|
|
|
|
|
| 354 |
}),
|
| 355 |
-
updateElaboracaoResiduosMap: (sessionId, variavelMapa, modoMapa = 'pontos', escalaExtremoAbs = null) => postJson('/api/elaboracao/residuos/map/update', {
|
| 356 |
session_id: sessionId,
|
| 357 |
variavel_mapa: variavelMapa,
|
| 358 |
modo_mapa: modoMapa,
|
| 359 |
escala_extremo_abs: escalaExtremoAbs,
|
|
|
|
|
|
|
| 360 |
}),
|
| 361 |
previewMarketDateColumn: (sessionId, colunaData) => postJson('/api/elaboracao/market-date/preview', { session_id: sessionId, coluna_data: colunaData }),
|
| 362 |
applyMarketDateColumn: (sessionId, colunaData) => postJson('/api/elaboracao/market-date/apply', { session_id: sessionId, coluna_data: colunaData }),
|
|
@@ -376,10 +380,12 @@ export const api = {
|
|
| 376 |
trabalhos_tecnicos_modelos_modo: trabalhosTecnicosModelosModo,
|
| 377 |
}),
|
| 378 |
evaluationContextViz: (sessionId) => postJson('/api/visualizacao/evaluation/context', { session_id: sessionId }),
|
| 379 |
-
updateVisualizacaoMap: (sessionId, variavelMapa, trabalhosTecnicosModelosModo = 'selecionados_e_outras_versoes') => postJson('/api/visualizacao/map/update', {
|
| 380 |
session_id: sessionId,
|
| 381 |
variavel_mapa: variavelMapa,
|
| 382 |
trabalhos_tecnicos_modelos_modo: trabalhosTecnicosModelosModo,
|
|
|
|
|
|
|
| 383 |
}),
|
| 384 |
evaluationCalculateViz: (sessionId, valoresX, indiceBase, avaliando = null) => postJson('/api/visualizacao/evaluation/calculate', {
|
| 385 |
session_id: sessionId,
|
|
|
|
| 347 |
confirmar_substituicao: confirmarSubstituicao,
|
| 348 |
}),
|
| 349 |
exportBase: (sessionId, filtered = true) => getBlob(`/api/elaboracao/export-base?session_id=${encodeURIComponent(sessionId)}&filtered=${String(filtered)}`),
|
| 350 |
+
updateElaboracaoMap: (sessionId, variavelMapa, modoMapa = 'pontos', pontoRaioMin = null, pontoRaioMax = null) => postJson('/api/elaboracao/map/update', {
|
| 351 |
session_id: sessionId,
|
| 352 |
variavel_mapa: variavelMapa,
|
| 353 |
modo_mapa: modoMapa,
|
| 354 |
+
ponto_raio_min: pontoRaioMin,
|
| 355 |
+
ponto_raio_max: pontoRaioMax,
|
| 356 |
}),
|
| 357 |
+
updateElaboracaoResiduosMap: (sessionId, variavelMapa, modoMapa = 'pontos', escalaExtremoAbs = null, pontoRaioMin = null, pontoRaioMax = null) => postJson('/api/elaboracao/residuos/map/update', {
|
| 358 |
session_id: sessionId,
|
| 359 |
variavel_mapa: variavelMapa,
|
| 360 |
modo_mapa: modoMapa,
|
| 361 |
escala_extremo_abs: escalaExtremoAbs,
|
| 362 |
+
ponto_raio_min: pontoRaioMin,
|
| 363 |
+
ponto_raio_max: pontoRaioMax,
|
| 364 |
}),
|
| 365 |
previewMarketDateColumn: (sessionId, colunaData) => postJson('/api/elaboracao/market-date/preview', { session_id: sessionId, coluna_data: colunaData }),
|
| 366 |
applyMarketDateColumn: (sessionId, colunaData) => postJson('/api/elaboracao/market-date/apply', { session_id: sessionId, coluna_data: colunaData }),
|
|
|
|
| 380 |
trabalhos_tecnicos_modelos_modo: trabalhosTecnicosModelosModo,
|
| 381 |
}),
|
| 382 |
evaluationContextViz: (sessionId) => postJson('/api/visualizacao/evaluation/context', { session_id: sessionId }),
|
| 383 |
+
updateVisualizacaoMap: (sessionId, variavelMapa, trabalhosTecnicosModelosModo = 'selecionados_e_outras_versoes', pontoRaioMin = null, pontoRaioMax = null) => postJson('/api/visualizacao/map/update', {
|
| 384 |
session_id: sessionId,
|
| 385 |
variavel_mapa: variavelMapa,
|
| 386 |
trabalhos_tecnicos_modelos_modo: trabalhosTecnicosModelosModo,
|
| 387 |
+
ponto_raio_min: pontoRaioMin,
|
| 388 |
+
ponto_raio_max: pontoRaioMax,
|
| 389 |
}),
|
| 390 |
evaluationCalculateViz: (sessionId, valoresX, indiceBase, avaliando = null) => postJson('/api/visualizacao/evaluation/calculate', {
|
| 391 |
session_id: sessionId,
|
frontend/src/components/ElaboracaoTab.jsx
CHANGED
|
@@ -2,6 +2,11 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
| 2 |
import { api, downloadBlob } from '../api'
|
| 3 |
import { tableToCsvBlob } from '../csv'
|
| 4 |
import { buildElaboracaoModeloLink } from '../deepLinks'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
import Plotly from 'plotly.js-dist-min'
|
| 6 |
import DataTable from './DataTable'
|
| 7 |
import EquationFormatsPanel from './EquationFormatsPanel'
|
|
@@ -1607,11 +1612,15 @@ export default function ElaboracaoTab({ sessionId, authUser, quickLoadRequest =
|
|
| 1607 |
const [mapaPayload, setMapaPayload] = useState(null)
|
| 1608 |
const [mapaVariavel, setMapaVariavel] = useState(MAPA_VARIAVEL_PADRAO)
|
| 1609 |
const [mapaModo, setMapaModo] = useState(MAPA_MODO_PONTOS)
|
|
|
|
|
|
|
| 1610 |
const [mapaGerado, setMapaGerado] = useState(false)
|
| 1611 |
const [mapaResiduosHtml, setMapaResiduosHtml] = useState('')
|
| 1612 |
const [mapaResiduosPayload, setMapaResiduosPayload] = useState(null)
|
| 1613 |
const [mapaResiduosModo, setMapaResiduosModo] = useState(MAPA_MODO_PONTOS)
|
| 1614 |
const [mapaResiduosExtremoAbs, setMapaResiduosExtremoAbs] = useState(MAPA_RESIDUOS_EXTREMO_ABS_DEFAULT)
|
|
|
|
|
|
|
| 1615 |
const [mapaResiduosGerado, setMapaResiduosGerado] = useState(false)
|
| 1616 |
|
| 1617 |
const [coordsInfo, setCoordsInfo] = useState(null)
|
|
@@ -1770,6 +1779,8 @@ export default function ElaboracaoTab({ sessionId, authUser, quickLoadRequest =
|
|
| 1770 |
|
| 1771 |
const mapaChoices = useMemo(() => [MAPA_VARIAVEL_PADRAO, ...colunasNumericas], [colunasNumericas])
|
| 1772 |
const mapaModoDisponivel = mapaVariavel !== MAPA_VARIAVEL_PADRAO
|
|
|
|
|
|
|
| 1773 |
const importPreviewColumns = useMemo(
|
| 1774 |
() => (Array.isArray(importPreview?.columns) ? importPreview.columns.map((item) => String(item)) : []),
|
| 1775 |
[importPreview],
|
|
@@ -1786,6 +1797,12 @@ export default function ElaboracaoTab({ sessionId, authUser, quickLoadRequest =
|
|
| 1786 |
() => importSelectedColumns.length >= 2,
|
| 1787 |
[importSelectedColumns],
|
| 1788 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1789 |
const colunasXDisponiveis = useMemo(
|
| 1790 |
() => (colunaY ? colunasNumericas.filter((coluna) => coluna !== colunaY) : []),
|
| 1791 |
[colunasNumericas, colunaY],
|
|
@@ -4795,8 +4812,9 @@ export default function ElaboracaoTab({ sessionId, authUser, quickLoadRequest =
|
|
| 4795 |
setMapaModo(nextModo)
|
| 4796 |
}
|
| 4797 |
if (!sessionId || !mapaGerado) return
|
|
|
|
| 4798 |
await withBusy(async () => {
|
| 4799 |
-
const resp = await api.updateElaboracaoMap(sessionId, value, nextModo)
|
| 4800 |
applyMapaResponse(resp)
|
| 4801 |
})
|
| 4802 |
}
|
|
@@ -4804,8 +4822,22 @@ export default function ElaboracaoTab({ sessionId, authUser, quickLoadRequest =
|
|
| 4804 |
async function onMapModeChange(value) {
|
| 4805 |
setMapaModo(value)
|
| 4806 |
if (!sessionId || !mapaGerado) return
|
|
|
|
| 4807 |
await withBusy(async () => {
|
| 4808 |
-
const resp = await api.updateElaboracaoMap(sessionId, mapaVariavel, value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4809 |
applyMapaResponse(resp)
|
| 4810 |
})
|
| 4811 |
}
|
|
@@ -4816,8 +4848,9 @@ export default function ElaboracaoTab({ sessionId, authUser, quickLoadRequest =
|
|
| 4816 |
if (modoAtual !== mapaModo) {
|
| 4817 |
setMapaModo(modoAtual)
|
| 4818 |
}
|
|
|
|
| 4819 |
await withBusy(async () => {
|
| 4820 |
-
const resp = await api.updateElaboracaoMap(sessionId, mapaVariavel, modoAtual)
|
| 4821 |
applyMapaResponse(resp)
|
| 4822 |
setMapaGerado(true)
|
| 4823 |
})
|
|
@@ -4827,8 +4860,9 @@ export default function ElaboracaoTab({ sessionId, authUser, quickLoadRequest =
|
|
| 4827 |
setMapaResiduosModo(value)
|
| 4828 |
if (!sessionId || !mapaResiduosGerado) return
|
| 4829 |
const extremoAbs = parseMapaResiduosExtremoAbs(mapaResiduosExtremoAbs)
|
|
|
|
| 4830 |
await withBusy(async () => {
|
| 4831 |
-
const resp = await api.updateElaboracaoResiduosMap(sessionId, MAPA_RESIDUOS_VARIAVEL, value, extremoAbs)
|
| 4832 |
applyMapaResiduosResponse(resp)
|
| 4833 |
const extremoResp = Number(resp?.escala_extremo_abs)
|
| 4834 |
if (Number.isFinite(extremoResp) && extremoResp > 0) {
|
|
@@ -4844,8 +4878,9 @@ export default function ElaboracaoTab({ sessionId, authUser, quickLoadRequest =
|
|
| 4844 |
setMapaResiduosExtremoAbs(raw)
|
| 4845 |
const extremoAbs = parseMapaResiduosExtremoAbs(raw)
|
| 4846 |
if (!sessionId || !mapaResiduosGerado) return
|
|
|
|
| 4847 |
await withBusy(async () => {
|
| 4848 |
-
const resp = await api.updateElaboracaoResiduosMap(sessionId, MAPA_RESIDUOS_VARIAVEL, mapaResiduosModo, extremoAbs)
|
| 4849 |
applyMapaResiduosResponse(resp)
|
| 4850 |
const extremoResp = Number(resp?.escala_extremo_abs)
|
| 4851 |
if (Number.isFinite(extremoResp) && extremoResp > 0) {
|
|
@@ -4859,8 +4894,9 @@ export default function ElaboracaoTab({ sessionId, authUser, quickLoadRequest =
|
|
| 4859 |
async function onGerarMapaResiduos() {
|
| 4860 |
if (!sessionId) return
|
| 4861 |
const extremoAbs = parseMapaResiduosExtremoAbs(mapaResiduosExtremoAbs)
|
|
|
|
| 4862 |
await withBusy(async () => {
|
| 4863 |
-
const resp = await api.updateElaboracaoResiduosMap(sessionId, MAPA_RESIDUOS_VARIAVEL, mapaResiduosModo, extremoAbs)
|
| 4864 |
applyMapaResiduosResponse(resp)
|
| 4865 |
const extremoResp = Number(resp?.escala_extremo_abs)
|
| 4866 |
if (Number.isFinite(extremoResp) && extremoResp > 0) {
|
|
@@ -4872,6 +4908,20 @@ export default function ElaboracaoTab({ sessionId, authUser, quickLoadRequest =
|
|
| 4872 |
})
|
| 4873 |
}
|
| 4874 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4875 |
function onDownloadTableCsv(table, fileNameBase) {
|
| 4876 |
const blob = tableToCsvBlob(table)
|
| 4877 |
if (!blob) {
|
|
@@ -5665,7 +5715,7 @@ export default function ElaboracaoTab({ sessionId, authUser, quickLoadRequest =
|
|
| 5665 |
|
| 5666 |
{!importSelectionReady ? (
|
| 5667 |
<div className="section1-empty-hint">
|
| 5668 |
-
|
| 5669 |
</div>
|
| 5670 |
) : null}
|
| 5671 |
|
|
@@ -6125,6 +6175,32 @@ export default function ElaboracaoTab({ sessionId, authUser, quickLoadRequest =
|
|
| 6125 |
</select>
|
| 6126 |
</div>
|
| 6127 |
) : null}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6128 |
</div>
|
| 6129 |
<div className="download-actions-bar">
|
| 6130 |
<button
|
|
@@ -7466,6 +7542,32 @@ export default function ElaboracaoTab({ sessionId, authUser, quickLoadRequest =
|
|
| 7466 |
))}
|
| 7467 |
</select>
|
| 7468 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7469 |
</div>
|
| 7470 |
<div className="residuos-map-scale-hint">
|
| 7471 |
{mapaResiduosExtremoAbsAtivo === null
|
|
|
|
| 2 |
import { api, downloadBlob } from '../api'
|
| 3 |
import { tableToCsvBlob } from '../csv'
|
| 4 |
import { buildElaboracaoModeloLink } from '../deepLinks'
|
| 5 |
+
import {
|
| 6 |
+
MAP_POINT_RADIUS_MAX_DEFAULT,
|
| 7 |
+
MAP_POINT_RADIUS_MIN_DEFAULT,
|
| 8 |
+
normalizeMapPointRadiusRange,
|
| 9 |
+
} from '../mapPointSizing'
|
| 10 |
import Plotly from 'plotly.js-dist-min'
|
| 11 |
import DataTable from './DataTable'
|
| 12 |
import EquationFormatsPanel from './EquationFormatsPanel'
|
|
|
|
| 1612 |
const [mapaPayload, setMapaPayload] = useState(null)
|
| 1613 |
const [mapaVariavel, setMapaVariavel] = useState(MAPA_VARIAVEL_PADRAO)
|
| 1614 |
const [mapaModo, setMapaModo] = useState(MAPA_MODO_PONTOS)
|
| 1615 |
+
const [mapaPontoRaioMin, setMapaPontoRaioMin] = useState(String(MAP_POINT_RADIUS_MIN_DEFAULT))
|
| 1616 |
+
const [mapaPontoRaioMax, setMapaPontoRaioMax] = useState(String(MAP_POINT_RADIUS_MAX_DEFAULT))
|
| 1617 |
const [mapaGerado, setMapaGerado] = useState(false)
|
| 1618 |
const [mapaResiduosHtml, setMapaResiduosHtml] = useState('')
|
| 1619 |
const [mapaResiduosPayload, setMapaResiduosPayload] = useState(null)
|
| 1620 |
const [mapaResiduosModo, setMapaResiduosModo] = useState(MAPA_MODO_PONTOS)
|
| 1621 |
const [mapaResiduosExtremoAbs, setMapaResiduosExtremoAbs] = useState(MAPA_RESIDUOS_EXTREMO_ABS_DEFAULT)
|
| 1622 |
+
const [mapaResiduosPontoRaioMin, setMapaResiduosPontoRaioMin] = useState(String(MAP_POINT_RADIUS_MIN_DEFAULT))
|
| 1623 |
+
const [mapaResiduosPontoRaioMax, setMapaResiduosPontoRaioMax] = useState(String(MAP_POINT_RADIUS_MAX_DEFAULT))
|
| 1624 |
const [mapaResiduosGerado, setMapaResiduosGerado] = useState(false)
|
| 1625 |
|
| 1626 |
const [coordsInfo, setCoordsInfo] = useState(null)
|
|
|
|
| 1779 |
|
| 1780 |
const mapaChoices = useMemo(() => [MAPA_VARIAVEL_PADRAO, ...colunasNumericas], [colunasNumericas])
|
| 1781 |
const mapaModoDisponivel = mapaVariavel !== MAPA_VARIAVEL_PADRAO
|
| 1782 |
+
const mapaRaioPontosDisponivel = mapaModoDisponivel && mapaModo === MAPA_MODO_PONTOS
|
| 1783 |
+
const mapaResiduosRaioPontosDisponivel = mapaResiduosModo === MAPA_MODO_PONTOS
|
| 1784 |
const importPreviewColumns = useMemo(
|
| 1785 |
() => (Array.isArray(importPreview?.columns) ? importPreview.columns.map((item) => String(item)) : []),
|
| 1786 |
[importPreview],
|
|
|
|
| 1797 |
() => importSelectedColumns.length >= 2,
|
| 1798 |
[importSelectedColumns],
|
| 1799 |
)
|
| 1800 |
+
const importColumnsLoadedMessage = useMemo(() => {
|
| 1801 |
+
const total = importPreviewColumns.length
|
| 1802 |
+
const colunaLabel = total === 1 ? 'coluna carregada' : 'colunas carregadas'
|
| 1803 |
+
const referenciaLabel = total === 1 ? 'dela' : 'delas'
|
| 1804 |
+
return `${total} ${colunaLabel}. Selecione pelo menos 2 ${referenciaLabel} para liberar as próximas etapas.`
|
| 1805 |
+
}, [importPreviewColumns])
|
| 1806 |
const colunasXDisponiveis = useMemo(
|
| 1807 |
() => (colunaY ? colunasNumericas.filter((coluna) => coluna !== colunaY) : []),
|
| 1808 |
[colunasNumericas, colunaY],
|
|
|
|
| 4812 |
setMapaModo(nextModo)
|
| 4813 |
}
|
| 4814 |
if (!sessionId || !mapaGerado) return
|
| 4815 |
+
const raioRange = normalizeMapPointRadiusRange(mapaPontoRaioMin, mapaPontoRaioMax)
|
| 4816 |
await withBusy(async () => {
|
| 4817 |
+
const resp = await api.updateElaboracaoMap(sessionId, value, nextModo, raioRange.min, raioRange.max)
|
| 4818 |
applyMapaResponse(resp)
|
| 4819 |
})
|
| 4820 |
}
|
|
|
|
| 4822 |
async function onMapModeChange(value) {
|
| 4823 |
setMapaModo(value)
|
| 4824 |
if (!sessionId || !mapaGerado) return
|
| 4825 |
+
const raioRange = normalizeMapPointRadiusRange(mapaPontoRaioMin, mapaPontoRaioMax)
|
| 4826 |
await withBusy(async () => {
|
| 4827 |
+
const resp = await api.updateElaboracaoMap(sessionId, mapaVariavel, value, raioRange.min, raioRange.max)
|
| 4828 |
+
applyMapaResponse(resp)
|
| 4829 |
+
})
|
| 4830 |
+
}
|
| 4831 |
+
|
| 4832 |
+
async function onMapPointRadiusChange(kind, value) {
|
| 4833 |
+
const nextMin = kind === 'min' ? value : mapaPontoRaioMin
|
| 4834 |
+
const nextMax = kind === 'max' ? value : mapaPontoRaioMax
|
| 4835 |
+
if (kind === 'min') setMapaPontoRaioMin(value)
|
| 4836 |
+
else setMapaPontoRaioMax(value)
|
| 4837 |
+
if (!sessionId || !mapaGerado || !mapaRaioPontosDisponivel) return
|
| 4838 |
+
const raioRange = normalizeMapPointRadiusRange(nextMin, nextMax)
|
| 4839 |
+
await withBusy(async () => {
|
| 4840 |
+
const resp = await api.updateElaboracaoMap(sessionId, mapaVariavel, MAPA_MODO_PONTOS, raioRange.min, raioRange.max)
|
| 4841 |
applyMapaResponse(resp)
|
| 4842 |
})
|
| 4843 |
}
|
|
|
|
| 4848 |
if (modoAtual !== mapaModo) {
|
| 4849 |
setMapaModo(modoAtual)
|
| 4850 |
}
|
| 4851 |
+
const raioRange = normalizeMapPointRadiusRange(mapaPontoRaioMin, mapaPontoRaioMax)
|
| 4852 |
await withBusy(async () => {
|
| 4853 |
+
const resp = await api.updateElaboracaoMap(sessionId, mapaVariavel, modoAtual, raioRange.min, raioRange.max)
|
| 4854 |
applyMapaResponse(resp)
|
| 4855 |
setMapaGerado(true)
|
| 4856 |
})
|
|
|
|
| 4860 |
setMapaResiduosModo(value)
|
| 4861 |
if (!sessionId || !mapaResiduosGerado) return
|
| 4862 |
const extremoAbs = parseMapaResiduosExtremoAbs(mapaResiduosExtremoAbs)
|
| 4863 |
+
const raioRange = normalizeMapPointRadiusRange(mapaResiduosPontoRaioMin, mapaResiduosPontoRaioMax)
|
| 4864 |
await withBusy(async () => {
|
| 4865 |
+
const resp = await api.updateElaboracaoResiduosMap(sessionId, MAPA_RESIDUOS_VARIAVEL, value, extremoAbs, raioRange.min, raioRange.max)
|
| 4866 |
applyMapaResiduosResponse(resp)
|
| 4867 |
const extremoResp = Number(resp?.escala_extremo_abs)
|
| 4868 |
if (Number.isFinite(extremoResp) && extremoResp > 0) {
|
|
|
|
| 4878 |
setMapaResiduosExtremoAbs(raw)
|
| 4879 |
const extremoAbs = parseMapaResiduosExtremoAbs(raw)
|
| 4880 |
if (!sessionId || !mapaResiduosGerado) return
|
| 4881 |
+
const raioRange = normalizeMapPointRadiusRange(mapaResiduosPontoRaioMin, mapaResiduosPontoRaioMax)
|
| 4882 |
await withBusy(async () => {
|
| 4883 |
+
const resp = await api.updateElaboracaoResiduosMap(sessionId, MAPA_RESIDUOS_VARIAVEL, mapaResiduosModo, extremoAbs, raioRange.min, raioRange.max)
|
| 4884 |
applyMapaResiduosResponse(resp)
|
| 4885 |
const extremoResp = Number(resp?.escala_extremo_abs)
|
| 4886 |
if (Number.isFinite(extremoResp) && extremoResp > 0) {
|
|
|
|
| 4894 |
async function onGerarMapaResiduos() {
|
| 4895 |
if (!sessionId) return
|
| 4896 |
const extremoAbs = parseMapaResiduosExtremoAbs(mapaResiduosExtremoAbs)
|
| 4897 |
+
const raioRange = normalizeMapPointRadiusRange(mapaResiduosPontoRaioMin, mapaResiduosPontoRaioMax)
|
| 4898 |
await withBusy(async () => {
|
| 4899 |
+
const resp = await api.updateElaboracaoResiduosMap(sessionId, MAPA_RESIDUOS_VARIAVEL, mapaResiduosModo, extremoAbs, raioRange.min, raioRange.max)
|
| 4900 |
applyMapaResiduosResponse(resp)
|
| 4901 |
const extremoResp = Number(resp?.escala_extremo_abs)
|
| 4902 |
if (Number.isFinite(extremoResp) && extremoResp > 0) {
|
|
|
|
| 4908 |
})
|
| 4909 |
}
|
| 4910 |
|
| 4911 |
+
async function onMapaResiduosPointRadiusChange(kind, value) {
|
| 4912 |
+
const nextMin = kind === 'min' ? value : mapaResiduosPontoRaioMin
|
| 4913 |
+
const nextMax = kind === 'max' ? value : mapaResiduosPontoRaioMax
|
| 4914 |
+
if (kind === 'min') setMapaResiduosPontoRaioMin(value)
|
| 4915 |
+
else setMapaResiduosPontoRaioMax(value)
|
| 4916 |
+
if (!sessionId || !mapaResiduosGerado || !mapaResiduosRaioPontosDisponivel) return
|
| 4917 |
+
const extremoAbs = parseMapaResiduosExtremoAbs(mapaResiduosExtremoAbs)
|
| 4918 |
+
const raioRange = normalizeMapPointRadiusRange(nextMin, nextMax)
|
| 4919 |
+
await withBusy(async () => {
|
| 4920 |
+
const resp = await api.updateElaboracaoResiduosMap(sessionId, MAPA_RESIDUOS_VARIAVEL, MAPA_MODO_PONTOS, extremoAbs, raioRange.min, raioRange.max)
|
| 4921 |
+
applyMapaResiduosResponse(resp)
|
| 4922 |
+
})
|
| 4923 |
+
}
|
| 4924 |
+
|
| 4925 |
function onDownloadTableCsv(table, fileNameBase) {
|
| 4926 |
const blob = tableToCsvBlob(table)
|
| 4927 |
if (!blob) {
|
|
|
|
| 5715 |
|
| 5716 |
{!importSelectionReady ? (
|
| 5717 |
<div className="section1-empty-hint">
|
| 5718 |
+
{importColumnsLoadedMessage}
|
| 5719 |
</div>
|
| 5720 |
) : null}
|
| 5721 |
|
|
|
|
| 6175 |
</select>
|
| 6176 |
</div>
|
| 6177 |
) : null}
|
| 6178 |
+
{mapaRaioPontosDisponivel ? (
|
| 6179 |
+
<>
|
| 6180 |
+
<div className="dados-mapa-control-field dados-mapa-radius-field">
|
| 6181 |
+
<label>Raio mínimo</label>
|
| 6182 |
+
<input
|
| 6183 |
+
type="number"
|
| 6184 |
+
min="1"
|
| 6185 |
+
max="64"
|
| 6186 |
+
step="0.5"
|
| 6187 |
+
value={mapaPontoRaioMin}
|
| 6188 |
+
onChange={(e) => void onMapPointRadiusChange('min', e.target.value)}
|
| 6189 |
+
/>
|
| 6190 |
+
</div>
|
| 6191 |
+
<div className="dados-mapa-control-field dados-mapa-radius-field">
|
| 6192 |
+
<label>Raio máximo</label>
|
| 6193 |
+
<input
|
| 6194 |
+
type="number"
|
| 6195 |
+
min="1"
|
| 6196 |
+
max="64"
|
| 6197 |
+
step="0.5"
|
| 6198 |
+
value={mapaPontoRaioMax}
|
| 6199 |
+
onChange={(e) => void onMapPointRadiusChange('max', e.target.value)}
|
| 6200 |
+
/>
|
| 6201 |
+
</div>
|
| 6202 |
+
</>
|
| 6203 |
+
) : null}
|
| 6204 |
</div>
|
| 6205 |
<div className="download-actions-bar">
|
| 6206 |
<button
|
|
|
|
| 7542 |
))}
|
| 7543 |
</select>
|
| 7544 |
</div>
|
| 7545 |
+
{mapaResiduosRaioPontosDisponivel ? (
|
| 7546 |
+
<>
|
| 7547 |
+
<div className="dados-mapa-control-field dados-mapa-radius-field">
|
| 7548 |
+
<label>Raio mínimo</label>
|
| 7549 |
+
<input
|
| 7550 |
+
type="number"
|
| 7551 |
+
min="1"
|
| 7552 |
+
max="64"
|
| 7553 |
+
step="0.5"
|
| 7554 |
+
value={mapaResiduosPontoRaioMin}
|
| 7555 |
+
onChange={(e) => void onMapaResiduosPointRadiusChange('min', e.target.value)}
|
| 7556 |
+
/>
|
| 7557 |
+
</div>
|
| 7558 |
+
<div className="dados-mapa-control-field dados-mapa-radius-field">
|
| 7559 |
+
<label>Raio máximo</label>
|
| 7560 |
+
<input
|
| 7561 |
+
type="number"
|
| 7562 |
+
min="1"
|
| 7563 |
+
max="64"
|
| 7564 |
+
step="0.5"
|
| 7565 |
+
value={mapaResiduosPontoRaioMax}
|
| 7566 |
+
onChange={(e) => void onMapaResiduosPointRadiusChange('max', e.target.value)}
|
| 7567 |
+
/>
|
| 7568 |
+
</div>
|
| 7569 |
+
</>
|
| 7570 |
+
) : null}
|
| 7571 |
</div>
|
| 7572 |
<div className="residuos-map-scale-hint">
|
| 7573 |
{mapaResiduosExtremoAbsAtivo === null
|
frontend/src/components/LeafletMapFrame.jsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'
|
| 2 |
import L from 'leaflet'
|
| 3 |
-
import 'leaflet.fullscreen'
|
| 4 |
import 'leaflet.heat'
|
| 5 |
import { apiUrl, downloadBlob, getAuthToken } from '../api'
|
| 6 |
|
|
@@ -399,7 +398,7 @@ function buildLegacyOverlayLayers(payload) {
|
|
| 399 |
overlays.push({
|
| 400 |
id: 'trabalhos-tecnicos',
|
| 401 |
label: 'Trabalhos técnicos',
|
| 402 |
-
show:
|
| 403 |
markers: payload.trabalhos_tecnicos_points,
|
| 404 |
})
|
| 405 |
}
|
|
@@ -631,6 +630,64 @@ function fitMapToPayloadBounds(map, payload, padding = 48) {
|
|
| 631 |
return false
|
| 632 |
}
|
| 633 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 634 |
async function waitForTileLayerLoad(tileLayer, timeoutMs = 6500) {
|
| 635 |
if (!tileLayer) return
|
| 636 |
await new Promise((resolve) => {
|
|
@@ -1018,7 +1075,7 @@ async function captureContainerRegionBlob(container, selectionRect = null, scale
|
|
| 1018 |
return canvasToBlob(canvas)
|
| 1019 |
}
|
| 1020 |
|
| 1021 |
-
const LeafletMapFrame = forwardRef(function LeafletMapFrame({ payload, sessionId }, ref) {
|
| 1022 |
const hostRef = useRef(null)
|
| 1023 |
const mapRef = useRef(null)
|
| 1024 |
const payloadRef = useRef(payload)
|
|
@@ -1785,9 +1842,6 @@ const LeafletMapFrame = forwardRef(function LeafletMapFrame({ payload, sessionId
|
|
| 1785 |
window.setTimeout(() => bindLayerControlHover(layerControl), 180)
|
| 1786 |
}
|
| 1787 |
}
|
| 1788 |
-
if (payload.controls?.fullscreen && typeof L.control.fullscreen === 'function') {
|
| 1789 |
-
L.control.fullscreen({ position: 'topleft' }).addTo(map)
|
| 1790 |
-
}
|
| 1791 |
if (payload.controls?.measure) {
|
| 1792 |
const disableInteractionsForMeasure = () => {
|
| 1793 |
if (restoreMapInteractions) return
|
|
@@ -2049,6 +2103,10 @@ const LeafletMapFrame = forwardRef(function LeafletMapFrame({ payload, sessionId
|
|
| 2049 |
measureControl.addTo(map)
|
| 2050 |
}
|
| 2051 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2052 |
if (payload.legend?.title) {
|
| 2053 |
const legendControl = construirLegendaControl(payload.legend)
|
| 2054 |
if (legendControl) {
|
|
@@ -2093,7 +2151,7 @@ const LeafletMapFrame = forwardRef(function LeafletMapFrame({ payload, sessionId
|
|
| 2093 |
}
|
| 2094 |
map.remove()
|
| 2095 |
}
|
| 2096 |
-
}, [payload, sessionId])
|
| 2097 |
|
| 2098 |
return (
|
| 2099 |
<div className="map-frame leaflet-map-host">
|
|
|
|
| 1 |
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'
|
| 2 |
import L from 'leaflet'
|
|
|
|
| 3 |
import 'leaflet.heat'
|
| 4 |
import { apiUrl, downloadBlob, getAuthToken } from '../api'
|
| 5 |
|
|
|
|
| 398 |
overlays.push({
|
| 399 |
id: 'trabalhos-tecnicos',
|
| 400 |
label: 'Trabalhos técnicos',
|
| 401 |
+
show: payload?.trabalhos_tecnicos_show !== false,
|
| 402 |
markers: payload.trabalhos_tecnicos_points,
|
| 403 |
})
|
| 404 |
}
|
|
|
|
| 630 |
return false
|
| 631 |
}
|
| 632 |
|
| 633 |
+
function addFullscreenControl(map, onToggleFullscreen) {
|
| 634 |
+
const control = L.control({ position: 'topright' })
|
| 635 |
+
let root = null
|
| 636 |
+
|
| 637 |
+
control.onAdd = () => {
|
| 638 |
+
root = L.DomUtil.create('div', 'mesa-leaflet-fullscreen leaflet-bar leaflet-control')
|
| 639 |
+
const button = L.DomUtil.create('a', 'mesa-leaflet-fullscreen-toggle', root)
|
| 640 |
+
button.href = '#'
|
| 641 |
+
button.title = 'Abrir mapa em tela cheia'
|
| 642 |
+
button.setAttribute('aria-label', 'Abrir mapa em tela cheia')
|
| 643 |
+
button.innerHTML = '<span class="mesa-sr-only">Abrir mapa em tela cheia</span>'
|
| 644 |
+
|
| 645 |
+
const updateState = () => {
|
| 646 |
+
const active = Boolean(document.fullscreenElement)
|
| 647 |
+
root?.classList.toggle('is-active', active)
|
| 648 |
+
button.title = active ? 'Sair da tela cheia' : 'Abrir mapa em tela cheia'
|
| 649 |
+
button.setAttribute('aria-label', active ? 'Sair da tela cheia' : 'Abrir mapa em tela cheia')
|
| 650 |
+
}
|
| 651 |
+
|
| 652 |
+
const fallbackToggle = async () => {
|
| 653 |
+
if (document.fullscreenElement) {
|
| 654 |
+
await document.exitFullscreen()
|
| 655 |
+
return
|
| 656 |
+
}
|
| 657 |
+
const container = map.getContainer()
|
| 658 |
+
if (typeof container?.requestFullscreen === 'function') {
|
| 659 |
+
await container.requestFullscreen()
|
| 660 |
+
}
|
| 661 |
+
}
|
| 662 |
+
|
| 663 |
+
L.DomEvent.disableClickPropagation(root)
|
| 664 |
+
L.DomEvent.disableScrollPropagation(root)
|
| 665 |
+
L.DomEvent.on(button, 'click', (event) => {
|
| 666 |
+
L.DomEvent.stop(event)
|
| 667 |
+
const result = onToggleFullscreen ? onToggleFullscreen(event) : fallbackToggle()
|
| 668 |
+
Promise.resolve(result).catch(() => {}).finally(() => {
|
| 669 |
+
window.setTimeout(() => {
|
| 670 |
+
map.invalidateSize?.(true)
|
| 671 |
+
}, 120)
|
| 672 |
+
})
|
| 673 |
+
})
|
| 674 |
+
document.addEventListener('fullscreenchange', updateState)
|
| 675 |
+
root._mesaFullscreenCleanup = () => {
|
| 676 |
+
document.removeEventListener('fullscreenchange', updateState)
|
| 677 |
+
}
|
| 678 |
+
updateState()
|
| 679 |
+
return root
|
| 680 |
+
}
|
| 681 |
+
|
| 682 |
+
control.onRemove = () => {
|
| 683 |
+
root?._mesaFullscreenCleanup?.()
|
| 684 |
+
root = null
|
| 685 |
+
}
|
| 686 |
+
|
| 687 |
+
control.addTo(map)
|
| 688 |
+
return control
|
| 689 |
+
}
|
| 690 |
+
|
| 691 |
async function waitForTileLayerLoad(tileLayer, timeoutMs = 6500) {
|
| 692 |
if (!tileLayer) return
|
| 693 |
await new Promise((resolve) => {
|
|
|
|
| 1075 |
return canvasToBlob(canvas)
|
| 1076 |
}
|
| 1077 |
|
| 1078 |
+
const LeafletMapFrame = forwardRef(function LeafletMapFrame({ payload, sessionId, onToggleFullscreen }, ref) {
|
| 1079 |
const hostRef = useRef(null)
|
| 1080 |
const mapRef = useRef(null)
|
| 1081 |
const payloadRef = useRef(payload)
|
|
|
|
| 1842 |
window.setTimeout(() => bindLayerControlHover(layerControl), 180)
|
| 1843 |
}
|
| 1844 |
}
|
|
|
|
|
|
|
|
|
|
| 1845 |
if (payload.controls?.measure) {
|
| 1846 |
const disableInteractionsForMeasure = () => {
|
| 1847 |
if (restoreMapInteractions) return
|
|
|
|
| 2103 |
measureControl.addTo(map)
|
| 2104 |
}
|
| 2105 |
|
| 2106 |
+
if (payload.controls?.fullscreen) {
|
| 2107 |
+
addFullscreenControl(map, onToggleFullscreen)
|
| 2108 |
+
}
|
| 2109 |
+
|
| 2110 |
if (payload.legend?.title) {
|
| 2111 |
const legendControl = construirLegendaControl(payload.legend)
|
| 2112 |
if (legendControl) {
|
|
|
|
| 2151 |
}
|
| 2152 |
map.remove()
|
| 2153 |
}
|
| 2154 |
+
}, [payload, sessionId, onToggleFullscreen])
|
| 2155 |
|
| 2156 |
return (
|
| 2157 |
<div className="map-frame leaflet-map-host">
|
frontend/src/components/MapFrame.jsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef } from 'react'
|
| 2 |
import LeafletMapFrame from './LeafletMapFrame'
|
| 3 |
|
| 4 |
function hashHtml(value) {
|
|
@@ -12,9 +12,11 @@ function hashHtml(value) {
|
|
| 12 |
}
|
| 13 |
|
| 14 |
const MapFrame = forwardRef(function MapFrame({ html, payload = null, sessionId = '' }, ref) {
|
|
|
|
| 15 |
const iframeRef = useRef(null)
|
| 16 |
const leafletRef = useRef(null)
|
| 17 |
const timersRef = useRef([])
|
|
|
|
| 18 |
const frameKey = useMemo(() => hashHtml(html || ''), [html])
|
| 19 |
|
| 20 |
const clearTimers = useCallback(() => {
|
|
@@ -125,12 +127,49 @@ const MapFrame = forwardRef(function MapFrame({ html, payload = null, sessionId
|
|
| 125 |
})
|
| 126 |
}, [clearTimers, recenterFromLayers])
|
| 127 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
useEffect(() => {
|
| 129 |
return () => {
|
| 130 |
clearTimers()
|
| 131 |
}
|
| 132 |
}, [clearTimers])
|
| 133 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
useImperativeHandle(ref, () => ({
|
| 135 |
fitToPayloadBounds(padding = 48) {
|
| 136 |
if (leafletRef.current?.fitToPayloadBounds) {
|
|
@@ -152,24 +191,37 @@ const MapFrame = forwardRef(function MapFrame({ html, payload = null, sessionId
|
|
| 152 |
},
|
| 153 |
}), [])
|
| 154 |
|
| 155 |
-
if (payload && payload.type === 'mesa_leaflet_payload') {
|
| 156 |
-
return <LeafletMapFrame ref={leafletRef} payload={payload} sessionId={sessionId} />
|
| 157 |
-
}
|
| 158 |
-
|
| 159 |
-
if (!html) {
|
| 160 |
return <div className="empty-box">Mapa indisponivel.</div>
|
| 161 |
}
|
| 162 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
return (
|
| 164 |
-
<
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
title="mapa"
|
| 168 |
-
className="map-frame"
|
| 169 |
-
srcDoc={html}
|
| 170 |
-
sandbox="allow-scripts allow-same-origin allow-popups"
|
| 171 |
-
onLoad={scheduleRecenter}
|
| 172 |
-
/>
|
| 173 |
)
|
| 174 |
})
|
| 175 |
|
|
|
|
| 1 |
+
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
|
| 2 |
import LeafletMapFrame from './LeafletMapFrame'
|
| 3 |
|
| 4 |
function hashHtml(value) {
|
|
|
|
| 12 |
}
|
| 13 |
|
| 14 |
const MapFrame = forwardRef(function MapFrame({ html, payload = null, sessionId = '' }, ref) {
|
| 15 |
+
const shellRef = useRef(null)
|
| 16 |
const iframeRef = useRef(null)
|
| 17 |
const leafletRef = useRef(null)
|
| 18 |
const timersRef = useRef([])
|
| 19 |
+
const [isFullscreen, setIsFullscreen] = useState(false)
|
| 20 |
const frameKey = useMemo(() => hashHtml(html || ''), [html])
|
| 21 |
|
| 22 |
const clearTimers = useCallback(() => {
|
|
|
|
| 127 |
})
|
| 128 |
}, [clearTimers, recenterFromLayers])
|
| 129 |
|
| 130 |
+
const refreshMapSize = useCallback(() => {
|
| 131 |
+
if (leafletRef.current?.fitToPayloadBounds) {
|
| 132 |
+
window.setTimeout(() => {
|
| 133 |
+
leafletRef.current?.fitToPayloadBounds?.(48)
|
| 134 |
+
}, 120)
|
| 135 |
+
return
|
| 136 |
+
}
|
| 137 |
+
scheduleRecenter()
|
| 138 |
+
}, [scheduleRecenter])
|
| 139 |
+
|
| 140 |
+
const toggleFullscreen = useCallback(async (event) => {
|
| 141 |
+
event.preventDefault()
|
| 142 |
+
event.stopPropagation()
|
| 143 |
+
const shell = shellRef.current
|
| 144 |
+
if (!shell) return
|
| 145 |
+
try {
|
| 146 |
+
if (document.fullscreenElement === shell) {
|
| 147 |
+
await document.exitFullscreen()
|
| 148 |
+
} else if (typeof shell.requestFullscreen === 'function') {
|
| 149 |
+
await shell.requestFullscreen()
|
| 150 |
+
}
|
| 151 |
+
} catch {
|
| 152 |
+
// Navegadores podem bloquear fullscreen fora de interação direta.
|
| 153 |
+
}
|
| 154 |
+
}, [])
|
| 155 |
+
|
| 156 |
useEffect(() => {
|
| 157 |
return () => {
|
| 158 |
clearTimers()
|
| 159 |
}
|
| 160 |
}, [clearTimers])
|
| 161 |
|
| 162 |
+
useEffect(() => {
|
| 163 |
+
function onFullscreenChange() {
|
| 164 |
+
const active = document.fullscreenElement === shellRef.current
|
| 165 |
+
setIsFullscreen(active)
|
| 166 |
+
refreshMapSize()
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
document.addEventListener('fullscreenchange', onFullscreenChange)
|
| 170 |
+
return () => document.removeEventListener('fullscreenchange', onFullscreenChange)
|
| 171 |
+
}, [refreshMapSize])
|
| 172 |
+
|
| 173 |
useImperativeHandle(ref, () => ({
|
| 174 |
fitToPayloadBounds(padding = 48) {
|
| 175 |
if (leafletRef.current?.fitToPayloadBounds) {
|
|
|
|
| 191 |
},
|
| 192 |
}), [])
|
| 193 |
|
| 194 |
+
if (!(payload && payload.type === 'mesa_leaflet_payload') && !html) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
return <div className="empty-box">Mapa indisponivel.</div>
|
| 196 |
}
|
| 197 |
|
| 198 |
+
const mapContent = payload && payload.type === 'mesa_leaflet_payload'
|
| 199 |
+
? (
|
| 200 |
+
<LeafletMapFrame
|
| 201 |
+
ref={leafletRef}
|
| 202 |
+
payload={payload}
|
| 203 |
+
sessionId={sessionId}
|
| 204 |
+
onToggleFullscreen={toggleFullscreen}
|
| 205 |
+
/>
|
| 206 |
+
)
|
| 207 |
+
: (
|
| 208 |
+
<iframe
|
| 209 |
+
key={frameKey}
|
| 210 |
+
ref={iframeRef}
|
| 211 |
+
title="mapa"
|
| 212 |
+
className="map-frame"
|
| 213 |
+
srcDoc={html}
|
| 214 |
+
sandbox="allow-scripts allow-same-origin allow-popups"
|
| 215 |
+
allow="fullscreen"
|
| 216 |
+
allowFullScreen
|
| 217 |
+
onLoad={scheduleRecenter}
|
| 218 |
+
/>
|
| 219 |
+
)
|
| 220 |
+
|
| 221 |
return (
|
| 222 |
+
<div ref={shellRef} className={`map-frame-shell${isFullscreen ? ' is-fullscreen' : ''}`}>
|
| 223 |
+
{mapContent}
|
| 224 |
+
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
)
|
| 226 |
})
|
| 227 |
|
frontend/src/components/PesquisaTab.jsx
CHANGED
|
@@ -2,6 +2,11 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'
|
|
| 2 |
import { createPortal } from 'react-dom'
|
| 3 |
import { api, downloadBlob } from '../api'
|
| 4 |
import { buildPesquisaLink, buildPesquisaRoutePayload, getPesquisaFilterDefaults, hasPesquisaRoutePayload } from '../deepLinks'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
import DataTable from './DataTable'
|
| 6 |
import EquationFormatsPanel from './EquationFormatsPanel'
|
| 7 |
import LoadingOverlay from './LoadingOverlay'
|
|
@@ -1051,6 +1056,8 @@ export default function PesquisaTab({
|
|
| 1051 |
const [modeloAbertoMapaPayload, setModeloAbertoMapaPayload] = useState(null)
|
| 1052 |
const [modeloAbertoMapaChoices, setModeloAbertoMapaChoices] = useState(['Visualização Padrão'])
|
| 1053 |
const [modeloAbertoMapaVar, setModeloAbertoMapaVar] = useState('Visualização Padrão')
|
|
|
|
|
|
|
| 1054 |
const [modeloAbertoTrabalhosTecnicosModelosModo, setModeloAbertoTrabalhosTecnicosModelosModo] = useState(MODELO_ABERTO_TRABALHOS_TECNICOS_PADRAO)
|
| 1055 |
const [modeloAbertoTrabalhosTecnicos, setModeloAbertoTrabalhosTecnicos] = useState([])
|
| 1056 |
const [modeloAbertoPlotObsCalc, setModeloAbertoPlotObsCalc] = useState(null)
|
|
@@ -1075,6 +1082,7 @@ export default function PesquisaTab({
|
|
| 1075 |
const modeloAbertoOpenVersionRef = useRef(0)
|
| 1076 |
|
| 1077 |
const sugestoes = result.sugestoes || {}
|
|
|
|
| 1078 |
const opcoesTipoModelo = useMemo(
|
| 1079 |
() => {
|
| 1080 |
const atual = String(filters.tipoModelo || '').trim()
|
|
@@ -1511,6 +1519,8 @@ export default function PesquisaTab({
|
|
| 1511 |
setModeloAbertoMapaPayload(null)
|
| 1512 |
setModeloAbertoMapaChoices(['Visualização Padrão'])
|
| 1513 |
setModeloAbertoMapaVar('Visualização Padrão')
|
|
|
|
|
|
|
| 1514 |
setModeloAbertoTrabalhosTecnicosModelosModo(MODELO_ABERTO_TRABALHOS_TECNICOS_PADRAO)
|
| 1515 |
setModeloAbertoTrabalhosTecnicos([])
|
| 1516 |
setModeloAbertoPlotObsCalc(null)
|
|
@@ -1691,11 +1701,14 @@ export default function PesquisaTab({
|
|
| 1691 |
async function atualizarMapaModeloAberto(
|
| 1692 |
nextVar = modeloAbertoMapaVar,
|
| 1693 |
nextTrabalhosTecnicosModo = modeloAbertoTrabalhosTecnicosModelosModo,
|
|
|
|
|
|
|
| 1694 |
) {
|
| 1695 |
if (!sessionId) return
|
| 1696 |
setModeloAbertoLoadingTabs((prev) => ({ ...prev, mapa: true }))
|
| 1697 |
try {
|
| 1698 |
-
const
|
|
|
|
| 1699 |
setModeloAbertoMapaHtml(resp?.mapa_html || '')
|
| 1700 |
setModeloAbertoMapaPayload(resp?.mapa_payload || null)
|
| 1701 |
setModeloAbertoTrabalhosTecnicos(Array.isArray(resp?.trabalhos_tecnicos) ? resp.trabalhos_tecnicos : [])
|
|
@@ -1706,6 +1719,19 @@ export default function PesquisaTab({
|
|
| 1706 |
}
|
| 1707 |
}
|
| 1708 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1709 |
async function onModeloAbertoMapChange(nextVar) {
|
| 1710 |
setModeloAbertoMapaVar(nextVar)
|
| 1711 |
try {
|
|
@@ -1931,6 +1957,32 @@ export default function PesquisaTab({
|
|
| 1931 |
<option value="selecionados_e_outras_versoes">Incluir demais versões do modelo</option>
|
| 1932 |
</select>
|
| 1933 |
</label>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1934 |
</div>
|
| 1935 |
<MapFrame html={modeloAbertoMapaHtml} payload={modeloAbertoMapaPayload} sessionId={sessionId} />
|
| 1936 |
</>
|
|
|
|
| 2 |
import { createPortal } from 'react-dom'
|
| 3 |
import { api, downloadBlob } from '../api'
|
| 4 |
import { buildPesquisaLink, buildPesquisaRoutePayload, getPesquisaFilterDefaults, hasPesquisaRoutePayload } from '../deepLinks'
|
| 5 |
+
import {
|
| 6 |
+
MAP_POINT_RADIUS_MAX_DEFAULT,
|
| 7 |
+
MAP_POINT_RADIUS_MIN_DEFAULT,
|
| 8 |
+
normalizeMapPointRadiusRange,
|
| 9 |
+
} from '../mapPointSizing'
|
| 10 |
import DataTable from './DataTable'
|
| 11 |
import EquationFormatsPanel from './EquationFormatsPanel'
|
| 12 |
import LoadingOverlay from './LoadingOverlay'
|
|
|
|
| 1056 |
const [modeloAbertoMapaPayload, setModeloAbertoMapaPayload] = useState(null)
|
| 1057 |
const [modeloAbertoMapaChoices, setModeloAbertoMapaChoices] = useState(['Visualização Padrão'])
|
| 1058 |
const [modeloAbertoMapaVar, setModeloAbertoMapaVar] = useState('Visualização Padrão')
|
| 1059 |
+
const [modeloAbertoMapaRaioMin, setModeloAbertoMapaRaioMin] = useState(String(MAP_POINT_RADIUS_MIN_DEFAULT))
|
| 1060 |
+
const [modeloAbertoMapaRaioMax, setModeloAbertoMapaRaioMax] = useState(String(MAP_POINT_RADIUS_MAX_DEFAULT))
|
| 1061 |
const [modeloAbertoTrabalhosTecnicosModelosModo, setModeloAbertoTrabalhosTecnicosModelosModo] = useState(MODELO_ABERTO_TRABALHOS_TECNICOS_PADRAO)
|
| 1062 |
const [modeloAbertoTrabalhosTecnicos, setModeloAbertoTrabalhosTecnicos] = useState([])
|
| 1063 |
const [modeloAbertoPlotObsCalc, setModeloAbertoPlotObsCalc] = useState(null)
|
|
|
|
| 1082 |
const modeloAbertoOpenVersionRef = useRef(0)
|
| 1083 |
|
| 1084 |
const sugestoes = result.sugestoes || {}
|
| 1085 |
+
const modeloAbertoMapaRaioDisponivel = modeloAbertoMapaVar !== 'Visualização Padrão'
|
| 1086 |
const opcoesTipoModelo = useMemo(
|
| 1087 |
() => {
|
| 1088 |
const atual = String(filters.tipoModelo || '').trim()
|
|
|
|
| 1519 |
setModeloAbertoMapaPayload(null)
|
| 1520 |
setModeloAbertoMapaChoices(['Visualização Padrão'])
|
| 1521 |
setModeloAbertoMapaVar('Visualização Padrão')
|
| 1522 |
+
setModeloAbertoMapaRaioMin(String(MAP_POINT_RADIUS_MIN_DEFAULT))
|
| 1523 |
+
setModeloAbertoMapaRaioMax(String(MAP_POINT_RADIUS_MAX_DEFAULT))
|
| 1524 |
setModeloAbertoTrabalhosTecnicosModelosModo(MODELO_ABERTO_TRABALHOS_TECNICOS_PADRAO)
|
| 1525 |
setModeloAbertoTrabalhosTecnicos([])
|
| 1526 |
setModeloAbertoPlotObsCalc(null)
|
|
|
|
| 1701 |
async function atualizarMapaModeloAberto(
|
| 1702 |
nextVar = modeloAbertoMapaVar,
|
| 1703 |
nextTrabalhosTecnicosModo = modeloAbertoTrabalhosTecnicosModelosModo,
|
| 1704 |
+
nextRaioMin = modeloAbertoMapaRaioMin,
|
| 1705 |
+
nextRaioMax = modeloAbertoMapaRaioMax,
|
| 1706 |
) {
|
| 1707 |
if (!sessionId) return
|
| 1708 |
setModeloAbertoLoadingTabs((prev) => ({ ...prev, mapa: true }))
|
| 1709 |
try {
|
| 1710 |
+
const raioRange = normalizeMapPointRadiusRange(nextRaioMin, nextRaioMax)
|
| 1711 |
+
const resp = await api.updateVisualizacaoMap(sessionId, nextVar, nextTrabalhosTecnicosModo, raioRange.min, raioRange.max)
|
| 1712 |
setModeloAbertoMapaHtml(resp?.mapa_html || '')
|
| 1713 |
setModeloAbertoMapaPayload(resp?.mapa_payload || null)
|
| 1714 |
setModeloAbertoTrabalhosTecnicos(Array.isArray(resp?.trabalhos_tecnicos) ? resp.trabalhos_tecnicos : [])
|
|
|
|
| 1719 |
}
|
| 1720 |
}
|
| 1721 |
|
| 1722 |
+
async function onModeloAbertoMapRadiusChange(kind, value) {
|
| 1723 |
+
const nextMin = kind === 'min' ? value : modeloAbertoMapaRaioMin
|
| 1724 |
+
const nextMax = kind === 'max' ? value : modeloAbertoMapaRaioMax
|
| 1725 |
+
if (kind === 'min') setModeloAbertoMapaRaioMin(value)
|
| 1726 |
+
else setModeloAbertoMapaRaioMax(value)
|
| 1727 |
+
if (!modeloAbertoMapaRaioDisponivel) return
|
| 1728 |
+
try {
|
| 1729 |
+
await atualizarMapaModeloAberto(modeloAbertoMapaVar, modeloAbertoTrabalhosTecnicosModelosModo, nextMin, nextMax)
|
| 1730 |
+
} catch (err) {
|
| 1731 |
+
setModeloAbertoError(err.message || 'Falha ao atualizar tamanho dos pontos.')
|
| 1732 |
+
}
|
| 1733 |
+
}
|
| 1734 |
+
|
| 1735 |
async function onModeloAbertoMapChange(nextVar) {
|
| 1736 |
setModeloAbertoMapaVar(nextVar)
|
| 1737 |
try {
|
|
|
|
| 1957 |
<option value="selecionados_e_outras_versoes">Incluir demais versões do modelo</option>
|
| 1958 |
</select>
|
| 1959 |
</label>
|
| 1960 |
+
{modeloAbertoMapaRaioDisponivel ? (
|
| 1961 |
+
<>
|
| 1962 |
+
<label className="pesquisa-field pesquisa-mapa-radius-field">
|
| 1963 |
+
Raio mínimo
|
| 1964 |
+
<input
|
| 1965 |
+
type="number"
|
| 1966 |
+
min="1"
|
| 1967 |
+
max="64"
|
| 1968 |
+
step="0.5"
|
| 1969 |
+
value={modeloAbertoMapaRaioMin}
|
| 1970 |
+
onChange={(event) => void onModeloAbertoMapRadiusChange('min', event.target.value)}
|
| 1971 |
+
/>
|
| 1972 |
+
</label>
|
| 1973 |
+
<label className="pesquisa-field pesquisa-mapa-radius-field">
|
| 1974 |
+
Raio máximo
|
| 1975 |
+
<input
|
| 1976 |
+
type="number"
|
| 1977 |
+
min="1"
|
| 1978 |
+
max="64"
|
| 1979 |
+
step="0.5"
|
| 1980 |
+
value={modeloAbertoMapaRaioMax}
|
| 1981 |
+
onChange={(event) => void onModeloAbertoMapRadiusChange('max', event.target.value)}
|
| 1982 |
+
/>
|
| 1983 |
+
</label>
|
| 1984 |
+
</>
|
| 1985 |
+
) : null}
|
| 1986 |
</div>
|
| 1987 |
<MapFrame html={modeloAbertoMapaHtml} payload={modeloAbertoMapaPayload} sessionId={sessionId} />
|
| 1988 |
</>
|
frontend/src/components/RepositorioTab.jsx
CHANGED
|
@@ -1,6 +1,11 @@
|
|
| 1 |
import React, { useEffect, useRef, useState } from 'react'
|
| 2 |
import { api, downloadBlob } from '../api'
|
| 3 |
import { buildRepositorioModeloLink, hasMesaDeepLink, normalizeMesaDeepLink, parseMesaDeepLink } from '../deepLinks'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
import DataTable from './DataTable'
|
| 5 |
import EquationFormatsPanel from './EquationFormatsPanel'
|
| 6 |
import ListPagination from './ListPagination'
|
|
@@ -191,6 +196,8 @@ export default function RepositorioTab({
|
|
| 191 |
const [modeloAbertoMapaPayload, setModeloAbertoMapaPayload] = useState(null)
|
| 192 |
const [modeloAbertoMapaChoices, setModeloAbertoMapaChoices] = useState(['Visualização Padrão'])
|
| 193 |
const [modeloAbertoMapaVar, setModeloAbertoMapaVar] = useState('Visualização Padrão')
|
|
|
|
|
|
|
| 194 |
const [modeloAbertoTrabalhosTecnicosModelosModo, setModeloAbertoTrabalhosTecnicosModelosModo] = useState(MODELO_ABERTO_TRABALHOS_TECNICOS_PADRAO)
|
| 195 |
const [modeloAbertoTrabalhosTecnicos, setModeloAbertoTrabalhosTecnicos] = useState([])
|
| 196 |
const [modeloAbertoPlotObsCalc, setModeloAbertoPlotObsCalc] = useState(null)
|
|
@@ -207,6 +214,7 @@ export default function RepositorioTab({
|
|
| 207 |
const lastOpenRequestKeyRef = useRef('')
|
| 208 |
const pendingTabRequestsRef = useRef({})
|
| 209 |
const modeloOpenVersionRef = useRef(0)
|
|
|
|
| 210 |
|
| 211 |
const isAdmin = String(authUser?.perfil || '').toLowerCase() === 'admin'
|
| 212 |
const totalModelos = modelos.length
|
|
@@ -372,6 +380,8 @@ export default function RepositorioTab({
|
|
| 372 |
setModeloAbertoMapaPayload(null)
|
| 373 |
setModeloAbertoMapaChoices(['Visualização Padrão'])
|
| 374 |
setModeloAbertoMapaVar('Visualização Padrão')
|
|
|
|
|
|
|
| 375 |
setModeloAbertoTrabalhosTecnicosModelosModo(MODELO_ABERTO_TRABALHOS_TECNICOS_PADRAO)
|
| 376 |
setModeloAbertoTrabalhosTecnicos([])
|
| 377 |
setModeloAbertoPlotObsCalc(null)
|
|
@@ -580,11 +590,14 @@ export default function RepositorioTab({
|
|
| 580 |
async function atualizarMapaModeloAberto(
|
| 581 |
nextVar = modeloAbertoMapaVar,
|
| 582 |
nextTrabalhosTecnicosModo = modeloAbertoTrabalhosTecnicosModelosModo,
|
|
|
|
|
|
|
| 583 |
) {
|
| 584 |
if (!sessionId) return
|
| 585 |
setModeloAbertoLoadingTabs((prev) => ({ ...prev, mapa: true }))
|
| 586 |
try {
|
| 587 |
-
const
|
|
|
|
| 588 |
setModeloAbertoMapaHtml(resp?.mapa_html || '')
|
| 589 |
setModeloAbertoMapaPayload(resp?.mapa_payload || null)
|
| 590 |
setModeloAbertoTrabalhosTecnicos(Array.isArray(resp?.trabalhos_tecnicos) ? resp.trabalhos_tecnicos : [])
|
|
@@ -595,6 +608,19 @@ export default function RepositorioTab({
|
|
| 595 |
}
|
| 596 |
}
|
| 597 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 598 |
async function onModeloAbertoMapChange(nextVar) {
|
| 599 |
setModeloAbertoMapaVar(nextVar)
|
| 600 |
try {
|
|
@@ -728,6 +754,32 @@ export default function RepositorioTab({
|
|
| 728 |
<option value="selecionados_e_outras_versoes">Incluir demais versões do modelo</option>
|
| 729 |
</select>
|
| 730 |
</label>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 731 |
</div>
|
| 732 |
<MapFrame html={modeloAbertoMapaHtml} payload={modeloAbertoMapaPayload} sessionId={sessionId} />
|
| 733 |
</>
|
|
|
|
| 1 |
import React, { useEffect, useRef, useState } from 'react'
|
| 2 |
import { api, downloadBlob } from '../api'
|
| 3 |
import { buildRepositorioModeloLink, hasMesaDeepLink, normalizeMesaDeepLink, parseMesaDeepLink } from '../deepLinks'
|
| 4 |
+
import {
|
| 5 |
+
MAP_POINT_RADIUS_MAX_DEFAULT,
|
| 6 |
+
MAP_POINT_RADIUS_MIN_DEFAULT,
|
| 7 |
+
normalizeMapPointRadiusRange,
|
| 8 |
+
} from '../mapPointSizing'
|
| 9 |
import DataTable from './DataTable'
|
| 10 |
import EquationFormatsPanel from './EquationFormatsPanel'
|
| 11 |
import ListPagination from './ListPagination'
|
|
|
|
| 196 |
const [modeloAbertoMapaPayload, setModeloAbertoMapaPayload] = useState(null)
|
| 197 |
const [modeloAbertoMapaChoices, setModeloAbertoMapaChoices] = useState(['Visualização Padrão'])
|
| 198 |
const [modeloAbertoMapaVar, setModeloAbertoMapaVar] = useState('Visualização Padrão')
|
| 199 |
+
const [modeloAbertoMapaRaioMin, setModeloAbertoMapaRaioMin] = useState(String(MAP_POINT_RADIUS_MIN_DEFAULT))
|
| 200 |
+
const [modeloAbertoMapaRaioMax, setModeloAbertoMapaRaioMax] = useState(String(MAP_POINT_RADIUS_MAX_DEFAULT))
|
| 201 |
const [modeloAbertoTrabalhosTecnicosModelosModo, setModeloAbertoTrabalhosTecnicosModelosModo] = useState(MODELO_ABERTO_TRABALHOS_TECNICOS_PADRAO)
|
| 202 |
const [modeloAbertoTrabalhosTecnicos, setModeloAbertoTrabalhosTecnicos] = useState([])
|
| 203 |
const [modeloAbertoPlotObsCalc, setModeloAbertoPlotObsCalc] = useState(null)
|
|
|
|
| 214 |
const lastOpenRequestKeyRef = useRef('')
|
| 215 |
const pendingTabRequestsRef = useRef({})
|
| 216 |
const modeloOpenVersionRef = useRef(0)
|
| 217 |
+
const modeloAbertoMapaRaioDisponivel = modeloAbertoMapaVar !== 'Visualização Padrão'
|
| 218 |
|
| 219 |
const isAdmin = String(authUser?.perfil || '').toLowerCase() === 'admin'
|
| 220 |
const totalModelos = modelos.length
|
|
|
|
| 380 |
setModeloAbertoMapaPayload(null)
|
| 381 |
setModeloAbertoMapaChoices(['Visualização Padrão'])
|
| 382 |
setModeloAbertoMapaVar('Visualização Padrão')
|
| 383 |
+
setModeloAbertoMapaRaioMin(String(MAP_POINT_RADIUS_MIN_DEFAULT))
|
| 384 |
+
setModeloAbertoMapaRaioMax(String(MAP_POINT_RADIUS_MAX_DEFAULT))
|
| 385 |
setModeloAbertoTrabalhosTecnicosModelosModo(MODELO_ABERTO_TRABALHOS_TECNICOS_PADRAO)
|
| 386 |
setModeloAbertoTrabalhosTecnicos([])
|
| 387 |
setModeloAbertoPlotObsCalc(null)
|
|
|
|
| 590 |
async function atualizarMapaModeloAberto(
|
| 591 |
nextVar = modeloAbertoMapaVar,
|
| 592 |
nextTrabalhosTecnicosModo = modeloAbertoTrabalhosTecnicosModelosModo,
|
| 593 |
+
nextRaioMin = modeloAbertoMapaRaioMin,
|
| 594 |
+
nextRaioMax = modeloAbertoMapaRaioMax,
|
| 595 |
) {
|
| 596 |
if (!sessionId) return
|
| 597 |
setModeloAbertoLoadingTabs((prev) => ({ ...prev, mapa: true }))
|
| 598 |
try {
|
| 599 |
+
const raioRange = normalizeMapPointRadiusRange(nextRaioMin, nextRaioMax)
|
| 600 |
+
const resp = await api.updateVisualizacaoMap(sessionId, nextVar, nextTrabalhosTecnicosModo, raioRange.min, raioRange.max)
|
| 601 |
setModeloAbertoMapaHtml(resp?.mapa_html || '')
|
| 602 |
setModeloAbertoMapaPayload(resp?.mapa_payload || null)
|
| 603 |
setModeloAbertoTrabalhosTecnicos(Array.isArray(resp?.trabalhos_tecnicos) ? resp.trabalhos_tecnicos : [])
|
|
|
|
| 608 |
}
|
| 609 |
}
|
| 610 |
|
| 611 |
+
async function onModeloAbertoMapRadiusChange(kind, value) {
|
| 612 |
+
const nextMin = kind === 'min' ? value : modeloAbertoMapaRaioMin
|
| 613 |
+
const nextMax = kind === 'max' ? value : modeloAbertoMapaRaioMax
|
| 614 |
+
if (kind === 'min') setModeloAbertoMapaRaioMin(value)
|
| 615 |
+
else setModeloAbertoMapaRaioMax(value)
|
| 616 |
+
if (!modeloAbertoMapaRaioDisponivel) return
|
| 617 |
+
try {
|
| 618 |
+
await atualizarMapaModeloAberto(modeloAbertoMapaVar, modeloAbertoTrabalhosTecnicosModelosModo, nextMin, nextMax)
|
| 619 |
+
} catch (err) {
|
| 620 |
+
setModeloAbertoError(err.message || 'Falha ao atualizar tamanho dos pontos.')
|
| 621 |
+
}
|
| 622 |
+
}
|
| 623 |
+
|
| 624 |
async function onModeloAbertoMapChange(nextVar) {
|
| 625 |
setModeloAbertoMapaVar(nextVar)
|
| 626 |
try {
|
|
|
|
| 754 |
<option value="selecionados_e_outras_versoes">Incluir demais versões do modelo</option>
|
| 755 |
</select>
|
| 756 |
</label>
|
| 757 |
+
{modeloAbertoMapaRaioDisponivel ? (
|
| 758 |
+
<>
|
| 759 |
+
<label className="pesquisa-field pesquisa-mapa-radius-field">
|
| 760 |
+
Raio mínimo
|
| 761 |
+
<input
|
| 762 |
+
type="number"
|
| 763 |
+
min="1"
|
| 764 |
+
max="64"
|
| 765 |
+
step="0.5"
|
| 766 |
+
value={modeloAbertoMapaRaioMin}
|
| 767 |
+
onChange={(event) => void onModeloAbertoMapRadiusChange('min', event.target.value)}
|
| 768 |
+
/>
|
| 769 |
+
</label>
|
| 770 |
+
<label className="pesquisa-field pesquisa-mapa-radius-field">
|
| 771 |
+
Raio máximo
|
| 772 |
+
<input
|
| 773 |
+
type="number"
|
| 774 |
+
min="1"
|
| 775 |
+
max="64"
|
| 776 |
+
step="0.5"
|
| 777 |
+
value={modeloAbertoMapaRaioMax}
|
| 778 |
+
onChange={(event) => void onModeloAbertoMapRadiusChange('max', event.target.value)}
|
| 779 |
+
/>
|
| 780 |
+
</label>
|
| 781 |
+
</>
|
| 782 |
+
) : null}
|
| 783 |
</div>
|
| 784 |
<MapFrame html={modeloAbertoMapaHtml} payload={modeloAbertoMapaPayload} sessionId={sessionId} />
|
| 785 |
</>
|
frontend/src/mapPointSizing.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export const MAP_POINT_RADIUS_MIN_DEFAULT = 3
|
| 2 |
+
export const MAP_POINT_RADIUS_MAX_DEFAULT = 18
|
| 3 |
+
export const MAP_POINT_RADIUS_MIN_LIMIT = 1
|
| 4 |
+
export const MAP_POINT_RADIUS_MAX_LIMIT = 64
|
| 5 |
+
|
| 6 |
+
function parseFiniteNumber(value, fallback) {
|
| 7 |
+
if (value === null || value === undefined || value === '') return fallback
|
| 8 |
+
const parsed = Number(String(value).trim().replace(',', '.'))
|
| 9 |
+
return Number.isFinite(parsed) ? parsed : fallback
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
export function normalizeMapPointRadiusRange(minValue, maxValue) {
|
| 13 |
+
let min = parseFiniteNumber(minValue, MAP_POINT_RADIUS_MIN_DEFAULT)
|
| 14 |
+
let max = parseFiniteNumber(maxValue, MAP_POINT_RADIUS_MAX_DEFAULT)
|
| 15 |
+
min = Math.min(MAP_POINT_RADIUS_MAX_LIMIT, Math.max(MAP_POINT_RADIUS_MIN_LIMIT, min))
|
| 16 |
+
max = Math.min(MAP_POINT_RADIUS_MAX_LIMIT, Math.max(MAP_POINT_RADIUS_MIN_LIMIT, max))
|
| 17 |
+
if (min > max) {
|
| 18 |
+
const temp = min
|
| 19 |
+
min = max
|
| 20 |
+
max = temp
|
| 21 |
+
}
|
| 22 |
+
return { min, max }
|
| 23 |
+
}
|
frontend/src/styles.css
CHANGED
|
@@ -2161,11 +2161,17 @@ textarea {
|
|
| 2161 |
margin: 0;
|
| 2162 |
}
|
| 2163 |
|
| 2164 |
-
.dados-mapa-control-field select
|
|
|
|
| 2165 |
width: 100%;
|
| 2166 |
}
|
| 2167 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2168 |
.dados-mapa-controls + .map-frame,
|
|
|
|
| 2169 |
.dados-mapa-controls + .empty-box {
|
| 2170 |
margin-top: 6px;
|
| 2171 |
}
|
|
@@ -2262,6 +2268,7 @@ textarea {
|
|
| 2262 |
}
|
| 2263 |
|
| 2264 |
.visualizacao-mapa-controls + .map-frame,
|
|
|
|
| 2265 |
.visualizacao-mapa-controls + .empty-box {
|
| 2266 |
margin-top: 10px;
|
| 2267 |
}
|
|
@@ -5651,6 +5658,37 @@ button.import-preview-clear-btn {
|
|
| 5651 |
background: #fff;
|
| 5652 |
}
|
| 5653 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5654 |
.leaflet-map-host {
|
| 5655 |
position: relative;
|
| 5656 |
overflow: hidden;
|
|
@@ -5995,7 +6033,7 @@ button.import-preview-clear-btn {
|
|
| 5995 |
border-top-color: rgba(207, 217, 227, 0.95);
|
| 5996 |
}
|
| 5997 |
|
| 5998 |
-
.leaflet-map-host .leaflet-
|
| 5999 |
.leaflet-map-host .leaflet-control-measure {
|
| 6000 |
border: none;
|
| 6001 |
box-shadow: 0 10px 24px rgba(27, 48, 72, 0.16);
|
|
@@ -6025,6 +6063,44 @@ button.import-preview-clear-btn {
|
|
| 6025 |
box-shadow: none;
|
| 6026 |
}
|
| 6027 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6028 |
.leaflet-map-host .mesa-leaflet-measure .mesa-leaflet-measure-toggle {
|
| 6029 |
width: 38px;
|
| 6030 |
height: 38px;
|
|
|
|
| 2161 |
margin: 0;
|
| 2162 |
}
|
| 2163 |
|
| 2164 |
+
.dados-mapa-control-field select,
|
| 2165 |
+
.dados-mapa-control-field input {
|
| 2166 |
width: 100%;
|
| 2167 |
}
|
| 2168 |
|
| 2169 |
+
.dados-mapa-radius-field {
|
| 2170 |
+
min-width: 128px;
|
| 2171 |
+
}
|
| 2172 |
+
|
| 2173 |
.dados-mapa-controls + .map-frame,
|
| 2174 |
+
.dados-mapa-controls + .map-frame-shell,
|
| 2175 |
.dados-mapa-controls + .empty-box {
|
| 2176 |
margin-top: 6px;
|
| 2177 |
}
|
|
|
|
| 2268 |
}
|
| 2269 |
|
| 2270 |
.visualizacao-mapa-controls + .map-frame,
|
| 2271 |
+
.visualizacao-mapa-controls + .map-frame-shell,
|
| 2272 |
.visualizacao-mapa-controls + .empty-box {
|
| 2273 |
margin-top: 10px;
|
| 2274 |
}
|
|
|
|
| 5658 |
background: #fff;
|
| 5659 |
}
|
| 5660 |
|
| 5661 |
+
.map-frame-shell {
|
| 5662 |
+
position: relative;
|
| 5663 |
+
width: 100%;
|
| 5664 |
+
max-width: 100%;
|
| 5665 |
+
}
|
| 5666 |
+
|
| 5667 |
+
.map-frame-shell .map-frame {
|
| 5668 |
+
margin: 0;
|
| 5669 |
+
}
|
| 5670 |
+
|
| 5671 |
+
.map-frame-shell:fullscreen {
|
| 5672 |
+
display: flex;
|
| 5673 |
+
width: 100vw;
|
| 5674 |
+
height: 100vh;
|
| 5675 |
+
padding: 14px;
|
| 5676 |
+
box-sizing: border-box;
|
| 5677 |
+
background: #eef4f8;
|
| 5678 |
+
}
|
| 5679 |
+
|
| 5680 |
+
.map-frame-shell:fullscreen .map-frame {
|
| 5681 |
+
flex: 1;
|
| 5682 |
+
min-height: calc(100vh - 28px);
|
| 5683 |
+
height: calc(100vh - 28px);
|
| 5684 |
+
}
|
| 5685 |
+
|
| 5686 |
+
.map-frame-shell:fullscreen .leaflet-map-canvas,
|
| 5687 |
+
.map-frame-shell:fullscreen .leaflet-map-host .leaflet-container {
|
| 5688 |
+
min-height: calc(100vh - 28px);
|
| 5689 |
+
height: calc(100vh - 28px);
|
| 5690 |
+
}
|
| 5691 |
+
|
| 5692 |
.leaflet-map-host {
|
| 5693 |
position: relative;
|
| 5694 |
overflow: hidden;
|
|
|
|
| 6033 |
border-top-color: rgba(207, 217, 227, 0.95);
|
| 6034 |
}
|
| 6035 |
|
| 6036 |
+
.leaflet-map-host .mesa-leaflet-fullscreen,
|
| 6037 |
.leaflet-map-host .leaflet-control-measure {
|
| 6038 |
border: none;
|
| 6039 |
box-shadow: 0 10px 24px rgba(27, 48, 72, 0.16);
|
|
|
|
| 6063 |
box-shadow: none;
|
| 6064 |
}
|
| 6065 |
|
| 6066 |
+
.leaflet-map-host .mesa-leaflet-fullscreen {
|
| 6067 |
+
background: transparent;
|
| 6068 |
+
border: none;
|
| 6069 |
+
box-shadow: none;
|
| 6070 |
+
}
|
| 6071 |
+
|
| 6072 |
+
.leaflet-map-host .mesa-leaflet-fullscreen .mesa-leaflet-fullscreen-toggle {
|
| 6073 |
+
width: 38px;
|
| 6074 |
+
height: 38px;
|
| 6075 |
+
position: relative;
|
| 6076 |
+
display: block;
|
| 6077 |
+
background: rgba(255, 255, 255, 0.96);
|
| 6078 |
+
border: 1px solid rgba(193, 207, 220, 0.95);
|
| 6079 |
+
border-radius: 10px;
|
| 6080 |
+
box-shadow: 0 10px 24px rgba(27, 48, 72, 0.16);
|
| 6081 |
+
}
|
| 6082 |
+
|
| 6083 |
+
.leaflet-map-host .mesa-leaflet-fullscreen .mesa-leaflet-fullscreen-toggle::before {
|
| 6084 |
+
content: '';
|
| 6085 |
+
position: absolute;
|
| 6086 |
+
inset: 0;
|
| 6087 |
+
margin: auto;
|
| 6088 |
+
width: 18px;
|
| 6089 |
+
height: 18px;
|
| 6090 |
+
background-repeat: no-repeat;
|
| 6091 |
+
background-position: center;
|
| 6092 |
+
background-size: 18px 18px;
|
| 6093 |
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2324405b' d='M5 5h6v2H8.41l3.3 3.29-1.42 1.42L7 8.41V11H5V5Zm8 0h6v6h-2V8.41l-3.29 3.3-1.42-1.42L15.59 7H13V5Zm-2 14H5v-6h2v2.59l3.29-3.3 1.42 1.42L8.41 17H11v2Zm8 0h-6v-2h2.59l-3.3-3.29 1.42-1.42 3.29 3.3V13h2v6Z'/%3E%3C/svg%3E");
|
| 6094 |
+
}
|
| 6095 |
+
|
| 6096 |
+
.leaflet-map-host .mesa-leaflet-fullscreen.is-active .mesa-leaflet-fullscreen-toggle::before {
|
| 6097 |
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%2324405b' d='M9 3h2v6H5V7h2.59L4.29 3.71 5.71 2.29 9 5.59V3Zm6 0v2.59l3.29-3.3 1.42 1.42L16.41 7H19v2h-6V3h2ZM5 15h6v6H9v-2.59l-3.29 3.3-1.42-1.42L7.59 17H5v-2Zm14 0v2h-2.59l3.3 3.29-1.42 1.42-3.29-3.3V21h-2v-6h6Z'/%3E%3C/svg%3E");
|
| 6098 |
+
}
|
| 6099 |
+
|
| 6100 |
+
.leaflet-map-host .mesa-leaflet-fullscreen .mesa-leaflet-fullscreen-toggle:hover {
|
| 6101 |
+
background: #eef5fb;
|
| 6102 |
+
}
|
| 6103 |
+
|
| 6104 |
.leaflet-map-host .mesa-leaflet-measure .mesa-leaflet-measure-toggle {
|
| 6105 |
width: 38px;
|
| 6106 |
height: 38px;
|