---
id: count_points_per_zone_pipeline_py_pipelines
name: count_points_per_zone_pipeline
kind: pipeline
lang: py
domain: pipelines
version: "1.0.0"
purity: impure
signature: "def count_points_per_zone_pipeline(points: list[tuple[float, float]], zones: list[dict]) -> dict"
description: "Cuenta cuántos puntos (lon, lat) caen dentro de cada zona geográfica definida por GeoJSON."
tags: [pipeline, geo, footprint, geojson, spatial, count, zone, pendiente-usar]
uses_functions: ["load_geojson_polygons_py_geo", "polygon_bbox_py_geo", "point_in_polygons_bbox_py_geo"]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
example: |
  result = count_points_per_zone_pipeline(
      points=[(-3.70, 40.41), (-3.65, 40.45)],
      zones=[{"label": "Madrid centro", "geojson_path": "zones/madrid_centro.geojson"}],
  )
  # result["counts"] == {"Madrid centro": 2}, result["unassigned"] == 0
tested: true
tests: ["test_count_points_per_zone_pipeline"]
test_file_path: "python/functions/pipelines/tests/test_count_points_per_zone_pipeline.py"
file_path: "python/functions/pipelines/count_points_per_zone_pipeline.py"
params:
  - {name: points, desc: "Lista de (lon, lat) de los puntos a clasificar por zona. Coordenadas WGS84."}
  - {name: zones, desc: "Lista de dicts con label y geojson_path. Define las zonas geográficas a evaluar."}
output: "Dict con counts (label→int), total_points, total_assigned y unassigned."
source_repo: "internal:footprint_aurgi"
source_license: "internal-aurgi"
source_file: "internal:composed"
---

## Ejemplo

```python
result = count_points_per_zone_pipeline(
    points=[(-3.70, 40.41), (-3.65, 40.45), (-4.50, 39.80)],
    zones=[
        {"label": "Madrid centro", "geojson_path": "zones/madrid_centro.geojson"},
        {"label": "Área metropolitana", "geojson_path": "zones/madrid_metro.geojson"},
    ],
)

print(result["counts"])
# {"Madrid centro": 2, "Área metropolitana": 2}
print(f"Sin zona: {result['unassigned']}")  # 1 (el punto en Toledo)
```

## Notas

Los puntos se representan como (lon, lat) — orden GeoJSON estándar (longitud primero).
Un punto puede contarse en múltiples zonas si los polígonos se solapan: `total_assigned` puede superar `total_points`.
`unassigned` cuenta los puntos que no cayeron en ninguna de las zonas especificadas.
La carga de polígonos se hace una vez por zona; el prefiltraje por bbox reduce el coste de ray-casting.
