response( array( 'error' => $message ), 404); } /** * Method GET * Get single record (by id) * http://DOMAIN/geocode/1 * @return string */ public function viewAction() { //id must be the first parameter after /geocode/x $id = array_shift($this->requestUri); if($id && (int)$id>0){ $db = new Db(); $address = Geocode::getAddressById($db->getConnect(), (int)$id); if(is_array($address) && count($address)>0){ return $this->response($address, 200); } } return $this->response( array( 'error'=> 'Data not found' ), 404); } public function createAction() { $message = "Failed to run sgbike directions"; $addresses = $this->requestParams["addresses"] ?? array(); $options = $this->requestParams["options"] ?? array(); try { throw new Exception("We are sorry but sgbikes are not yet launched in your country.  Please check back again soon!"); if ($addresses==NULL || !is_array($addresses) || count($addresses)<1 || !isset($addresses[0]["address"])) { throw new Exception("Invalid input"); } $db = new Db(); $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) = self::sgbike($db, $input); if (!is_array($result) || count($result)<1) { throw new Exception($err!=''? $err : 'No sgbike 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 = self::options($db, $input, $result, $options); } return $this->response( array( 'data'=>$result, 'options'=>$options), 200); } catch (Exception $e) { error_log(json_encode($e)); $message = $e->getMessage(); } return $this->response( array( "error" => $message ), 500); } public function updateAction() { return $this->response( array( "error" => "Update error" ), 400); } public function deleteAction() { return $this->response( array( "error" => "Delete error" ), 500); } public function sgbike($db, $input) { global $savvyext; $httpAuthToken = $savvyext->cfgReadChar('system.oauth2_token'); $oauth2_url = $savvyext->cfgReadChar('system.oauth2_url'); // Call sgbike service $gps1 = $input[1]["coordinates"]; $gps2 = $input[2]["coordinates"]; $data = http_build_query( array( 'gps' => sprintf("%s,%s,%s,%s",$gps1["lat"],$gps1["lng"],$gps2["lat"],$gps2["lng"]), 'from' => $input[1]["address"], 'to' => $input[2]["address"] ) ); $url = $oauth2_url."sgbike?" . $data; //error_log($url); $opts = array( 'http' => array( 'method' => "GET", 'header' => "Content-Type: application/x-www-form-urlencoded\r\n" . "Accept: application/json\r\n" . "Authorization: Server-Token ${httpAuthToken}\r\n", ), "ssl" => array( "verify_peer"=>false, "verify_peer_name"=>false, ) ); $context = stream_context_create($opts); $body = file_get_contents($url, false, $context); $sgbike = json_decode($body,true); if (is_array($sgbike) && is_array($sgbike["data"]) && !isset($sgbike["error"])) { // Cache the result in DB //$sgbike["data"]["id"] = sgbike::save($db->getConnect(), $sgbike["data"]); return array($sgbike["data"], NULL); } else if (is_array($sgbike) && isset($sgbike["error"])) { $body = $sgbike["error"]; } return array(NULL, "sgbike service call error: ".$body); } public function options($db, $input, $result, $options) { if (isset($options["travel_time"]) && $options["travel_time"]) { $travel_time = 0; foreach ($result as $step) { $travel_time += $step["duration"]; } $options["travel_time"] = (int)$travel_time; } if (isset($options["quote"]) && $options["quote"] && is_array($result) && count($result)>1 && isset($result[1]["duration"]) && $result[1]["duration"]>0) { $sgbike_time = $result[1]["duration"]; // 2nd entry is the sgbike step list($res,$err) = self::quote($db, $sgbike_time, 'SG'); if ($err!=NULL) { $options["error"] = $err; } else { $options["quote"] = $res; } } if (isset($options["route_overlay"]) && $options["route_overlay"]) { $options = self::directions($db, $input, $result, $options); } return $options; } public function quote($db, $seconds, $country='SG',$step_details=[]) { global $savvyext; $sgbike_token = $savvyext->cfgReadChar('system.scooter_token'); // Check local DB cache /* list ($res, $err) = sgbike::checksgbikeQuote($db->getConnect(), $minutes, $country); if (is_array($res) && isset($res[0]["quote"])) { return array($res, NULL); } */ // Call sgbike quote service $data = http_build_query( array( 'minutes' => (int)($seconds/60), 'country' => $country ) ); $url = $savvyext->cfgReadChar('microservices.catalog') . "/api/v1/bikes/price-quote/?" . $data; //echo $url;exit; // https://float.catalog.float.sg/api/v1/bikes/price-quote/?minutes=49&country=SG // curl -X GET "https://float.catalog.float.sg/api/v1/bikes/price-quote/?minutes=60&country=SG" -H "accept: application/json" -H "Authorization: Server-Token MNHEzG4uEDy47NhZqBnBKM9kvKVGXzC8" $opts = array( 'http' => array( 'method' => "GET", 'header' => "Content-Type: application/x-www-form-urlencoded\r\n" . "Accept: application/json\r\n" . "Authorization: Server-Token $sgbike_token\r\n", ), "ssl" => array( "verify_peer"=>false, "verify_peer_name"=>false, ) ); $context = stream_context_create($opts); $body = file_get_contents($url, false, $context); $data = json_decode($body,true); if (is_array($data) && count($data) > 0 && !isset($data["error"])) { // Cache the result in DB $extra_fee_default = 5.0; // default extra fee fall-back $apply_extra_fee = false; $duration = ceil($duration/60);// if (is_array($step_details) && $step_details["line"] == null && $step_details["headsign"] != null) { $apply_extra_fee = true; if ($vendor == null) { $vendor = $step_details["headsign"]; /* The provider will be in the "headsign" and this means we drop off anywhere */ } } if (is_array($data) && count($data) > 0 && !isset($data["error"])) { if ($vendor != null) { foreach ($data as $d) { if (isset($d['provider']) && $d['provider'] == $vendor) { return ($d["quote"]+ ($apply_extra_fee ? ($d["pricing"]["dropoff_anywhere"] != null ? $d["pricing"]["dropoff_anywhere"] : $extra_fee_default) : 0))*100; } } } $result = 0.0; // If we do not know the vendor - get the most expensive foreach ($data as $d) { $value = $d["quote"]; if ($value > $result) { $result = $value; $extra_fee = $d["pricing"]["dropoff_anywhere"] != null ? $d["pricing"]["dropoff_anywhere"] : $extra_fee_default; } } $result += ($apply_extra_fee ? $extra_fee : 0); } return array($result*100,null); } else if (is_array($sgbike) && isset($sgbike["error"])) { $body = $sgbike["error"]; } return array(NULL, "sgbike quote service call error: ".$body); } public function directions($db, $input, $result, $options) { // Walking with waypoints or 3 routes $mode='walking'; $waypoints=[ $result[1]["data"]["location_start_lat"], $result[1]["data"]["location_start_lng"], $result[1]["data"]["location_end_lat"], $result[1]["data"]["location_end_lng"] ]; $fromLat = $input[1]["coordinates"]["lat"]; $fromLng = $input[1]["coordinates"]["lng"]; $toLat = $input[2]["coordinates"]["lat"]; $toLng = $input[2]["coordinates"]["lng"]; list($res, $err) = GeocodeApi::route( $db, $fromLat, $fromLng, $toLat, $toLng, $mode, $waypoints); if ($err!=NULL || !is_array($res) || !isset($res["routes"]) || count($res["routes"])<1) { $options['error'] = $err!=NULL ? $err : 'No available routes'; return $options; } $options["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']; } $options["route_overlay"][] = $r; } // ["route_overlay"] return $options; } }