Guilherme Silberfarb Costa commited on
Commit
4678b81
·
1 Parent(s): 7031c42

Speed up logradouro catalog loading

Browse files
backend/app/core/shapefile_runtime.py CHANGED
@@ -30,6 +30,14 @@ def _build_transformer(source_crs: Any, target_crs: str | None):
30
  return None
31
 
32
 
 
 
 
 
 
 
 
 
33
  def _iter_features_pyshp(shapefile_path: Path, *, target_crs: str | None) -> list[tuple[dict[str, Any], Any]]:
34
  import shapefile
35
  from shapely.geometry import shape
@@ -73,7 +81,7 @@ def _iter_features_pyshp(shapefile_path: Path, *, target_crs: str | None) -> lis
73
  encoding="utf-8",
74
  encodingErrors="replace",
75
  )
76
- field_names = [str(field[0]) for field in reader.fields[1:]]
77
  source_crs = None
78
  prj_path = shapefile_path.with_suffix(".prj")
79
  if prj_path.exists():
@@ -214,3 +222,42 @@ def load_vector_geojson(
214
  )
215
 
216
  return feature_collection
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  return None
31
 
32
 
33
+ def _field_name(field: Any) -> str:
34
+ if hasattr(field, "name"):
35
+ return str(getattr(field, "name") or "")
36
+ if isinstance(field, (list, tuple)) and field:
37
+ return str(field[0] or "")
38
+ return str(field or "")
39
+
40
+
41
  def _iter_features_pyshp(shapefile_path: Path, *, target_crs: str | None) -> list[tuple[dict[str, Any], Any]]:
42
  import shapefile
43
  from shapely.geometry import shape
 
81
  encoding="utf-8",
82
  encodingErrors="replace",
83
  )
84
+ field_names = [_field_name(field) for field in reader.fields[1:]]
85
  source_crs = None
86
  prj_path = shapefile_path.with_suffix(".prj")
87
  if prj_path.exists():
 
222
  )
223
 
224
  return feature_collection
225
+
226
+
227
+ def load_attribute_records(
228
+ shapefile_path: str | Path,
229
+ *,
230
+ property_fields: tuple[str, ...] | list[str] | None = None,
231
+ ) -> list[dict[str, Any]]:
232
+ path = Path(shapefile_path).expanduser().resolve()
233
+ dbf_path = path.with_suffix(".dbf")
234
+ if not dbf_path.exists():
235
+ raise FileNotFoundError(f"DBF nao encontrado: {dbf_path}")
236
+
237
+ wanted_fields = tuple(str(field).strip() for field in (property_fields or ()) if str(field).strip())
238
+
239
+ try:
240
+ import shapefile
241
+ except Exception:
242
+ records: list[dict[str, Any]] = []
243
+ for properties, _ in _load_features(path, target_crs=None):
244
+ if wanted_fields:
245
+ records.append({field: properties.get(field) for field in wanted_fields if field in properties})
246
+ else:
247
+ records.append(dict(properties))
248
+ return records
249
+
250
+ with open(dbf_path, "rb") as dbf_handle:
251
+ reader = shapefile.Reader(dbf=dbf_handle, encoding="utf-8", encodingErrors="replace")
252
+ field_names = [_field_name(field) for field in reader.fields[1:]]
253
+ records: list[dict[str, Any]] = []
254
+ for record in reader.iterRecords():
255
+ values = list(record)
256
+ row = {
257
+ field_name: values[index] if index < len(values) else None
258
+ for index, field_name in enumerate(field_names)
259
+ }
260
+ if wanted_fields:
261
+ row = {field: row.get(field) for field in wanted_fields if field in row}
262
+ records.append(row)
263
+ return records
backend/app/services/pesquisa_service.py CHANGED
@@ -20,6 +20,7 @@ from fastapi import HTTPException
20
  from joblib import load
21
 
22
  from app.core.elaboracao import geocodificacao
 
23
  from app.core.elaboracao.core import _migrar_pacote_v1_para_v2, normalizar_observacao_modelo
