Compare commits
7
Commits
0c19486a24
...
c24e921bac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c24e921bac | ||
|
|
e7453cb6f9 | ||
|
|
bec8f61d70 | ||
|
|
4d16fbda48 | ||
|
|
ccf7dc99ee | ||
|
|
1b67c5e8fc | ||
|
|
d00502769a |
@@ -73,9 +73,15 @@ try {
|
||||
$newAwfarPrice = round($newSpeedPrice * 0.85, 3);
|
||||
|
||||
// أسعار الدقائق
|
||||
$newNormalMin = round($newSpeedPrice / 4, 3);
|
||||
$newPeakMin = round($newNormalMin * 1.15, 3);
|
||||
$newLateMin = round($newNormalMin * 1.25, 3);
|
||||
if ($country === 'JO') {
|
||||
$newNormalMin = 0.06;
|
||||
$newPeakMin = 0.07;
|
||||
$newLateMin = 0.08;
|
||||
} else {
|
||||
$newNormalMin = round($newSpeedPrice / 4, 3);
|
||||
$newPeakMin = round($newNormalMin * 1.15, 3);
|
||||
$newLateMin = round($newNormalMin * 1.25, 3);
|
||||
}
|
||||
|
||||
$updateSql = "UPDATE kazan
|
||||
SET speedPrice = :speedPrice,
|
||||
|
||||
@@ -85,16 +85,28 @@ foreach ($data as $row) {
|
||||
}
|
||||
|
||||
$distanceKm = (float)($resultData['distance_km'] ?? 1);
|
||||
if ($distanceKm <= 0) $distanceKm = 1; // Prevent division by zero
|
||||
$pricePerKm = $amount / $distanceKm;
|
||||
|
||||
$durationMin = isset($resultData['duration_min']) ? (int)$resultData['duration_min'] : null;
|
||||
|
||||
$startLat = $resultData['start_lat'] ?? null;
|
||||
$startLng = $resultData['start_lng'] ?? null;
|
||||
$endLat = $resultData['end_lat'] ?? null;
|
||||
$endLng = $resultData['end_lng'] ?? null;
|
||||
$countryCode = $row['country_code'] ?? 'JO'; // Default
|
||||
|
||||
// --- OSRM Integration (Dynamic Distance & Duration) ---
|
||||
require_once __DIR__ . '/../core/osrm_routing.php';
|
||||
if ($startLat && $endLat && (!$durationMin || $durationMin <= 0)) {
|
||||
$osrmResult = getOsrmRouteDetails($startLat, $startLng, $endLat, $endLng, $countryCode);
|
||||
if ($osrmResult) {
|
||||
$distanceKm = $osrmResult['distance_km']; // Override with accurate OSRM distance
|
||||
$durationMin = $osrmResult['duration_min'];
|
||||
}
|
||||
}
|
||||
// ------------------------------------------------------
|
||||
|
||||
if ($distanceKm <= 0) $distanceKm = 1; // Prevent division by zero
|
||||
$pricePerKm = $amount / $distanceKm;
|
||||
|
||||
$countryCode = $row['country_code'] ?? 'JO'; // Default
|
||||
|
||||
if ($stmt->execute([
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
// =========================================================
|
||||
// backend/bot/fill_durations.php
|
||||
// One-off script to fill missing duration_min values
|
||||
// in the scraped_competitor_prices table using Intaleq Maps.
|
||||
// =========================================================
|
||||
|
||||
require_once __DIR__ . '/../core/bootstrap.php';
|
||||
require_once __DIR__ . '/../functions.php';
|
||||
require_once __DIR__ . '/../core/osrm_routing.php';
|
||||
|
||||
try {
|
||||
$con = Database::get('main');
|
||||
} catch (Exception $e) {
|
||||
die("Database connection failed: " . $e->getMessage() . "\n");
|
||||
}
|
||||
|
||||
echo "Fetching rows where duration_min IS NULL...\n";
|
||||
|
||||
// Get rows needing update
|
||||
$stmt = $con->prepare("
|
||||
SELECT id, start_lat, start_lng, end_lat, end_lng, country_code
|
||||
FROM scraped_competitor_prices
|
||||
WHERE duration_min IS NULL
|
||||
AND start_lat IS NOT NULL
|
||||
AND start_lng IS NOT NULL
|
||||
");
|
||||
$stmt->execute();
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$rows || count($rows) === 0) {
|
||||
echo "No rows found needing duration updates.\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
echo "Found " . count($rows) . " rows to update. Processing...\n";
|
||||
|
||||
$updateStmt = $con->prepare("
|
||||
UPDATE scraped_competitor_prices
|
||||
SET duration_min = :duration_min, distance_km = :distance_km
|
||||
WHERE id = :id
|
||||
");
|
||||
|
||||
$successCount = 0;
|
||||
$failCount = 0;
|
||||
|
||||
foreach ($rows as $index => $row) {
|
||||
$id = $row['id'];
|
||||
$countryCode = $row['country_code'];
|
||||
|
||||
// Call GraphHopper / Intaleq Maps
|
||||
$routeInfo = getOsrmRouteDetails($row['start_lat'], $row['start_lng'], $row['end_lat'], $row['end_lng'], $countryCode);
|
||||
|
||||
if ($routeInfo && isset($routeInfo['duration_min']) && isset($routeInfo['distance_km'])) {
|
||||
$updateStmt->execute([
|
||||
':duration_min' => (int) round($routeInfo['duration_min']),
|
||||
':distance_km' => $routeInfo['distance_km'],
|
||||
':id' => $id
|
||||
]);
|
||||
$successCount++;
|
||||
echo "Row $id: Distance {$routeInfo['distance_km']}km, Duration " . round($routeInfo['duration_min']) . "min [SUCCESS]\n";
|
||||
} else {
|
||||
$failCount++;
|
||||
echo "Row $id: Failed to fetch route details.\n";
|
||||
}
|
||||
|
||||
// Small sleep to avoid hitting API rate limits too hard
|
||||
usleep(100000); // 100ms
|
||||
}
|
||||
|
||||
echo "======================================\n";
|
||||
echo "Completed processing " . count($rows) . " rows.\n";
|
||||
echo "Success: $successCount\n";
|
||||
echo "Failed: $failCount\n";
|
||||
@@ -94,12 +94,43 @@ $countriesConfig = [
|
||||
['name' => 'Jabal Amman', 'lat' => 31.9511, 'lng' => 35.9189],
|
||||
['name' => 'Sweileh', 'lat' => 32.0167, 'lng' => 35.8333],
|
||||
['name' => 'Khalda', 'lat' => 31.9861, 'lng' => 35.8450],
|
||||
['name' => 'Al-Jubaiha', 'lat' => 32.0194, 'lng' => 35.8753],
|
||||
['name' => 'Al-Jubaiha', 'lat' => 32.0194, 'lng' => 35.8753],
|
||||
['name' => 'Tla Al-Ali', 'lat' => 31.9961, 'lng' => 35.8647],
|
||||
['name' => 'Shmeisani', 'lat' => 31.9680, 'lng' => 35.9020],
|
||||
['name' => 'Um Uthaina', 'lat' => 31.9610, 'lng' => 35.8770],
|
||||
['name' => 'Jabal Al-Weibdeh', 'lat' => 31.9560, 'lng' => 35.9220],
|
||||
['name' => 'Marj Al-Hamam', 'lat' => 31.9000, 'lng' => 35.8500],
|
||||
['name' => '7th Circle', 'lat' => 31.9525, 'lng' => 35.8600],
|
||||
['name' => 'Zarqa New', 'lat' => 32.0833, 'lng' => 36.1000],
|
||||
['name' => 'Zarqa Camp', 'lat' => 32.0667, 'lng' => 36.0833],
|
||||
['name' => 'Russeifa', 'lat' => 32.0167, 'lng' => 36.0500],
|
||||
['name' => 'Marka', 'lat' => 31.9833, 'lng' => 35.9833],
|
||||
['name' => 'Taj Mall', 'lat' => 31.9423, 'lng' => 35.9038],
|
||||
['name' => 'City Mall', 'lat' => 31.9806, 'lng' => 35.8353],
|
||||
['name' => 'Mecca Mall', 'lat' => 31.9792, 'lng' => 35.8464],
|
||||
['name' => 'Abdali Mall', 'lat' => 31.9644, 'lng' => 35.9117],
|
||||
['name' => 'Alrabieh', 'lat' => 31.9722, 'lng' => 35.8814],
|
||||
['name' => 'Dabouq', 'lat' => 31.9989, 'lng' => 35.8153],
|
||||
['name' => 'Al-Fuheis', 'lat' => 32.0125, 'lng' => 35.7761],
|
||||
['name' => 'Wadi Al-Seer', 'lat' => 31.9533, 'lng' => 35.8197],
|
||||
['name' => 'Bayader', 'lat' => 31.9472, 'lng' => 35.8236],
|
||||
['name' => 'Tariq', 'lat' => 32.0083, 'lng' => 35.9417],
|
||||
['name' => 'Hashmi Shamali', 'lat' => 31.9800, 'lng' => 35.9556],
|
||||
['name' => 'Jabal Al-Hussein', 'lat' => 31.9639, 'lng' => 35.9231],
|
||||
['name' => 'Downtown Amman', 'lat' => 31.9517, 'lng' => 35.9333],
|
||||
['name' => 'Al-Ashrafiyeh', 'lat' => 31.9361, 'lng' => 35.9417],
|
||||
['name' => 'Al-Muqabalain', 'lat' => 31.9011, 'lng' => 35.9228],
|
||||
['name' => 'Al-Wehdat', 'lat' => 31.9250, 'lng' => 35.9417],
|
||||
['name' => 'Abu Nuseir', 'lat' => 32.0469, 'lng' => 35.8750],
|
||||
['name' => 'Shafa Badran', 'lat' => 32.0417, 'lng' => 35.9083],
|
||||
['name' => 'Al-Zaytoonah Uni', 'lat' => 31.8319, 'lng' => 35.9281],
|
||||
['name' => 'German Jordan Uni','lat' => 31.7778, 'lng' => 35.8114],
|
||||
['name' => 'Queen Alia Airport','lat'=> 31.7225, 'lng' => 35.9933],
|
||||
['name' => 'Irbid Center', 'lat' => 32.5514, 'lng' => 35.8500],
|
||||
['name' => 'Jerash', 'lat' => 32.2817, 'lng' => 35.8917],
|
||||
['name' => 'Salt', 'lat' => 32.0333, 'lng' => 35.7333],
|
||||
['name' => 'Madaba', 'lat' => 31.7167, 'lng' => 35.7950],
|
||||
['name' => 'Dead Sea Hotels', 'lat' => 31.7161, 'lng' => 35.5878]
|
||||
]
|
||||
]
|
||||
];
|
||||
@@ -129,8 +160,12 @@ $tasksCreated = 0;
|
||||
foreach ($countriesConfig as $countryCode => $config) {
|
||||
$competitors = $config['competitors'];
|
||||
$regions = $config['regions'];
|
||||
|
||||
// Randomly select 20 regions (or all if less than 20)
|
||||
shuffle($regions);
|
||||
$selectedRegions = array_slice($regions, 0, 20);
|
||||
|
||||
foreach ($regions as $region) {
|
||||
foreach ($selectedRegions as $region) {
|
||||
// A. Generate Start Point (within 2km of region center)
|
||||
$start = generateRandomPoint($region['lat'], $region['lng'], 2);
|
||||
|
||||
|
||||
+16
-3
@@ -131,15 +131,28 @@ if ($method === 'GET') {
|
||||
$pricePerKm = $distance_km > 0 ? ($price / $distance_km) : 0.0;
|
||||
$country_code = $result_data['country_code'] ?? 'SY';
|
||||
|
||||
// --- OSRM Integration (Dynamic Distance & Duration) ---
|
||||
require_once __DIR__ . '/../core/osrm_routing.php';
|
||||
$duration_min = 0;
|
||||
if ($start_lat != 0 && $end_lat != 0) {
|
||||
$osrmResult = getOsrmRouteDetails($start_lat, $start_lng, $end_lat, $end_lng, $country_code);
|
||||
if ($osrmResult) {
|
||||
$distance_km = $osrmResult['distance_km']; // Override with accurate OSRM distance
|
||||
$duration_min = $osrmResult['duration_min'];
|
||||
$pricePerKm = $distance_km > 0 ? ($price / $distance_km) : 0.0; // Recalculate based on OSRM
|
||||
}
|
||||
}
|
||||
// ------------------------------------------------------
|
||||
|
||||
// 1. Save to MySQL
|
||||
$stmt = $con->prepare("
|
||||
INSERT INTO scraped_competitor_prices
|
||||
(task_id, app_name, competitor_name, start_location, end_location, start_lat, start_lng, end_lat, end_lng, price_amount, price_per_km, currency, country_code)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
(task_id, app_name, competitor_name, start_location, end_location, start_lat, start_lng, end_lat, end_lng, price_amount, price_per_km, distance_km, duration_min, currency, country_code)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
");
|
||||
$start_loc = "Lat: $start_lat, Lng: $start_lng";
|
||||
$end_loc = "Lat: $end_lat, Lng: $end_lng";
|
||||
$stmt->execute([$task_id, $app_name, $app_name, $start_loc, $end_loc, (string)$start_lat, (string)$start_lng, (string)$end_lat, (string)$end_lng, $price, $pricePerKm, 'JOD', $country_code]);
|
||||
$stmt->execute([$task_id, $app_name, $app_name, $start_loc, $end_loc, (string)$start_lat, (string)$start_lng, (string)$end_lat, (string)$end_lng, $price, $pricePerKm, $distance_km, $duration_min, 'JOD', $country_code]);
|
||||
|
||||
// 2. Save to Redis (Calculate Price Per KM)
|
||||
if ($distance_km > 0 && $price > 0) {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// osrm_routing.php
|
||||
// Helper functions for OpenStreetMap Routing Machine (OSRM)
|
||||
// Used to estimate accurate distance (km) and duration (mins)
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Get Distance and Duration between two coordinates using public OSRM server.
|
||||
* Note: If you host your own OSRM, replace the $osrmBaseUrl.
|
||||
*
|
||||
* @param float $startLat
|
||||
* @param float $startLng
|
||||
* @param float $endLat
|
||||
* @param float $endLng
|
||||
* @param string $countryCode (e.g. 'JO', 'SY', 'EG')
|
||||
* @return array|null Returns ['distance_km' => float, 'duration_min' => float] or null on failure.
|
||||
*/
|
||||
function getOsrmRouteDetails($startLat, $startLng, $endLat, $endLng, $countryCode = 'JO') {
|
||||
// Intaleq Maps SaaS server handles all countries (Jordan, Syria, Egypt)
|
||||
$baseUrl = "https://map-saas.intaleqapp.com/api/maps/route";
|
||||
|
||||
$queryParams = http_build_query([
|
||||
'fromLat' => $startLat,
|
||||
'fromLng' => $startLng,
|
||||
'toLat' => $endLat,
|
||||
'toLng' => $endLng
|
||||
]);
|
||||
|
||||
$url = "{$baseUrl}?{$queryParams}";
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 5); // 5 seconds timeout
|
||||
|
||||
// Add Intaleq API Key Header
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
"x-api-key: in_9478b32836d19cff73db3063"
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode === 200 && $response) {
|
||||
$data = json_decode($response, true);
|
||||
if (isset($data['distance']) && isset($data['duration'])) {
|
||||
$distanceMeters = (float)$data['distance'];
|
||||
$durationSeconds = isset($data['trafficAwareDuration']) ? (float)$data['trafficAwareDuration'] : (float)$data['duration'];
|
||||
|
||||
return [
|
||||
'distance_km' => round($distanceMeters / 1000, 2),
|
||||
'duration_min' => round($durationSeconds / 60, 2)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -183,13 +183,28 @@ function calculateDynamicPrice($country, $minFare, $distance, $duration, $kazanR
|
||||
$damascusAirportBoundAddon = 1400.0;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
$longSpeedThresholdKm = 40.0;
|
||||
$longSpeedPerKm = 26.0;
|
||||
|
||||
// Country specific overrides for long distance pricing
|
||||
// Damascus bounds to roughly estimate if we are in Syria vs Jordan/Egypt
|
||||
$isSyria = ($passengerLat > 32.0 && $passengerLat < 37.0 && $passengerLng > 35.0 && $passengerLng < 42.0 && $kazanRow['country'] === 'Syria');
|
||||
$isJordan = ($kazanRow['country'] === 'Jordan' || $kazanRow['country'] === 'JO');
|
||||
$isEgypt = ($kazanRow['country'] === 'Egypt' || $kazanRow['country'] === 'EG');
|
||||
|
||||
if ($isJordan) {
|
||||
$longSpeedPerKm = 0.30; // Reasonable long distance speed price for Jordan
|
||||
$longTripPerMin = 0.06; // Reasonable long distance minute price for Jordan
|
||||
} else if ($isEgypt) {
|
||||
$longSpeedPerKm = 18.0; // Reasonable long distance speed price for Egypt
|
||||
$longTripPerMin = 0.80; // 80 Piasters for long distance minute price for Egypt
|
||||
} else {
|
||||
$longSpeedPerKm = 26.0; // Syrian Pound
|
||||
$longTripPerMin = 6.0; // Syrian Pound
|
||||
}
|
||||
|
||||
$mediumDistThresholdKm = 25.0;
|
||||
$longDistThresholdKm = 35.0;
|
||||
$longTripPerMin = 6.0;
|
||||
$minuteCapMedium = 60;
|
||||
$minuteCapLong = 80;
|
||||
$freeMinutesLong = 10;
|
||||
|
||||
@@ -2198,32 +2198,24 @@ class RideLifecycleController extends GetxController {
|
||||
Future<void> calculateDriverToPassengerRoute(
|
||||
LatLng driverPos, LatLng passengerPos,
|
||||
{bool isBeginPhase = false}) async {
|
||||
final Map<String, String> queryParams = {
|
||||
'fromLat': driverPos.latitude.toString(),
|
||||
'fromLng': driverPos.longitude.toString(),
|
||||
'toLat': passengerPos.latitude.toString(),
|
||||
'toLng': passengerPos.longitude.toString(),
|
||||
};
|
||||
final uri =
|
||||
Uri.parse(AppLink.mapSaasRoute).replace(queryParameters: queryParams);
|
||||
|
||||
if (mapController == null) {
|
||||
Log.print('⚠️ mapController is null, cannot calculate route via intaleq_maps');
|
||||
return;
|
||||
}
|
||||
|
||||
Log.print('📍 Calculating Driver Route: $uri');
|
||||
Log.print('📍 Calculating Driver Route via IntaleqMapController...');
|
||||
|
||||
try {
|
||||
final response = await http.get(uri, headers: {
|
||||
'x-api-key': Env.mapSaasKey,
|
||||
}).timeout(const Duration(seconds: 20));
|
||||
final responseData = await mapController!.getDirections(driverPos, passengerPos);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final responseData = json.decode(response.body);
|
||||
var routeData = responseData['routes'] != null
|
||||
? responseData['routes'][0]
|
||||
: responseData;
|
||||
|
||||
var routeData = responseData['routes'] != null
|
||||
? responseData['routes'][0]
|
||||
: responseData;
|
||||
|
||||
double durationSecondsRaw = (routeData['duration'] as num).toDouble();
|
||||
int finalDurationSeconds =
|
||||
(durationSecondsRaw * kDurationScalar).toInt();
|
||||
double durationSecondsRaw = (routeData['trafficAwareDuration'] ?? routeData['duration'] as num).toDouble();
|
||||
int finalDurationSeconds =
|
||||
(durationSecondsRaw * kDurationScalar).toInt();
|
||||
double distanceMeters = (routeData['distance'] as num).toDouble();
|
||||
|
||||
updateDriverRouteMetrics(
|
||||
@@ -2299,7 +2291,6 @@ class RideLifecycleController extends GetxController {
|
||||
mapEngine.fitCameraToPoints(driverPos, passengerPos);
|
||||
_updatePassengerWalkLine();
|
||||
update();
|
||||
}
|
||||
} catch (e) {
|
||||
Log.print('❌ Error calculating driver route: $e');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user