FlotillasGPS - Sistema completo de monitoreo de flotillas GPS
Sistema completo para monitoreo y gestion de flotas de vehiculos con: - Backend FastAPI con PostgreSQL/TimescaleDB - Frontend React con TypeScript y TailwindCSS - App movil React Native con Expo - Soporte para dispositivos GPS, Meshtastic y celulares - Video streaming en vivo con MediaMTX - Geocercas, alertas, viajes y reportes - Autenticacion JWT y WebSockets en tiempo real Documentacion completa y guias de usuario incluidas.
This commit is contained in:
156
backend/app/models/ubicacion.py
Normal file
156
backend/app/models/ubicacion.py
Normal file
@@ -0,0 +1,156 @@
|
||||
"""
|
||||
Modelo de Ubicación para almacenar datos GPS.
|
||||
|
||||
Utiliza TimescaleDB hypertable para almacenamiento eficiente
|
||||
de series temporales de ubicaciones.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import (
|
||||
Boolean,
|
||||
DateTime,
|
||||
Float,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Integer,
|
||||
String,
|
||||
event,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
|
||||
class Ubicacion(Base):
|
||||
"""
|
||||
Modelo de ubicación GPS.
|
||||
|
||||
Esta tabla está diseñada para ser una hypertable de TimescaleDB,
|
||||
optimizada para almacenar millones de registros de ubicación.
|
||||
"""
|
||||
|
||||
__tablename__ = "ubicaciones"
|
||||
|
||||
# Clave primaria compuesta: tiempo + vehiculo_id
|
||||
tiempo: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
primary_key=True,
|
||||
nullable=False,
|
||||
)
|
||||
vehiculo_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("vehiculos.id", ondelete="CASCADE"),
|
||||
primary_key=True,
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
# Coordenadas
|
||||
lat: Mapped[float] = mapped_column(Float, nullable=False)
|
||||
lng: Mapped[float] = mapped_column(Float, nullable=False)
|
||||
altitud: Mapped[float | None] = mapped_column(Float, nullable=True) # metros sobre nivel del mar
|
||||
|
||||
# Movimiento
|
||||
velocidad: Mapped[float | None] = mapped_column(Float, nullable=True) # km/h
|
||||
rumbo: Mapped[float | None] = mapped_column(Float, nullable=True) # grados (0-360)
|
||||
|
||||
# Precisión
|
||||
precision: Mapped[float | None] = mapped_column(Float, nullable=True) # metros
|
||||
hdop: Mapped[float | None] = mapped_column(Float, nullable=True) # Horizontal Dilution of Precision
|
||||
|
||||
# Información GPS
|
||||
satelites: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
fuente: Mapped[str] = mapped_column(
|
||||
String(20),
|
||||
default="gps",
|
||||
nullable=False,
|
||||
) # gps, network, fused, meshtastic
|
||||
|
||||
# Estado del dispositivo
|
||||
bateria_dispositivo: Mapped[float | None] = mapped_column(Float, nullable=True) # porcentaje
|
||||
bateria_vehiculo: Mapped[float | None] = mapped_column(Float, nullable=True) # voltaje
|
||||
|
||||
# Estado del vehículo
|
||||
motor_encendido: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
|
||||
odometro: Mapped[float | None] = mapped_column(Float, nullable=True) # km
|
||||
|
||||
# Sensores OBD (opcional)
|
||||
rpm: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||
temperatura_motor: Mapped[float | None] = mapped_column(Float, nullable=True) # Celsius
|
||||
nivel_combustible: Mapped[float | None] = mapped_column(Float, nullable=True) # porcentaje
|
||||
|
||||
# Índices para consultas frecuentes
|
||||
__table_args__ = (
|
||||
# Índice espacial aproximado para consultas por área
|
||||
Index("idx_ubicaciones_coords", "lat", "lng"),
|
||||
# Índice para consultas por vehículo en un rango de tiempo
|
||||
Index("idx_ubicaciones_vehiculo_tiempo", "vehiculo_id", "tiempo"),
|
||||
# Índice para encontrar paradas (velocidad 0)
|
||||
Index("idx_ubicaciones_velocidad", "velocidad"),
|
||||
# Configuración para TimescaleDB
|
||||
{
|
||||
"timescaledb_hypertable": {
|
||||
"time_column_name": "tiempo",
|
||||
"chunk_time_interval": "1 day",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Ubicacion(vehiculo_id={self.vehiculo_id}, tiempo={self.tiempo}, lat={self.lat}, lng={self.lng})>"
|
||||
|
||||
def to_geojson(self) -> dict:
|
||||
"""Convierte la ubicación a formato GeoJSON Point."""
|
||||
return {
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [self.lng, self.lat],
|
||||
},
|
||||
"properties": {
|
||||
"vehiculo_id": self.vehiculo_id,
|
||||
"tiempo": self.tiempo.isoformat(),
|
||||
"velocidad": self.velocidad,
|
||||
"rumbo": self.rumbo,
|
||||
"motor_encendido": self.motor_encendido,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# Función para crear la hypertable en TimescaleDB
|
||||
# Se ejecuta después de crear la tabla
|
||||
def create_hypertable(target, connection, **kw):
|
||||
"""Crea la hypertable de TimescaleDB después de crear la tabla."""
|
||||
# Esta función se ejecutará solo si TimescaleDB está instalado
|
||||
try:
|
||||
connection.execute(
|
||||
"""
|
||||
SELECT create_hypertable(
|
||||
'ubicaciones',
|
||||
'tiempo',
|
||||
if_not_exists => TRUE,
|
||||
chunk_time_interval => INTERVAL '1 day'
|
||||
);
|
||||
"""
|
||||
)
|
||||
# Habilitar compresión después de 7 días
|
||||
connection.execute(
|
||||
"""
|
||||
ALTER TABLE ubicaciones SET (
|
||||
timescaledb.compress,
|
||||
timescaledb.compress_segmentby = 'vehiculo_id'
|
||||
);
|
||||
"""
|
||||
)
|
||||
# Política de compresión automática
|
||||
connection.execute(
|
||||
"""
|
||||
SELECT add_compression_policy('ubicaciones', INTERVAL '7 days', if_not_exists => TRUE);
|
||||
"""
|
||||
)
|
||||
except Exception:
|
||||
# Si TimescaleDB no está instalado, continuar sin hypertable
|
||||
pass
|
||||
|
||||
|
||||
# Registrar evento para crear hypertable
|
||||
event.listen(Ubicacion.__table__, "after_create", create_hypertable)
|
||||
Reference in New Issue
Block a user