Overview
The resource tracking module enables you to monitor mobile units (patrol vehicles, emergency responders), analyze their movements, and identify operational patterns. This helps optimize resource allocation and response times.
Resource tracking uses GPS telemetry from the posicionesgps database table to provide detailed movement analysis.
Accessing Resource Tracking
Navigate to the mobile units interface:
CecocoController.php:22-33
public function indexMoviles ()
{
// Obtener los recursos únicos
$recursos = DB :: connection ( 'mysql_second' )
-> table ( 'posicionesgps' )
-> distinct ()
-> pluck ( 'recurso' )
-> toArray ();
return view ( 'cecoco.moviles.index' , compact ( 'recursos' ));
}
This displays all available mobile resources from the GPS tracking system.
You must have the ver-moviles-cecoco permission to access resource tracking features.
Tracking Mobile Routes
Retrieve the complete route history for one or more mobile units:
Request Parameters
CecocoController.php:225-231
request () -> validate ([
'recursos' => 'required|not_in:Seleccionar recurso' ,
'fecha_desde' => 'required' ,
'fecha_hasta' => 'required' ,
], [
'required' => 'El campo :attribute es necesario completar.'
]);
Parameter Type Required Description recursosarray Yes JSON array of resource IDs to track fecha_desdedatetime Yes Start date/time for tracking period fecha_hastadatetime Yes End date/time for tracking period
Implementation
CecocoController.php:233-253
$fecha_desde = \Carbon\ Carbon :: parse ( $request -> fecha_desde ) -> format ( 'Y-m-d H:i:s' );
$fecha_hasta = \Carbon\ Carbon :: parse ( $request -> fecha_hasta ) -> format ( 'Y-m-d H:i:s' );
$coordinates = [];
$results = DB :: connection ( 'mysql_second' )
-> table ( 'posicionesgps' )
-> whereIn ( 'recurso' , json_decode ( $request -> recursos ))
-> whereBetween ( 'fecha' , [ $fecha_desde , $fecha_hasta ])
-> get ()
-> map ( function ( $result ) use ( & $coordinates ) {
// Convertir las coordenadas de radianes a grados decimales
$result -> latitud = round ( $result -> latitud / 0.0174533 , 7 );
$result -> longitud = round ( $result -> longitud / 0.0174533 , 7 );
// Convertir la fecha al formato deseado
$result -> fecha = \Carbon\ Carbon :: createFromFormat ( 'Y-m-d H:i:s' , $result -> fecha ) -> format ( 'd/m/Y H:i:s' );
$coordinates [] = [ 'lat' => $result -> latitud , 'lng' => $result -> longitud ];
return $result ;
});
Response Structure
The response groups GPS positions by resource:
CecocoController.php:256-284
// Agrupar los resultados por recurso
$groupedResults = collect ( $results ) -> groupBy ( 'recurso' );
// Estructura final de datos
$finalData = [];
// Iterar sobre los grupos y agregarlos a $finalData
foreach ( $groupedResults as $recurso => $group ) {
$data = [];
foreach ( $group as $result ) {
// Realizar la geocodificación inversa
$result -> direccion = $this -> getDireccion ( $result -> latitud , $result -> longitud );
$data [] = [
'id' => $result -> id ,
'recurso' => $result -> recurso ,
'latitud' => $result -> latitud ,
'longitud' => $result -> longitud ,
'velocidad' => ( float ) $result -> velocidad ,
'fecha' => $result -> fecha ,
'direccion' => (( $result -> latitud == 0 ) && ( $result -> longitud == 0 )) ? 'Dirección no encontrada' : $result -> direccion ,
];
}
$finalData [] = [
'recurso' => $recurso ,
'datos' => $data ,
];
}
return response () -> json ([ 'moviles' => $finalData ]);
Identifying Stopped Intervals
Detect when and where mobile units were stopped for extended periods:
POST /get-moviles-parados
Request Parameters
CecocoController.php:293-300
$request -> validate ([
'recursos' => 'required|string' ,
'fecha_desde' => 'required' ,
'fecha_hasta' => 'required' ,
'tiempo_permitido' => 'required|numeric' ,
], [
'required' => 'El campo :attribute es necesario completar.' ,
]);
Parameter Type Required Description recursosstring Yes JSON string of resource IDs fecha_desdedatetime Yes Start date/time fecha_hastadatetime Yes End date/time tiempo_permitidonumeric Yes Minimum stopped time in minutes to report
Detection Algorithm
CecocoController.php:334-366
if ( $prevPosition ) {
$distance = $this -> calcularDistancia ( $prevPosition -> latitud , $prevPosition -> longitud , $result -> latitud , $result -> longitud );
if ( $distance < 0.02 ) {
if ( ! $inicioParada ) {
$inicioParada = \Carbon\ Carbon :: parse ( $result -> fecha );
}
} else {
if ( $inicioParada ) {
$finParada = \Carbon\ Carbon :: parse ( $result -> fecha );
$tiempoParado = $finParada -> diffInMinutes ( $inicioParada );
if ( $tiempoParado > $tiempoPermitidoParar ) {
$intervalosParado [] = [
'recurso' => $recurso ,
'inicio_parado' => $inicioParada -> format ( 'H:i:s' ),
'fin_parado' => $finParada -> format ( 'H:i:s' ),
'latitud' => $prevPosition -> latitud ,
'longitud' => $prevPosition -> longitud ,
'lugar' => $this -> getDireccion ( $prevPosition -> latitud , $prevPosition -> longitud ),
'tiempo_parado' => $tiempoParado ,
];
}
$inicioParada = null ;
}
}
}
The algorithm:
Compares consecutive GPS positions
Detects stops when distance < 0.02 km (20 meters)
Records start and end times
Reports only stops exceeding the specified threshold
Distance Calculation
Uses the Haversine formula to calculate distance between coordinates:
CecocoController.php:375-394
private function calcularDistancia ( $lat1 , $lon1 , $lat2 , $lon2 )
{
// Fórmula de Haversine para calcular la distancia entre dos puntos en la Tierra
$lat1Rad = deg2rad ( $lat1 );
$lon1Rad = deg2rad ( $lon1 );
$lat2Rad = deg2rad ( $lat2 );
$lon2Rad = deg2rad ( $lon2 );
$deltaLat = $lat2Rad - $lat1Rad ;
$deltaLon = $lon2Rad - $lon1Rad ;
$a = sin ( $deltaLat / 2 ) * sin ( $deltaLat / 2 ) + cos ( $lat1Rad ) * cos ( $lat2Rad ) * sin ( $deltaLon / 2 ) * sin ( $deltaLon / 2 );
$c = 2 * atan2 ( sqrt ( $a ), sqrt ( 1 - $a ));
$earthRadius = 6371 ; // Radio de la Tierra en kilómetros
$distance = $earthRadius * $c ;
return $distance ;
}
This formula returns distance in kilometers and accounts for the Earth’s curvature.
Reverse Geocoding
Convert GPS coordinates to human-readable addresses:
CecocoController.php:397-420
private function getDireccion ( $latitud , $longitud )
{
$geocoding = GeocodificacionInversa :: where ( 'latitud' , round ( $latitud , 7 ))
-> where ( 'longitud' , round ( $longitud , 7 ))
-> first ();
if ( $geocoding ) {
// Dirección encontrada en la tabla GeocodificacionInversa
return $geocoding -> direccion ;
} else {
// Dirección no encontrada en la tabla, obtenerla a través de Google Maps
$direccion = $this -> getAddressGoogle ( $latitud , $longitud );
if ( $direccion != 'Dirección no encontrada' ) {
// Guardar la dirección en la tabla GeocodificacionInversa para futuras consultas
GeocodificacionInversa :: create ([
'latitud' => $latitud ,
'longitud' => $longitud ,
'direccion' => $direccion ,
]);
}
return $direccion ;
}
}
The system:
First checks cached addresses in the database
Falls back to Google Maps Geocoding API
Caches new addresses for future lookups
Google Maps Integration
CecocoController.php:474-488
private function getAddressGoogle ( $lat , $lng )
{
$apiKey = env ( 'API_GOOGLE' ); // Reemplaza con tu clave de API de Google Maps
// Realizar solicitud a la API de geocodificación
$url = "https://maps.googleapis.com/maps/api/geocode/json?latlng= $lat , $lng &key= $apiKey " ;
$response = file_get_contents ( $url );
$data = json_decode ( $response );
if ( isset ( $data -> results [ 0 ] -> formatted_address )) {
return $data -> results [ 0 ] -> formatted_address ;
} else {
return 'Dirección no encontrada' ;
}
}
You need to configure the API_GOOGLE environment variable with your Google Maps API key.
Resource Status Monitoring
Get current status of all resources:
This endpoint provides real-time resource positions with:
Current GPS coordinates (converted to decimal degrees)
Resource type (patrol car, ambulance, etc.)
Last update timestamp
Movement status
CecocoController.php:91-109
$recursos = DB :: connection ( 'mysql_second' )
-> table ( 'ultimasposicionesgps' )
-> select (
'ultimasposicionesgps.*' ,
'recursos.*' ,
'tiposrecurso.nombre as tipo_recurso' ,
)
-> join ( 'recursos' , 'recursos.id' , '=' , 'ultimasposicionesgps.recursos_id' )
-> join ( 'tiposrecurso' , 'tiposrecurso.id' , '=' , 'recursos.idTipo' )
-> whereBetween ( 'ultimasposicionesgps.fecha' , [ $fecha_desde , $fecha_hasta ])
-> where ( 'ultimasposicionesgps.latitud' , '!=' , '0.0' )
-> where ( 'ultimasposicionesgps.longitud' , '!=' , '0.0' )
-> get ()
-> map ( function ( $result ) {
// Convertir las coordenadas de radianes a grados decimales
$result -> latitud_decimal = round ( $result -> latitud / 0.0174533 , 7 );
$result -> longitud_decimal = round ( $result -> longitud / 0.0174533 , 7 );
return $result ;
});
Use Cases
Route Optimization Analyze patrol routes to optimize coverage and reduce response times
Compliance Monitoring Verify that units are following assigned patrol areas and schedules
Incident Investigation Review unit locations during specific incidents for investigation purposes
Performance Analysis Identify patterns in stopped intervals to improve operational efficiency
Best Practices
Time Windows
Limit your tracking queries to reasonable time windows:
Real-time monitoring: Last 30-60 minutes
Daily review: 8-24 hours
Historical analysis: Up to 7 days
Stop Detection Threshold
Set appropriate thresholds for stopped intervals:
Short stops (5-10 min): Normal operational stops
Medium stops (10-30 min): Investigation or extended activity
Long stops (30+ min): Potential issues requiring attention
The system uses geocoding cache to reduce API calls:
GeocodificacionInversa :: where ( 'latitud' , round ( $latitud , 7 ))
-> where ( 'longitud' , round ( $longitud , 7 ))
-> first ();
This significantly improves performance when tracking multiple units.
Live Map Visualize resource positions in real-time on the interactive map