154 lines
6.7 KiB
PHP
154 lines
6.7 KiB
PHP
<?php
|
|
/**
|
|
* LocationIntelligenceEngine.php
|
|
* Core engine for processing passenger location updates from various sources
|
|
* (App Usage, Geofencing, Silent Push) and making automated decisions.
|
|
*/
|
|
|
|
class LocationIntelligenceEngine {
|
|
private $db;
|
|
|
|
public function __construct($dbConnection) {
|
|
$this->db = $dbConnection;
|
|
}
|
|
|
|
/**
|
|
* Process a location update from any source.
|
|
*
|
|
* @param int|string $passengerId
|
|
* @param float $lat
|
|
* @param float $lng
|
|
* @param string $source Enum: 'app_usage', 'geofence', 'silent_push'
|
|
* @return array Optional new geofence regions to register on user's device
|
|
*/
|
|
public function processLocationUpdate($passengerId, $lat, $lng, $source = 'app_usage', $batteryLevel = null) {
|
|
if (!function_exists('sendFCM_Internal')) {
|
|
require_once __DIR__ . '/../../functions.php';
|
|
}
|
|
|
|
// 1. Update Database
|
|
$this->updateLocationDatabase($passengerId, $lat, $lng, $source, $batteryLevel);
|
|
|
|
$zone = null;
|
|
// 2. Check for Geofence intersections (always evaluate what zone they are in)
|
|
$zone = $this->checkGeofenceZone($lat, $lng);
|
|
|
|
if ($zone) {
|
|
// 3. Trigger Campaigns / Notifications
|
|
$this->evaluateCampaignOpportunity($passengerId, $zone, $source);
|
|
}
|
|
|
|
// 4. Update Driver Demand Map
|
|
$this->updateDemandMap($passengerId, $lat, $lng);
|
|
|
|
// 5. Get Geofencing regions for the user's device (closest ones)
|
|
// iOS allows 20, Android 100. We'll return top 20 by default.
|
|
return $this->getUpdatedGeofencesForUser($lat, $lng);
|
|
}
|
|
|
|
private function updateLocationDatabase($passengerId, $lat, $lng, $source, $batteryLevel) {
|
|
try {
|
|
$sql = "INSERT INTO passenger_opening_locations (passenger_id, latitude, longitude, source, battery_level)
|
|
VALUES (:pid, :lat, :lng, :source, :battery)";
|
|
$stmt = $this->db->prepare($sql);
|
|
$stmt->execute([
|
|
':pid' => $passengerId,
|
|
':lat' => $lat,
|
|
':lng' => $lng,
|
|
':source' => $source,
|
|
':battery' => $batteryLevel
|
|
]);
|
|
} catch (Exception $e) {
|
|
error_log("[LocationIntelligenceEngine] DB Error: " . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
private function checkGeofenceZone($lat, $lng) {
|
|
// Find if the user's lat/lng is within the radius of any active geofence zone
|
|
// Using Haversine formula
|
|
$sql = "SELECT id, zone_name, country_code, radius_meters,
|
|
(6371000 * acos(cos(radians(:lat)) * cos(radians(latitude)) * cos(radians(longitude) - radians(:lng)) + sin(radians(:lat)) * sin(radians(latitude)))) AS distance
|
|
FROM geofence_zones
|
|
WHERE is_active = 1
|
|
HAVING distance <= radius_meters
|
|
ORDER BY distance ASC LIMIT 1";
|
|
$stmt = $this->db->prepare($sql);
|
|
$stmt->execute([':lat' => $lat, ':lng' => $lng]);
|
|
return $stmt->fetch(PDO::FETCH_ASSOC);
|
|
}
|
|
private function evaluateCampaignOpportunity($passengerId, $zone, $source) {
|
|
// 1. Check if passenger received a campaign recently (Anti-Spam)
|
|
$sqlSpamCheck = "SELECT COUNT(*) FROM marketing_campaigns_log
|
|
WHERE passenger_id = :pid
|
|
AND message_type = 'push'
|
|
AND sent_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)";
|
|
$stmtSpam = $this->db->prepare($sqlSpamCheck);
|
|
$stmtSpam->execute([':pid' => $passengerId]);
|
|
$spamCount = intval($stmtSpam->fetchColumn());
|
|
|
|
if ($spamCount == 0) {
|
|
// 2. Fetch Active Campaign (Placeholder for real campaign DB logic)
|
|
// Currently, we just send a generic welcome to the zone if priority is high.
|
|
if ($zone['priority'] >= 1) {
|
|
$this->sendCampaignNotification($passengerId, $zone);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function sendCampaignNotification($passengerId, $zone) {
|
|
// Get passenger token
|
|
$sql = "SELECT users_token, country_code FROM users WHERE users_id = :pid";
|
|
$stmt = $this->db->prepare($sql);
|
|
$stmt->execute([':pid' => $passengerId]);
|
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if ($user && !empty($user['users_token'])) {
|
|
$title = "مرحباً بك في " . $zone['zone_name'] . " \u{1F389}";
|
|
$body = "اطلب رحلتك الآن من " . $zone['zone_name'] . " واستمتع بتجربة سيرو!";
|
|
|
|
// Send Push
|
|
sendFCM_Internal([$user['users_token']], $title, $body, ['type' => 'geofence_promo'], '');
|
|
|
|
// Log it
|
|
$logSql = "INSERT INTO marketing_campaigns_log (passenger_id, message_type, country_code, region_name, triggered_by)
|
|
VALUES (:pid, 'push', :country, :region, 'geofence_trigger')";
|
|
$logStmt = $this->db->prepare($logSql);
|
|
$logStmt->execute([
|
|
':pid' => $passengerId,
|
|
':country' => $user['country_code'] ?? 'JO',
|
|
':region' => $zone['zone_name']
|
|
]);
|
|
}
|
|
}
|
|
|
|
private function updateDemandMap($passengerId, $lat, $lng) {
|
|
// Broadcast this location to drivers or update a demand heat map cache
|
|
// Here we insert into passengerlocation to log the hotspot.
|
|
try {
|
|
$sql = "INSERT INTO passengerlocation (passengerId, lat, lng, rideId) VALUES (:pid, :lat, :lng, '0')";
|
|
$stmt = $this->db->prepare($sql);
|
|
// $stmt->execute([':pid' => $passengerId, ':lat' => $lat, ':lng' => $lng]); // Suppressed for now to avoid db constraints errors
|
|
} catch (Exception $e) {
|
|
error_log("[LocationIntelligenceEngine] Demand Map Error: " . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
private function getUpdatedGeofencesForUser($lat, $lng, $limit = 20) {
|
|
// Return top nearest geofences to update on the user's device
|
|
$sql = "SELECT id, zone_name, latitude, longitude, radius_meters,
|
|
(6371000 * acos(cos(radians(:lat)) * cos(radians(latitude)) * cos(radians(longitude) - radians(:lng)) + sin(radians(:lat)) * sin(radians(latitude)))) AS distance
|
|
FROM geofence_zones
|
|
WHERE is_active = 1
|
|
ORDER BY distance ASC LIMIT :limit";
|
|
|
|
$stmt = $this->db->prepare($sql);
|
|
$stmt->bindParam(':lat', $lat);
|
|
$stmt->bindParam(':lng', $lng);
|
|
$stmt->bindValue(':limit', (int) $limit, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
|
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
}
|
|
?>
|