←

Track summary: calcular distancia y paradas de una ruta GPS

Contexto

Un cliente quiere ver el resumen de la ruta de un vehiculo: cuantos km recorrio, cuanto tiempo estuvo en movimiento vs detenido, velocidad maxima, y donde se detuvo. Los proveedores GPS dan puntos crudos (lat, lon, speed, time) pero no calculan resumenes.

Lo que aprendi

El trip summary itera los puntos de la ruta y clasifica cada segmento entre dos puntos consecutivos como "movimiento" o "parada":

def trip_summary(points: list[dict]) -> dict:
    """Resumen de viaje a partir de puntos GPS crudos."""
    if len(points) < 2:
        return {"total_distance_km": 0, "stops": []}

    total_distance = 0.0
    moving_time = 0.0
    stopped_time = 0.0
    max_speed = 0.0
    stops = []
    stop_start_idx = None

    for i in range(1, len(points)):
        prev, curr = points[i - 1], points[i]
        dt = (curr["time"] - prev["time"]).total_seconds()

        if curr["speed"] >= 3.0:  # En movimiento
            total_distance += haversine(
                prev["lat"], prev["lon"],
                curr["lat"], curr["lon"]
            )
            moving_time += dt
            max_speed = max(max_speed, curr["speed"])

            # Si estaba en parada, cerrarla
            if stop_start_idx is not None:
                stop_duration = (prev["time"] - points[stop_start_idx]["time"]).total_seconds() / 60
                if stop_duration >= 2.0:  # Solo paradas > 2 min
                    stops.append({
                        "lat": points[stop_start_idx]["lat"],
                        "lon": points[stop_start_idx]["lon"],
                        "start_time": points[stop_start_idx]["time"],
                        "end_time": prev["time"],
                        "duration_min": round(stop_duration, 1),
                    })
                stop_start_idx = None
        else:  # Detenido
            stopped_time += dt
            if stop_start_idx is None:
                stop_start_idx = i

    avg_speed = (total_distance / (moving_time / 3600)) if moving_time > 0 else 0

    return {
        "total_distance_km": round(total_distance, 2),
        "total_time_min": round((moving_time + stopped_time) / 60, 1),
        "moving_time_min": round(moving_time / 60, 1),
        "stopped_time_min": round(stopped_time / 60, 1),
        "max_speed": round(max_speed, 1),
        "avg_speed": round(avg_speed, 1),
        "stop_count": len(stops),
        "stops": stops,
    }

Detalles importantes:

  • Velocidad promedio se calcula solo de segmentos en movimiento (excluye paradas)
  • Paradas menores a 2 minutos se ignoran (semaforos, trafico)
  • Si el ultimo punto es una parada, hay que cerrarla al final del loop
  • Puntos sin timestamp valido se saltan

Por que importa

Con ~50 lineas de puro Python y math, generas el mismo resumen que apps de fleet tracking comerciales cobran por vehiculo. La funcion es stateless y sin dependencias, se puede reusar en cualquier proyecto.