24
  from app.core.map_layers import (
25
  add_bairros_layer,
@@ -27,6 +28,7 @@ from app.core.map_layers import (
27
  add_zoom_responsive_circle_markers,
28
  )
29
  from app.portable_runtime_log import append_runtime_log
 
30
  from app.services import model_repository, trabalhos_tecnicos_service
31
  from app.services.serializers import sanitize_value
32
 
@@ -2566,6 +2568,44 @@ def _carregar_catalogo_vias() -> list[dict[str, Any]]:
2566
  if _CATALOGO_VIAS_CACHE is not None:
2567
  return list(_CATALOGO_VIAS_CACHE)
2568
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2569
  gdf = geocodificacao.carregar_eixos()
2570
  if gdf is None or gdf.empty:
2571
  append_runtime_log("[mesa] pesquisa: base de eixos indisponivel para catalogo de logradouros")
 
20
  from joblib import load
21
 
22
  from app.core.elaboracao import geocodificacao
23
+ from app.core.shapefile_runtime import load_attribute_records
24
  from app.core.elaboracao.core import _migrar_pacote_v1_para_v2, normalizar_observacao_modelo
25
  from app.core.map_layers import (
26
  add_bairros_layer,
 
28
  add_zoom_responsive_circle_markers,
29
  )
30
  from app.portable_runtime_log import append_runtime_log
31
+ from app.runtime_config import resolve_core_path
32
  from app.services import model_repository, trabalhos_tecnicos_service
33
  from app.services.serializers import sanitize_value
34
 
 
2568
  if _CATALOGO_VIAS_CACHE is not None:
2569
  return list(_CATALOGO_VIAS_CACHE)
2570
 
2571
+ registros: list[dict[str, Any]] = []
2572
+ try:
2573
+ registros = load_attribute_records(
2574
+ resolve_core_path("dados", "EixosLogradouros.shp"),
2575
+ property_fields=("CDLOG", "NMIDELOG", "NMIDEPRE", "NMIDEABR"),
2576
+ )
2577
+ append_runtime_log(f"[mesa] pesquisa: catalogo leve de logradouros carregado com {len(registros)} registros")
2578
+ except Exception as exc:
2579
+ append_runtime_log(f"[mesa] pesquisa: falha no catalogo leve de logradouros: {exc}")
2580
+
2581
+ def _catalogo_from_rows(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
2582
+ vistos: set[int] = set()
2583
+ catalogo_local: list[dict[str, Any]] = []
2584
+ for row in rows:
2585
+ cdlog = _to_int_or_none(row.get("CDLOG"))
2586
+ if cdlog is None or cdlog in vistos:
2587
+ continue
2588
+ vistos.add(cdlog)
2589
+ nome = str(row.get("NMIDELOG") or "").strip()
2590
+ prefixo = str(row.get("NMIDEPRE") or "").strip()
2591
+ abreviado = str(row.get("NMIDEABR") or "").strip()
2592
+ logradouro = " ".join(item for item in [prefixo, nome] if item).strip() or nome or abreviado
2593
+ aliases = _dedupe_strings([logradouro, nome, abreviado])
2594
+ catalogo_local.append(
2595
+ {
2596
+ "cdlog": cdlog,
2597
+ "logradouro": logradouro,
2598
+ "_aliases_norm": [_normalize(item) for item in aliases if _normalize(item)],
2599
+ }
2600
+ )
2601
+ return catalogo_local
2602
+
2603
+ if registros:
2604
+ catalogo = _catalogo_from_rows(registros)
2605
+ _CATALOGO_VIAS_CACHE = list(catalogo)
2606
+ append_runtime_log(f"[mesa] pesquisa: catalogo de logradouros carregado com {len(catalogo)} vias")
2607
+ return list(catalogo)
2608
+
2609
  gdf = geocodificacao.carregar_eixos()
2610
  if gdf is None or gdf.empty:
2611
  append_runtime_log("[mesa] pesquisa: base de eixos indisponivel para catalogo de logradouros")