response( array( 'error' => 'Data not found' ), 404); } /** * Method GET * Get single record (by id) * http://DOMAIN/trips/1 * @return string */ public function viewAction() { return $this->response( array( 'error'=> 'Data not found' ), 404); } public function createAction() { syslog(LOG_WARNING,'ActivityApi::createAction()'); $message = 'We could not find any recent activity in this area'; $code = 404; $time = $this->requestParams['time'] ?? ''; $location = $this->requestParams['location'] ?? []; $country = $this->requestParams['country'] ?? ''; $member_id = $this->requestParams['member_id'] ?? 0; try { $db = new Db(); $country = Geocode::getCountryByGPS($db->getConnect(), $location, $country, Activity::DEFAULT_COUNTRY); syslog(LOG_WARNING,'country='.$country); $country = Geocode::mockGPSCountry($db->getConnect(), $member_id, $country, 'default'); if ($country!='US' && $country!='SG') { throw new RuntimeException('Trips has not yet launched in your country. You can still track your travel activity and access exclusive deals. Start exploring!',500); } if ($member_id<1) { throw new RuntimeException('Invalid member ID',500); } if ($time=='' || strtotime($time) " . json_encode($location)); if (is_array($location) && isset($location["lat"]) && isset($location["lng"])) { // DEBUG list($location["lat"],$location["lng"]) = Geocode::mockGPSLocation( $db->getConnect(), $member_id, $location["lat"], $location["lng"], 'default'); syslog(LOG_WARNING,"lat=".$location["lat"].",lng=".$location["lng"]); list($street_address,$err) = GeocodeApi::reverseGeocode($db, $location["lat"], $location["lng"]); if ($street_address=="") { list($address,$err) = GeofenceApi::getAnchor($db, $location["lat"], $location["lng"]); if (is_array($address) && array_key_exists("address",$address) && $address["address"]!="") { $street_address = $address["address"]; } } if ($street_address=="") { syslog(LOG_WARNING,'Reverse geocoder failed for ('.$location["lat"].', '.$location["lng"].')'); throw new RuntimeException($err?"Geocoder failed: $err":'Cannot geocode your GPS location',500); } syslog(LOG_WARNING,"street_address=".json_encode($street_address)); if (!is_array($address) || !array_key_exists("address",$address) || $address["address"]=="") { list($address,$err) = Geocode::checkLatLngByAddress($db->getConnect(), $street_address, $country); } if (!is_array($address) || !isset($address["address"])) { syslog(LOG_WARNING,'Geocoder failed for "'.$street_address.'"'); throw new RuntimeException($err?"Geocoder failed: $err":'Cannot get address for your GPS location '.$street_address,500); } // Adjust GPS coordinates if any if (array_key_exists($address["latitude"]) && $address["latitude"]!=null && array_key_exists($address["longitude"]) && $address["longitude"]!=null && $address["latitude"]!=0 && $address["longitude"]!=0) { $location["lat"] = $address["latitude"]; $location["lng"] = $address["longitude"]; $address["lat"] = $address["latitude"]; $address["lng"] = $address["longitude"]; } else if (array_key_exists($address["lat"]) && $address["lat"]!=null && array_key_exists($address["lng"]) && $address["lng"]!=null && $address["lat"]!=0 && $address["lng"]!=0) { $location["lat"] = $address["lat"]; $location["lng"] = $address["lng"]; $address["latitude"] = $address["lat"]; $address["longitude"] = $address["lng"]; } list($location,$address) = ActivityApi::adjustAddressLocationGPS($location,$address); } // Step 0. Check if we are in the service area /* GeocodeApi::checkWithinTheServiceArea($country,[ [ "type"=>1, "geocode" => [ "lat" => $location["lat"], "lng" => $location["lng"] ] ] ]); //*/ syslog(LOG_WARNING, "location => " . json_encode($location)); // Step 1. Get activities list($res,$err) = Activity::getActivity( $db->getConnect(), $db->getConnectGPS(), $member_id, $location["lat"], $location["lng"], Activity::ACTIVITY_RADIUS, $time, Activity::TIME_DELTA, $country); if (!$res || count($res)<1) { if ($err!="") { throw new RuntimeException($err, 500); } throw new RuntimeException($message, 404); } syslog(LOG_WARNING,'Recent activity has '.count($res).' trips!'); //ActivityApi::debugTrips($res,'0'); $destination_cache = []; $result = []; $i = 0; foreach ($res as $trip) { list($trip, $destination_cache) = ActivityApi::processAddressTrip($db, $country, $address, $trip, $destination_cache); ActivityApi::debugTrips([$trip],'1'); if ($trip==NULL) continue; $advice = ActivityApi::processAdvice($db, $member_id, $country, $trip); ActivityApi::debugTrips([$advice],'2'); if ($advice && count($advice)>0) { $result[] = $advice; $i++; } if ($i>=Activity::DEFAULT_OPTIONS) { break; } } $result = ActivityApi::uniqueFilter($db, $result); //syslog(LOG_WARNING,json_encode($result)); ActivityApi::debugTrips($result,'3'); $result = ActivityApi::processQuotesTrips($db, $member_id, $country, $result); ActivityApi::debugTrips($result,'4'); //$result = ActivityApi::processGojekOption($db, $member_id, $country, $result); //ActivityApi::debugTrips($result,'5'); //syslog(LOG_WARNING,json_encode($result)); return $this->response( array( "trips" => $result, "street_address" => html_entity_decode($street_address) ), 200); } catch (RuntimeException $e) { $message = $e->getMessage(); $code = $e->getCode(); syslog(LOG_WARNING,$message); } return $this->response( array( "error" => $message ), $code); } private static function adjustAddressLocationGPS($location,$address) { syslog(LOG_WARNING,'ActivityApi::adjustAddressLocationGPS($location,$address)'); if (array_key_exists($address["latitude"]) && $address["latitude"]!=null && array_key_exists($address["longitude"]) && $address["longitude"]!=null && $address["latitude"]!=0 && $address["longitude"]!=0) { $location["lat"] = $address["latitude"]; $location["lng"] = $address["longitude"]; $address["lat"] = $address["latitude"]; $address["lng"] = $address["longitude"]; } else if (array_key_exists($address["lat"]) && $address["lat"]!=null && array_key_exists($address["lng"]) && $address["lng"]!=null && $address["lat"]!=0 && $address["lng"]!=0) { $location["lat"] = $address["lat"]; $location["lng"] = $address["lng"]; $address["latitude"] = $address["lat"]; $address["longitude"] = $address["lng"]; } //syslog(LOG_WARNING,json_encode($address)); //syslog(LOG_WARNING,"location => " . json_encode($location)); return [$location,$address]; } public static function debugTrips($trips,$what='0') { return; /* foreach ($trips as $trip) { $leg_fare = $trip['multimodal']['options']['leg_fare']; syslog(LOG_WARNING,'>>>>>>> '.$what.' >>>>>>> '.json_encode($leg_fare)); }*/ } public function updateAction() { return $this->response( array( "error" => "Update error" ), 400); } public function deleteAction() { return $this->response( array( "error" => "Delete error" ), 500); } protected function uniqueFilter($db, $result) { syslog(LOG_WARNING,'ActivityApi::uniqueFilter()'); $cache = []; $filtered = []; foreach ($result as $trip) { list ($cached, $cache) = ActivityApi::checkCache($trip["trip"], $cache); if ($cached) { $filtered[] = $trip; } else { syslog(LOG_WARNING,'Duplicate trip? id=' . $trip["trip"]["id"]); syslog(LOG_WARNING,json_encode($trip["trip"])); } } if (count($filtered)>0) { return $filtered; } // Let's hope we will show something... return $result; } protected function checkCache($trip, $cache) { syslog(LOG_WARNING,'ActivityApi::checkCache()'); foreach ($cache as $item) { if ($item["id"]>0 && $item["id"]==$trip["id"]) { return [false, $cache]; } if ($item["location_end_id"]>0 && $item["location_end_id"]==$trip["location_end_id"]) { return [false, $cache]; } } $cache[] = $trip; return [true, $cache]; } public function processAddressTrip($db, $country, $address, $trip, $cache) { syslog(LOG_WARNING,'ActivityApi::processAddressTrip()'); /* { "id":"10888", "travel_date":"2019-08-23 23:54:00", "duration":"13", "cost_raw":"SGD 18.00", "trackedemail_item_id":"2117062", "cost":"18.00","updated": "2019-08-23 19:31:39.916296", "distance":"7.38", "transport_provider_id":"3", "scheduled":null, "travel_date_end":"2019-08-24 00:07:10", "dup_id":null,"location_start_id":"523", "location_end_id":"42", "private":"f", "dm":"1434"} */ if (!is_array($address) || !isset($address["lat"]) || !isset($address["lng"]) || ($address["lat"]==0 && $address["lng"]==0)) { return [NULL, $cache]; } if ($address["id"]==$trip["location_start_id"]) { list($pass, $cache) = ActivityApi::checkCache($trip, $cache); if ($pass) { return [$trip, $cache]; } } list($trip1,$err) = Trips::tripByLocations($db->getConnect(), $address["id"], $trip["location_start_id"]); if (is_array($trip1) && isset($trip1["id"]) && $trip1["id"]>0) { list($pass, $cache) = ActivityApi::checkCache($trip1, $cache); if ($pass) { return [$trip1, $cache]; } } $loc_start = Address::getAddressById($db->getConnect(), $trip["location_start_id"]); syslog(LOG_WARNING,json_encode($loc_start)); if (round($address["lat"],3)==round($loc_start["latitude"],3) && round($address["lng"],3)==round($loc_start["longitude"],3)) { list($pass, $cache) = ActivityApi::checkCache($trip1, $cache); if ($pass) { return [$trip1, $cache]; } } list($trip2,$err) = Trips::tripByLocations($db->getConnect(), $address["id"], $trip["location_end_id"]); if (is_array($trip2) && isset($trip2["id"]) && $trip2["id"]>0) { list($pass, $cache) = ActivityApi::checkCache($trip2, $cache); if ($pass) { return [$trip2, $cache]; } } $loc_end = Address::getAddressById($db->getConnect(), $trip["location_end_id"]); syslog(LOG_WARNING,json_encode($loc_end)); if (round($address["lat"],3)==round($loc_end["latitude"],3) && round($address["lng"],3)==round($loc_end["longitude"],3)) { list($pass, $cache) = ActivityApi::checkCache($trip2, $cache); if ($pass) { return [$trip2, $cache]; } } // Get distance, duration $distance = sprintf("%0.02f",Activity::CITY_CURVATURE * GeocodeApi::distanceBetweenTwoGpsCoordinates($address["lat"],$address["lng"],$loc_start["latitude"],$loc_start["longitude"],'K')); $duration = (int)($distance / Activity::CITY_SPEED); syslog(LOG_WARNING,"distance1=${distance}"); syslog(LOG_WARNING,json_encode([$address["lat"],$address["lng"],$loc_start["latitude"],$loc_start["longitude"]])); if (Activity::MIN_ACTIVITY_DISTANCE<=$distance && $distance<=Activity::MAX_ACTIVITY_DISTANCE) { // 50m? < x < 50km? $trip1 = ActivityApi::createTrip($db, $country, $duration, $distance, $address["id"], $trip["location_start_id"]); if (is_array($trip1) && isset($trip1["id"]) && $trip1["id"]>0) { list($pass, $cache) = ActivityApi::checkCache($trip1, $cache); if ($pass) { return [$trip1, $cache]; } } } $distance = sprintf("%0.02f",Activity::CITY_CURVATURE * GeocodeApi::distanceBetweenTwoGpsCoordinates($address["lat"],$address["lng"],$loc_end["latitude"],$loc_end["longitude"],'K')); $duration = (int)($distance / Activity::CITY_SPEED); syslog(LOG_WARNING,"distance2=${distance}"); syslog(LOG_WARNING,json_encode([$address["lat"],$address["lng"],$loc_end["latitude"],$loc_end["longitude"]])); if (Activity::MIN_ACTIVITY_DISTANCE<=$distance && $distance<=Activity::MAX_ACTIVITY_DISTANCE) { // 50m? < x < 50km? $trip2 = ActivityApi::createTrip($db, $country, $duration, $distance, $address["id"], $trip["location_end_id"]); if (is_array($trip2) && isset($trip2["id"]) && $trip2["id"]>0) { list($pass, $cache) = ActivityApi::checkCache($trip2, $cache); if ($pass) { return [$trip2, $cache]; } } } //syslog(LOG_WARNING,json_encode($trip)); return [NULL, $cache]; } public function createTrip($db, $country, $duration, $distance, $location_start_id, $location_end_id) { syslog(LOG_WARNING,"ActivityApi::createTrip(\$db, $country, $duration, $distance, $location_start_id, $location_end_id)"); $data = [ 'travel_date' => MultiModal::getDate($country), 'duration' => (int)$duration, 'cost_raw' => NULL, 'trackedemail_item_id' => NULL, 'cost' => NULL, 'distance' => $distance, 'transport_provider_id' => NULL, 'scheduled' => NULL, 'travel_date_end' => MultiModal::getDate($country), 'dup_id' => NULL, 'location_start_id' => $location_start_id, 'location_end_id' => $location_end_id, 'private' => 't' /* this is a private entry without the source e-mail */ ]; list($id, $err) = Trips::create($db->getConnect(), $data); if ($id && $id>0) { $trip = Trips::getById($db->getConnect(), $id); return $trip; } return NULL; // WTF? } public function processAdvice($db, $member_id, $country, $trip) { syslog(LOG_WARNING,"ActivityApi::processAdvice(\$db, ${member_id}, ${country}, \$trip)"); $advice = [ "trip"=>$trip, "scooter"=>[], "multimodal"=>[], "rideshare"=>[], "faster"=>NULL, "cheaper"=>NULL ]; $durations = [ "scooter" => PHP_INT_MAX, "multimodal" => PHP_INT_MAX, "rideshare" => PHP_INT_MAX ]; try { $id = $trip["id"]; // Get addresses $address_start = Address::getAddressById($db->getConnect(), $trip["location_start_id"]); $address_end = Address::getAddressById($db->getConnect(), $trip["location_end_id"]); if (!$address_start || !$address_end || count($address_start)<3 || count($address_end)<3 || ($address_start["latitude"]==0 && $address_start["longitude"]==0) || ($address_end["latitude"]==0 && $address_end["longitude"]==0)) { throw new Exception("Invalid address"); } // {"id":"10888","travel_date":"2019-08-23 23:54:00","duration":"13","cost_raw":"SGD 18.00","trackedemail_item_id":"2117062","cost":"18.00","updated":"2019-08-23 19:31:39.916296","distance":"7.38","transport_provider_id":"3","scheduled":null,"travel_date_end":"2019-08-24 00:07:10","dup_id":null,"location_start_id":"523","location_end_id":"42","private":"f","dm":"1434"} // Step 2. Get advice // Step 2.0. Geocode? // Step 2.1. Scooter $data = []; $options = []; $err = NULL; if ($country!='SG') { list($data, $options, $err) = ActivityApi::scooter($db, [ [ "address" => $address_start["address"], "type" => 1, "coordinates" => [ "lat" => $address_start["latitude"], "lng" => $address_start["longitude"] ] ], [ "address" => $address_end["address"], "type" => 2, "coordinates" => [ "lat" => $address_end["latitude"], "lng" => $address_end["longitude"] ] ] ], [ "travel_time" => true, "quote" => true, "route_overlay" => true ]); } if ($data && count($data)) { $advice["scooter"] = ["data"=>$data,"options"=>$options]; $durations["scooter"] = 0; foreach ($data as $step) { $durations["scooter"] += $step["duration"]; } } // Step 2.2. Multimodal $leg_id = 0; $checkOptions = [ [ "type"=>1, "geocode" => [ "lat" => $address_start["latitude"], "lng" => $address_end["longitude"] ] ] ]; syslog(LOG_WARNING,"ActivityApi: ".json_encode($checkOptions)); $withinTheServiceArea = GeocodeApi::checkWithinTheServiceArea($country,$checkOptions,false); if ($withinTheServiceArea) { syslog(LOG_WARNING,"ActivityApi: WITHIN SERVICE AREA!"); ActivityApi::debugTrips([['multimodal'=>$trip]],'11'); $trip = MultiModalApi::multimodal( $db, $address_start, $address_end, $country, false/*no cache?*/, $member_id, MultiModalApi::CACHE_TIMEOUT, $trip["distance"], $trip["duration"] ); ActivityApi::debugTrips([['multimodal'=>$trip]],'12'); if ($trip && count($trip) && array_key_exists("options",$trip)) { $advice["multimodal"] = $trip; foreach ($trip["options"]["legs"] as $leg) { if ($leg["duration"]<$durations["multimodal"]) { $durations["multimodal"] = $leg["duration"]; $leg_id = $leg["id"]; } } } } else { syslog(LOG_WARNING,'ActivityApi: IS NOT WITHIN SERVICE AREA!'); $trip = ActivityApi::createTrip( $db, $country, $trip["duration"], $trip["distance"], $trip["location_start_id"], $trip["location_end_id"] ); } // Step 2.3. Rideshare /******* Start GOJEK ******/ //$trip = MultiModalApi::processGojekOption($db, $member_id, $country, $trip); /******* End GOJEK ******/ if (!$withinTheServiceArea && $country=='US') { $trip = MultiModalApi::processRidesharesOption($db, $member_id, $country, $trip); syslog(LOG_WARNING,'ActivityApi: >>>>>>> '.json_encode($trip)); if (is_array($trip) && count($trip)>0 && array_key_exists("options",$trip)) { $advice["rideshare"] = $trip; foreach ($trip["options"]["legs"] as $leg) { if ($leg["duration"]<$durations["multimodal"]) { $durations["rideshare"] = $leg["duration"]; $leg_id = $leg["id"]; } } } } // Durations... foreach ($durations as $key=>$val) { if ($val==PHP_INT_MAX) { $durations[$key] = 0; } } // Process advice... return $advice; // END } catch (Exception $e) { syslog(LOG_WARNING,$e->getMessage()); } return NULL; } public function scooter($db, $addresses, $options) { syslog(LOG_WARNING,"ActivityApi::scooter()"); $message = "Failed to run scooter directions"; try { if ($addresses==NULL || !is_array($addresses) || count($addresses)<1 || !isset($addresses[0]["address"])) { throw new Exception("Invalid input"); } $result = []; $input = []; foreach ($addresses as $item) { if (isset($item["type"]) && $item["type"]>0 && $item["type"]<3) { $input[$item["type"]] = $item; } } if (isset($input[1]) && isset($input[2]) && isset($input[1]["address"]) && isset($input[2]["address"])) { list($result, $err) = ScooterApi::scooter($db, $input); if (!is_array($result) || count($result)<1) { throw new Exception($err!=''? $err : 'No scooter option available'); } } else { throw new Exception("Invalid address"); } if ((isset($options["travel_time"]) && $options["travel_time"]) || (isset($options["quote"]) && $options["quote"]) || (isset($options["route_overlay"]) && $options["route_overlay"])) { $options = ScooterApi::options($db, $input, $result, $options); } return [$result, $options, NULL]; } catch (Exception $e) { syslog(LOG_WARNING,json_encode($e)); $message = $e->getMessage(); } return [NULL, NULL, $message]; } public static function processQuotesTrips($db, $member_id, $country, $trips) { syslog(LOG_WARNING,"ActivityApi::processQuotes(\$db, $member_id, $country, \$trips)"); if (!is_array($trips) || count($trips)<1) { syslog(LOG_WARNING,'Result is missing trips!'); return $trips; // Whatever it was - not our fault here } foreach ($trips as $key=>$trip) { $trips[$key] = ActivityApi::processQuotesTrip($db, $member_id, $country, $trip); } return $trips; } protected static function processQuotesTrip($db, $member_id, $country, $trip) { syslog(LOG_WARNING,"ActivityApi::processQuotesTrip(\$db, $member_id, $country, \$trip)"); if (!is_array($trip) || !array_key_exists('multimodal', $trip) || !is_array($trip['multimodal']) || count($trip['multimodal'])<1) { syslog(LOG_WARNING,'Trip is missing multimodal!'); return $trip; // Whatever it was - not our fault here } // Process multimodal $trip['multimodal'] = ActivityApi::processQuotesTripMultimodal($db, $member_id, $country, $trip); // Process scooter if (array_key_exists('scooter', $trip) && count($trip['scooter'])>0) { $trip['scooter'] = ActivityApi::processQuotesTripScooter($db, $member_id, $country, $trip); } // Process rideshare if (array_key_exists('rideshare', $trip) && count($trip['rideshare'])>0) { $trip['rideshare'] = ActivityApi::processQuotesTripRideshare($db, $member_id, $country, $trip); } return $trip; } protected static function processQuotesTripMultimodal($db, $member_id, $country, $trip) { syslog(LOG_WARNING,"ActivityApi::processQuotesTripMultimodal(\$db, $member_id, $country, \$trip)"); if (!array_key_exists('options',$trip['multimodal']) || count($trip['multimodal']['options'])<1) { syslog(LOG_WARNING,'Multimodal trip is missing options!'); return $trip['multimodal']; // we had checked its existance up the call stack } $multimodal = $trip['multimodal']; $multimodal['options'] = ActivityApi::processQuotesTripMultimodalOptions( $db, $member_id, $country, $multimodal['options']); return $multimodal; } public static function processQuotesTripMultimodalOptions($db, $member_id, $country, $options) { syslog(LOG_WARNING,"ActivityApi::processQuotesTripMultimodalOptions(\$db, $member_id, $country, \$options)"); if (!is_array($options) || !array_key_exists('legs',$options) || !array_key_exists('leg_steps',$options) || !is_array($options['legs']) || !is_array($options['leg_steps']) || count($options['legs'])<1 || count($options['leg_steps'])<1) { syslog(LOG_WARNING,'Multimodal trip options is missing data!'); return $options; // we had checked its existance up the call stack } $legs = $options['legs']; $leg_steps = $options['leg_steps']; foreach ($legs as $key=>$leg) { $steps = $leg_steps[$leg['id']]; list($leg, $steps) = ActivityApi::processQuotesTripMultimodalLegSteps($db, $member_id, $country, $leg, $steps); $leg_steps[$leg['id']] = $steps; $legs[$key] = $leg; } $options['leg_steps'] = $leg_steps; $options['legs'] = $legs; return $options; } protected static function processQuotesTripMultimodalLegSteps($db, $member_id, $country, $leg, $steps) { syslog(LOG_WARNING,"ActivityApi::processQuotesTripMultimodalLegSteps(\$db, $member_id, $country, \$leg, \$steps)"); if (!is_array($leg) || !is_array($steps) || !array_key_exists('id',$leg) || count($steps)<1) { syslog(LOG_WARNING,'Multimodal trip option leg is missing data!'); return [$leg, $steps]; } $total = 0; foreach ($steps as $key=>$step) { $step = ActivityApi::processQuotesTripMultimodalLegStep($db, $member_id, $country, $step, $leg['id']); if (array_key_exists('fare_raw',$step) && $step['fare_raw']>0) { $total += $step['fare_raw']; } $steps[$key] = $step; } if (!array_key_exists('fare_raw',$leg) || $leg['fare_raw']<$total) { $leg['fare_raw'] = $total; } return [$leg, $steps]; } protected static function processQuotesTripMultimodalLegStep($db, $member_id, $country, $step, $leg_id) { syslog(LOG_WARNING,"ActivityApi::processQuotesTripMultimodalLegStep(\$db, $member_id, $country, \$step, $leg_id)"); if (!is_array($step) || !array_key_exists('travel_mode',$step) || ActivityApi::noGPSCoordinates($step)) { syslog(LOG_WARNING,'Multimodal trip option leg step is missing data!'); return $step; } if ($step['travel_mode']=='WALKING') { // Walking is free! return $step; } if ($step['travel_mode']=='BIKE') { // Biking is free! return $step; } if ($step['travel_mode']=='NUS_TRANSIT') { // NUS shuttle is free! return $step; } if ($step['travel_mode']=='PARK') { // I do not know what to do with parking right now return $step; } if ($step['travel_mode']=='SCOOTER') { return ActivityApi::processQuotesTripMultimodalLegStepScooter($db, $member_id, $country, $step, $leg_id); } if ($step['travel_mode']=='TAXI') { return ActivityApi::processQuotesTripMultimodalLegStepTaxi($db, $member_id, $country, $step, $leg_id); } if ($step['travel_mode']=='TRANSIT') { return ActivityApi::processQuotesTripMultimodalLegStepTransit($db, $member_id, $country, $step, $leg_id); } return $step; } protected static function processQuotesTripMultimodalLegStepScooter($db, $member_id, $country, $step, $leg_id) { syslog(LOG_WARNING,"ActivityApi::processQuotesTripMultimodalLegStepScooter(\$db, $member_id, $country, \$step, $leg_id)"); // TODO: Scooter return $step; } protected static function processQuotesTripMultimodalLegStepTaxi($db, $member_id, $country, $step, $leg_id) { syslog(LOG_WARNING,"ActivityApi::processQuotesTripMultimodalLegStepTaxi(\$db, $member_id, $country, \$step, $leg_id)"); // Quote Taxi if ($country!='SG') { // We support Singapore/ComfortDelGro only for now! return $step; } // Do we have a quote already? if (array_key_exists('fare_quote',$step) && $step['fare_quote']>0) { $step['fare_raw'] = $step['fare_quote']; return $step; } list ($origin, $err) = GeocodeApi::reverseGeocode($db, $step['location_start_lat'], $step['location_start_lng']); list ($destination, $err) = GeocodeApi::reverseGeocode($db, $step['location_end_lat'], $step['location_end_lng']); $transport_provider_id = Quotes::VENDOR_COMFORTDELGRO; // ComfortDelGro $quote_group_id = NULL; // Not groupped $trackedemail_item_id = NULL; // Not from a receipt try { list($message,$code,$action,$data) = QuoteAPI::createReal( $db, $origin, $destination, $member_id, $transport_provider_id, $quote_group_id, $trackedemail_item_id, $country); if ($code==200) { $load = true; if ($action=="view") { $quote = Quotes::getById($db->getConnect(), (int)$data["id"]); if (is_array($quote) && count($quote)>0 && $quote["completed"]!="" && (int)$quote["cost"]!=0) { $load = false; $step['fare_raw'] = (int)$quote["cost"]; } } if ($load) { // Load for ($i=0; $i<3; $i++) { sleep(1); // wait for a sec $quote = Quotes::getById($db->getConnect(), (int)$data["id"]); if (is_array($quote) && count($quote)>0 && $quote["completed"]!="" && (int)$quote["cost"]!=0) { $step['fare_raw'] = (int)$quote["cost"]; break; } } } } } catch (Exception $e) { syslog(LOG_WARNING,$e->getMessage()); } return $step; } protected static function processQuotesTripMultimodalLegStepTransit($db, $member_id, $country, $step, $leg_id) { syslog(LOG_WARNING,"ActivityApi::processQuotesTripMultimodalLegStepTransit(\$db, $member_id, $country, \$step, $leg_id)"); // Quote Transit if ($country!='SG' && $country!='US') { // We support Singapore & US only for now! return $step; } if ($country=='US') { // We support BART only return ActivityApi::processQuotesTripMultimodalLegStepTransitUS($db, $member_id, $step, $leg_id); } return ActivityApi::processQuotesTripMultimodalLegStepTransitSG($db, $member_id, $step, $leg_id); } protected static function processQuotesTripMultimodalLegStepTransitUS($db, $member_id, $step, $leg_id) { syslog(LOG_WARNING,"ActivityApi::processQuotesTripMultimodalLegStepTransitUS(\$db, $member_id, \$step, $leg_id)"); // TODO: !!! return $step; } protected static function processQuotesTripMultimodalLegStepTransitSG($db, $member_id, $step, $leg_id) { syslog(LOG_WARNING,"ActivityApi::processQuotesTripMultimodalLegStepTransitSG(\$db, $member_id, \$step, $leg_id)"); if (array_key_exists('fare_raw',$step) && $step['fare_raw']>0) { return $step; // There is a quote already } $result = MultiModalApi::mytansportsgServiceLeg($leg_id); if (is_array($result) && array_key_exists("code",$result) && $result["code"]==0 && array_key_exists("data",$result)) { $data = $result["data"]; if (is_array($data) && array_key_exists($leg_id,$data) && is_array($data[$leg_id]) && array_key_exists('total',$data[$leg_id]) && $data[$leg_id]['total']>0) { // Get from the DB! list ($res, $err) = Quotes::getLegStepQuoteBySID($db->getConnect(), $step['sid']); if (is_array($res) && array_key_exists('fare_raw',$res) && $res['fare_raw']>0) { $step['fare_raw'] = $res['fare_raw']; } else { syslog(LOG_WARNING,'No step(sid='.$step['sid'].') quote found in the DB from mytransport.sg service'); } } else { syslog(LOG_WARNING,'Invalid response or no quote from mytransport.sg service'); syslog(LOG_WARNING,json_encode($data)); } } else { syslog(LOG_WARNING,'Failed to get the quote from mytransport.sg service'); } return $step; } protected static function noGPSCoordinates($step) { syslog(LOG_WARNING,"ActivityApi::noGPSCoordinates(\$step)"); if (!array_key_exists('location_start_lat',$step) || !array_key_exists('location_start_lng',$step) || !array_key_exists('location_end_lat',$step) || !array_key_exists('location_end_lng',$step) || ($step['location_start_lat']==NULL && $step['location_start_lng']==NULL) || ($step['location_end_lat']==NULL && $step['location_end_lng']==NULL) || ($step['location_start_lat']==0 && $step['location_start_lng']==0) || ($step['location_end_lat']==0 && $step['location_end_lng']==0) ) { return true; } return false; } protected static function processQuotesTripScooter($db, $member_id, $country, $trip) { syslog(LOG_WARNING,"ActivityApi::processQuotesTripScooter(\$db, $member_id, $country, \$trip)"); $scooter = $trip['scooter']; return $scooter; } protected static function processQuotesTripRideshare($db, $member_id, $country, $trip) { syslog(LOG_WARNING,"ActivityApi::processQuotesTripRideshare(\$db, $member_id, $country, \$trip)"); $rideshare = $trip['rideshare']; return $rideshare; } public static function processRidesharesOption($db, $member_id, $country, $trips) { syslog(LOG_WARNING,"ActivityApi::processRidesharesOption(\$db, $member_id, $country, \$trips)"); if ($country=='SG') { return ActivityApi::processRideshareOption( $db, $member_id, $country, $trips, 'Gojek', Quotes::VENDOR_GOJEK, "Asia/Singapore"); } else if ($country=='US') { $tz1 = Address::getTzByAddressId($db->getConnect(),$trips[0]["trip"]["location_start_id"]); if ($tz1=="") $tz1 = "America/Los_Angeles"; $trips_1 = ActivityApi::processRideshareOption( $db, $member_id, $country, $trips, 'Uber', Quotes::VENDOR_UBER, $tz1); $tz2 = Address::getTzByAddressId($db->getConnect(),$trips[0]["trip"]["location_end_id"]); if ($tz2=="") $tz2 = "America/Los_Angeles"; $trips_2 = ActivityApi::processRideshareOption( $db, $member_id, $country, $trips, 'Lyft', Quotes::VENDOR_LYFT, $tz2); $trips_3 = ActivityApi::processRideshareOption( $db, $member_id, $country, $trips, 'Autocab', Quotes::VENDOR_AUTOCAB, $tz1); // Basic merge? $n1 = count($trips_1) - 1; $n2 = count($trips_2) - 1; $n3 = count($trips_3) - 1; $res = []; if ($n1>=0 && $n2>=0 && $n3>=0) { $n = rand(0,12); //syslog(LOG_WARNING,"ActivityApi rand is ".$n); if ($n<4) { $res[] = $trips_1[$n1]; $res[] = $trips_2[$n2]; $res[] = $trips_3[$n3]; } else if ($n >=4 && $n < 8){ $res[] = $trips_2[$n2]; $res[] = $trips_3[$n3]; $res[] = $trips_1[$n1]; } else { $res[] = $trips_3[$n3]; $res[] = $trips_1[$n1]; $res[] = $trips_2[$n2]; } } else if ($n1>=0 && $n2>=0) { $n = rand(0,10); //syslog(LOG_WARNING,"ActivityApi rand is ".$n); if ($n<5) { $res[] = $trips_1[$n1]; $res[] = $trips_2[$n2]; } else { $res[] = $trips_2[$n2]; $res[] = $trips_1[$n1]; } } else if ($n1>=0 && $n3>=0) { $n = rand(0,10); //syslog(LOG_WARNING,"ActivityApi rand is ".$n); if ($n<5) { $res[] = $trips_1[$n1]; $res[] = $trips_3[$n3]; } else { $res[] = $trips_3[$n3]; $res[] = $trips_1[$n1]; } } else if ($n2>=0 && $n3>=0) { $n = rand(0,10); //syslog(LOG_WARNING,"ActivityApi rand is ".$n); if ($n<5) { $res[] = $trips_2[$n2]; $res[] = $trips_3[$n3]; } else { $res[] = $trips_3[$n3]; $res[] = $trips_2[$n2]; } } else if ($n1>=0) { $res[] = $trips_1[$n1]; } else if ($n2>=0) { $res[] = $trips_2[$n2]; } else if ($n3>=0) { $res[] = $trips_3[$n3]; } return $res; } return []; } public static function processGojekOption($db, $member_id, $country, $trips) { syslog(LOG_WARNING,"ActivityApi::processGojekOption(\$db, $member_id, $country, \$trips)"); return ActivityApi::processRideshareOption( $db, $member_id, $country, $trips, 'Gojek', Quotes::VENDOR_GOJEK, "Asia/Singapore"); } public static function processRideshareOption( $db, $member_id, $country, $trips, $rideshare, $transport_provider_id, $tz="Asia/Singapore") { syslog(LOG_WARNING,"ActivityApi::processRideshareOption(\$db, $member_id, $country, \$trips, $rideshare, $transport_provider_id, $tz)"); //"trips"=>$trips; if (!is_array($trips) && count($trips)<1) { syslog(LOG_WARNING,"Invalid trips!"); return $trips; // No idea how to handle it } $trip = $trips[0]; if (!array_key_exists('trip',$trip) || !is_array($trip['trip']) || count($trip['trip'])<10) { syslog(LOG_WARNING,"Invalid trip[0]!"); return $trips; // No idea how to handle it } $oldTrip = $trip['trip']; $newTrip = [ 'cheaper' => [], 'faster' => [], 'multimodal' => [], 'rideshare' => [], 'scooter' => [], 'trip' => $trip['trip'] ]; $origin = Address::getAddressById($db->getConnect(), $oldTrip['location_start_id']); $destination = Address::getAddressById($db->getConnect(), $oldTrip['location_end_id']); $availableTransportProviders = Gis::getCityServicesAvailableForCoordinates($db->getConnect(), $origin["lat"], $origin["lng"],[$transport_provider_id]); if (count($availableTransportProviders) == 0) { $availableTransportProviders = Gis::getCountryServicesAvailableForCoordinates( $db->getConnect(), $origin["lat"], $origin["lng"],[$transport_provider_id]); } if (count($availableTransportProviders) == 0) { return []; } list($res, $err) = GeocodeApi::route($db, $origin['latitude'], $origin['longitude'], $destination['latitude'], $destination['longitude'], 'driving' ); if ($err!=NULL || !is_array($res) || !isset($res["routes"]) || count($res["routes"])<1) { syslog(LOG_WARNING,'No available routes!'); syslog(LOG_WARNING,$err); return $trips; // We cannot route driving! } $route_overlay = []; $travel_time = PHP_INT_MAX; $travel_distance = PHP_INT_MAX; foreach ($res["routes"] as $route) { $r = GeocodeApi::processRoute($route); if ($travel_time>$r['duration']) { $travel_time = $r['duration']; $travel_distance = $r['distance']; } $route_overlay[] = $r; } $oldTrip["duration"] = $travel_time==PHP_INT_MAX ? NULL : $travel_time; $oldTrip["distance"] = $travel_distance==PHP_INT_MAX ? NULL : $travel_distance; /* $result = [ 'polyline' => array(), 'distance' => NULL, 'duration' => NULL, 'summary' => $route["summary"], 'overview_polyline' => $route["overview_polyline"], 'bounds' => $route["bounds"] ]; */ //syslog(LOG_WARNING,json_encode($res)); $route = $res["routes"][0]; $route_leg = $route["legs"][0]; $route_leg_steps = $route_leg["steps"]; $overview_polyline = $route["overview_polyline"]["points"]; $leg_steps = []; $distance = 0; $duration = 0; $location_start_lat = 0; $location_start_lng = 0; $location_end_lat = 0; $location_end_lng = 0; $polyline = []; $i = 0; $j=1; $n = count($route_leg_steps); for (; $i<$n; $i++) { $step = $route_leg_steps[$i]; if ($step["travel_mode"] == "DRIVING") { $location_start_lat = $step["start_location"]["lat"]; $location_start_lng = $step["start_location"]["lng"]; break; } $leg_steps[] = [ 'distance' => $step["distance"]["value"], 'duration' => $step["duration"]["value"], 'html_instructions' => $step["html_instructions"], 'location_end_lat' => $step["end_location"]["lat"], 'location_end_lng' => $step["end_location"]["lng"], 'location_start_lat' => $step["start_location"]["lat"], 'location_start_lng' => $step["start_location"]["lng"], 'polyline' => $step["polyline"]["points"], /*"yz|Fm_nxRi@e@_@P",*/ 'sid' => $j++, 'travel_mode' => $step["travel_mode"] ]; } for (;$i<$n; $i++) { $step = $route_leg_steps[$i]; if ($step["travel_mode"] != "DRIVING") { break; } $distance += $step["distance"]["value"]; $duration += $step["duration"]["value"]; $location_end_lat = $step["end_location"]["lat"]; $location_end_lng = $step["end_location"]["lng"]; $polyline[] = $step["polyline"]["points"]; } $leg_steps[] = [ 'distance' => $distance, 'duration' => $duration, 'fare_quote' => null, 'fare_raw' => null, 'html_instructions' => "Take ".$rideshare." to ".$destination["address"], 'location_end_lat' => $location_end_lat, 'location_end_lng' => $location_end_lng, 'location_start_lat' => $location_start_lat, 'location_start_lng' => $location_start_lng, 'polyline' => MultiModal::processPolyline($polyline), 'quote_group_id' => null, 'sid' => $j++, 'travel_mode' => strtoupper($rideshare) ]; for (;$i<$n; $i++) { $step = $route_leg_steps[$i]; $leg_steps[] = [ 'distance' => $step["distance"]["value"], 'duration' => $step["duration"]["value"], 'html_instructions' => $step["html_instructions"], 'location_end_lat' => $step["end_location"]["lat"], 'location_end_lng' => $step["end_location"]["lng"], 'location_start_lat' => $step["start_location"]["lat"], 'location_start_lng' => $step["start_location"]["lng"], 'polyline' => $step["polyline"]["points"], /*"yz|Fm_nxRi@e@_@P",*/ 'sid' => $j++, 'travel_mode' => $step["travel_mode"] ]; } list($cost,$quote_id) = QuoteApi::getRideshareQuote( $db, $origin["address"], $destination["address"], $member_id, $transport_provider_id, $country); $cost = ((int)100*$cost); $rideshare = [ 'cost' => $cost, 'cost_raw' => $cost, 'count' => 1, 'country' => $country, 'distance' => $oldTrip['distance'], 'dup_id' => null, 'duration' => $oldTrip['duration'], 'id' => $oldTrip['id'], 'location_end' => $destination['address'], 'location_end_id' => $oldTrip['location_end_id'], 'location_end_lat' => $destination['latitude'], 'location_end_lng' => $destination['longitude'], 'location_end_tz' => $destination['timezone'], 'location_start' => $origin['address'], 'location_start_id' => $oldTrip['location_start_id'], 'location_start_lat' => $origin['latitude'], 'location_start_lng' => $origin['longitude'], 'location_start_tz' => $origin['timezone'], 'member_id' => $member_id, 'options' => [], 'parsedemail_item_id' => null, /* $trip['id'] ??? */ 'private' => 't', 'scheduled' => null, 'trackedemail_item_id' => null, 'transport_provider_id' => $transport_provider_id, 'travel_date' => $oldTrip['travel_date'], 'travel_date_end' => $oldTrip['travel_date_end'], 'updated' => $oldTrip['updated'] ]; $leg_id = rand(1,10000); // virtual temporary... $leg_fare = $cost; // Get departure and arrival times... $date = new DateTime("now", new DateTimeZone($tz)); $date->add(new DateInterval('PT15M')); $departure_time = $date->getTimestamp(); $duration = $oldTrip['duration']>0 ? $oldTrip['duration'] : 15; $date->add(new DateInterval('PT'.$duration.'M')); $arrival_time = $date->getTimestamp(); $leg = [ 'id' => $leg_id, 'arrival_time' => $arrival_time, 'arrival_time_zone' => $destination['timezone'], 'arrival_timezone' => $tz, 'departure_time' => $departure_time, 'departure_time_zone' => $origin['timezone'], 'departure_timezone' => $tz, 'distance' => $oldTrip['distance'], 'duration' => $oldTrip['duration'], 'fare_raw' => $cost, 'polyline' => $overview_polyline, 'quote' => $cost, 'steps' => count($leg_steps), ]; $options = [ 'gid' => $leg_id, /* ??? */ 'leg_fare' => [$leg_id => $leg_fare], 'leg_steps' => [$leg_id => $leg_steps], 'legs' => [$leg], 'routes' => 1 ]; $rideshare['options'] = $options; $newTrip['rideshare'] = $rideshare; if (count($trips)>2) { array_shift($trips); // Remove the first element } $trips[] = $newTrip; syslog(LOG_WARNING,"ActivityApi: ".json_encode($trips)); return $trips; } }