737 lines
32 KiB
PHP
737 lines
32 KiB
PHP
<?php
|
|
|
|
class GeocodeApi extends Api
|
|
{
|
|
public $apiName = 'geocode';
|
|
const DEFAULT_COUNTRY_CODE = 'SG';
|
|
|
|
public function indexAction() {
|
|
$message = 'Data not found';
|
|
$address = $this->requestParams["address"] ?? "";
|
|
$member_id = $this->requestParams["member_id"] ?? 0;
|
|
$country = $this->requestParams["country"] ?? self::DEFAULT_COUNTRY_CODE;
|
|
try {
|
|
if ($address==NULL || $address=="") {
|
|
throw new Exception("Invalid input");
|
|
}
|
|
$db = new Db();
|
|
|
|
// DEBUG
|
|
$country = Geocode::mockGPSCountry($db->getConnect(), $member_id, $country, 'default');
|
|
|
|
$result = [];
|
|
$result["address"] = $address;
|
|
list($result["geocode"], $result["error"]) = GeocodeApi::geocode($db, $address, $country);
|
|
return $this->response($result, 200);
|
|
} catch (Exception $e) {
|
|
error_log(json_encode($e));
|
|
$message = $e->getMessage();
|
|
}
|
|
return $this->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() {
|
|
error_log('GeocodeApi::createAction()');
|
|
$message = "";
|
|
$addresses = $this->requestParams["addresses"] ?? array();
|
|
$options = $this->requestParams["options"] ?? array();
|
|
$gps_country_code = $this->requestParams["gps_country_code"] ?? null;
|
|
$country = $this->requestParams["country"] ?? SELF::DEFAULT_COUNTRY_CODE;
|
|
$member_id = $this->requestParams["member_id"] ?? 0;
|
|
$address_data = $this->requestParams["address_data"] ?? null;
|
|
try {
|
|
if ($addresses==NULL || !is_array($addresses) || count($addresses)<1 || !isset($addresses[0]["address"])) {
|
|
throw new Exception("Invalid input");
|
|
}
|
|
|
|
$db = new Db();
|
|
|
|
// DEBUG
|
|
$country = Geocode::mockGPSCountry($db->getConnect(), $member_id, $country, 'default');
|
|
|
|
// Check country
|
|
if ($country!='SG' && $country!='US') {
|
|
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 ($gps_country_code==null) {
|
|
$gps_country_code = $country;
|
|
}
|
|
|
|
$result = array();
|
|
foreach ($addresses as $item) {
|
|
if (isset($item["type"]) && $item["type"]>0 && is_array($address_data)) {
|
|
if ($item["type"]==1
|
|
&& array_key_exists('location_start',$address_data)
|
|
&& is_array($address_data['location_start'])
|
|
&& array_key_exists('id',$address_data['location_start'])
|
|
&& $address_data['location_start']['id']>0) {
|
|
$item["geocode"] = GeocodeApi::addressDataToGeocode($address_data['location_start']);
|
|
$item["error"] = NULL;
|
|
$result[] = $item;
|
|
continue;
|
|
}
|
|
if ($item["type"]==2
|
|
&& array_key_exists('location_end',$address_data)
|
|
&& is_array($address_data['location_end'])
|
|
&& array_key_exists('id',$address_data['location_end'])
|
|
&& $address_data['location_end']['id']>0
|
|
) {
|
|
$item["geocode"] = GeocodeApi::addressDataToGeocode($address_data['location_end']);
|
|
$item["error"] = NULL;
|
|
$result[] = $item;
|
|
continue;
|
|
}
|
|
}
|
|
if (isset($item["address"]) && $item["address"]!="") {
|
|
list($item["geocode"], $item["error"]) = GeocodeApi::geocode($db, $item["address"], $country);
|
|
} else {
|
|
$item["geocode"] = NULL;
|
|
$item["error"] = "Invalid address";
|
|
}
|
|
$result[] = $item;
|
|
}
|
|
if (count($result)<1) throw new Exception('Sorry, but we cannot find your address');
|
|
|
|
$withinTheServiceArea = GeocodeApi::checkWithinTheServiceArea($country,$result, false);
|
|
|
|
if ((isset($options["travel_time"]) && $options["travel_time"]) ||
|
|
(isset($options["route_overlay"]) && $options["route_overlay"])) {
|
|
$options = GeocodeApi::options($db, $result, $options);
|
|
}
|
|
|
|
list($providers,$country,$service_enabled, $currency_code) = GeocodeApi::getProviders(
|
|
$db, $gps_country_code, $country, $options, $member_id);
|
|
return $this->response(array(
|
|
'addresses' => $result,
|
|
'options' => $options,
|
|
'providers' => $providers,
|
|
'country' => $country,
|
|
'service' => $service_enabled,
|
|
'currency' => $currency_code), 200);
|
|
} catch (Exception $e) {
|
|
error_log(json_encode($e));
|
|
$message = $e->getMessage();
|
|
}
|
|
return $this->response(
|
|
array(
|
|
"error" => $message==""?"Failed to run geocoder":$message
|
|
), 500);
|
|
}
|
|
|
|
protected function addressDataToGeocode($address_data) {
|
|
// TODO: check the timeZoneId?
|
|
return [
|
|
"address" => $address_data["address"],
|
|
"lat" => array_key_exists("latitude",$address_data) ? $address_data["latitude"] : $address_data["lat"],
|
|
"lng" => array_key_exists("longitude",$address_data) ? $address_data["longitude"] : $address_data["lng"],
|
|
"postal" => $address_data["postal"],
|
|
"timezone" => $address_data["timezone"],
|
|
"id" => $address_data["id"],
|
|
"country" => $address_data["country"],
|
|
"timeZoneId" => $address_data["timeZoneId"]
|
|
];
|
|
/*
|
|
id: "12400"
|
|
address: "2600 Bentley Rd SE, Marietta, GA 30067, USA"
|
|
latitude: "33.9212128"
|
|
longitude: "-84.4766081"
|
|
timezone: "3"
|
|
geocoding_date: "2019-12-31"
|
|
postal: "30067"
|
|
country: "US"
|
|
geometry: "0101000020E61000008FA042BF801E55C00B54104DEAF54040"
|
|
description: null
|
|
timeZoneId: "America/New_York"
|
|
*/
|
|
}
|
|
|
|
public function updateAction() {
|
|
$message = 'Data not found';
|
|
$latitude = $this->requestParams["latitude"] ?? 0;
|
|
$longitude = $this->requestParams["longitude"] ?? 0;
|
|
$country = $this->requestParams["country"] ?? GeocodeApi::DEFAULT_COUNTRY_CODE;
|
|
$internal = $this->requestParams["internal"] ?? false;
|
|
try {
|
|
if ($latitude===NULL || $longitude===NULL || ($latitude==0 && $longitude==0)) {
|
|
$message = "Invalid input: $latitude,$longitude,$country";
|
|
$message.= json_encode($this->requestParams);
|
|
throw new Exception($message);
|
|
//throw new Exception("Invalid input");
|
|
}
|
|
if ($internal) {
|
|
$this->encryption = false;
|
|
}
|
|
$db = new Db();
|
|
list($address, $err) = GeocodeApi::reverseGeocode(
|
|
$db, $latitude, $longitude);
|
|
if ($address!=NULL && strlen($address)>5) {
|
|
return $this->response([
|
|
"address" => $address,
|
|
"latitude" => $latitude,
|
|
"longitude" => $longitude,
|
|
"country" => $country
|
|
], 200);
|
|
}
|
|
if ($err && $err!="") {
|
|
$message = $err;
|
|
}
|
|
} catch (Exception $e) {
|
|
error_log(json_encode($e));
|
|
$message = $e->getMessage();
|
|
}
|
|
return $this->response(
|
|
array(
|
|
'error' => $message
|
|
), 404);
|
|
}
|
|
|
|
public function deleteAction() {
|
|
return $this->response(
|
|
array(
|
|
"error" => "Delete error"
|
|
), 500);
|
|
}
|
|
|
|
public static function processAddressCountry($db, $address, $country) {
|
|
syslog(LOG_WARNING,"GeocodeApi::processAddressCountry(\$db, $address, $country)");
|
|
$code = $country;
|
|
$name = "";
|
|
$short_name = "";
|
|
$res = Geocode::getCountry($db->getConnect(),$country);
|
|
if (is_array($res) && array_key_exists('country',$res) && $res['country']!='') {
|
|
$name = $res['country'];
|
|
$short_name = $res['short_name'];
|
|
$code = $res['code'];
|
|
}
|
|
// if it has the country name in address?
|
|
if ($name!="" && strripos($address,$name)!==false) {
|
|
return $address;
|
|
}
|
|
|
|
$length = strlen($code); // if it ends with the country code?
|
|
$lengthcode = strlen($short_name); // if it ends with the country code?
|
|
if ((strripos($address," ${code}")!==false || strrpos($address,",${short_name}")!==false)) {
|
|
return $address;
|
|
}
|
|
$address.= ", ".($name!=""?$name:$code); // append country name or code to address
|
|
return $address;
|
|
}
|
|
|
|
public static function geocode($db, $address, $country, $client_id="Unknown") {
|
|
|
|
syslog(LOG_WARNING,"GeocodeApi::geocode(\$db,'${address}',$country,'${client_id}')");
|
|
global $savvyext;
|
|
$httpAuthToken = $savvyext->cfgReadChar('system.oauth2_token');
|
|
$oauth2_url = $savvyext->cfgReadChar('system.oauth2_url');
|
|
// Check for broken clients and assume default country for now
|
|
if ($country==null || $country=="") {
|
|
$country = self::DEFAULT_COUNTRY_CODE;
|
|
}
|
|
$address_input = $address;
|
|
// Check local DB cache
|
|
list ($res, $err) = Geocode::checkLatLngByAddress($db->getConnect(), $address, $country);
|
|
if (is_array($res) && isset($res["timeZoneId"]) && !empty($res["postal"])) {
|
|
/* if(empty($res['plus_code']) || $res['address'] == $res['plus_code']){
|
|
$reverse_info = self::reverseGeocodeService($db, $res['lat'],$res['lng']);
|
|
$log = [
|
|
'message' => 'GeocodeApi::reverseGeocodeService',
|
|
'data' => ['reverse_info'=>$reverse_info]
|
|
];
|
|
syslog(LOG_WARNING, "GeocodeApi::reverseGeocodeService => ".json_encode($log));
|
|
Logger::debug($log);
|
|
if(!empty($reverse_info[0]['plus_code'])){
|
|
$dataUpdate = [
|
|
'plus_code' => $reverse_info[0]['plus_code'],
|
|
'address' => $reverse_info[0]['address'],
|
|
];
|
|
$condition = "id = '".$res['id']."'";
|
|
Address::update($db->getConnect(),$condition, $dataUpdate );
|
|
$res['plus_code'] = $reverse_info[0]['plus_code'];
|
|
$res['address'] = $reverse_info[0]['address'];
|
|
}
|
|
} */
|
|
return [$res, NULL];
|
|
} else {
|
|
syslog(LOG_WARNING, "GeocodeApi::geocode => ".json_encode($res));
|
|
}
|
|
// Massage address
|
|
$address = GeocodeApi::processAddressCountry($db, $address, $country);
|
|
|
|
// Call geocoding service
|
|
$data = http_build_query(
|
|
array(
|
|
'address' => $address,
|
|
)
|
|
);
|
|
$headers = getallheaders();
|
|
$client_id = isset($headers['client_id'])?$headers['client_id']:$client_id;
|
|
syslog(LOG_WARNING,"GeocodeApi::oauth2_geocode(\$db,'${address}',$country,'${client_id}')");
|
|
$url = $oauth2_url."geocode?" . $data;
|
|
$opts = array(
|
|
'http' => array(
|
|
'method' => "GET",
|
|
'timeout' => 60, /* 1 minute */
|
|
'header' =>
|
|
//"Content-Type: application/x-www-form-urlencoded\r\n" .
|
|
//"Accept: application/json\r\n" .
|
|
"Authorization: Server-Token ${httpAuthToken}\r\n".
|
|
"client_id: ${client_id}\r\n",
|
|
),
|
|
"ssl" => array(
|
|
"verify_peer"=>false,
|
|
"verify_peer_name"=>false,
|
|
)
|
|
);
|
|
$benchmarks['function'] = 'GeocodeApi::geocode';
|
|
Utilities::startBenchmark($benchmarks);
|
|
$context = stream_context_create($opts);
|
|
$body = file_get_contents($url, false, $context);
|
|
$geocoded = json_decode($body,true);
|
|
$log = [
|
|
'message' => 'GeocodeApi::geocode call',
|
|
'data' => ['address_input'=>$address_input],
|
|
'errror' => $err
|
|
];
|
|
Logger::debug($log);
|
|
Utilities::finishBenchmark($benchmarks);
|
|
|
|
if (is_array($geocoded) && is_array($geocoded["data"]) && !isset($geocoded["error"])) {
|
|
// Cache the result in DB
|
|
syslog(LOG_WARNING, "GeocodeApi::geocode success => ".json_encode($geocoded));
|
|
$address_output = pg_escape_string(html_entity_decode($geocoded["data"]["address"],ENT_QUOTES|ENT_HTML5,"UTF-8"));
|
|
$geocoded["data"]['address_input'] = $address_input;
|
|
$geocoded["data"]['country'] = $country;
|
|
/* if(empty($geocoded["data"]['plus_code'])){
|
|
$benchmarks['function'] = 'google geocode';
|
|
|
|
$reverse_info = self::reverseGeocodeService($db,$geocoded["data"]['lat'],$geocoded["data"]['lng']);
|
|
|
|
if(!empty($reverse_info[0]['plus_code'])){
|
|
$geocoded["data"]['plus_code'] = $reverse_info[0]['plus_code'];
|
|
}
|
|
} */
|
|
|
|
$address = Geocode::save($db->getConnect(), $geocoded["data"]);
|
|
if(isset($address['id'])){
|
|
if($address_input!=$address["address"]){
|
|
Address::saveAliasAddress($db->getConnect(), $address['id'], $address_input,"UserInput");
|
|
}
|
|
$geocoded["data"] = array_merge($geocoded["data"], $address);
|
|
}
|
|
return array($geocoded["data"], NULL);
|
|
} else if (is_array($geocoded) && isset($geocoded["error"])) {
|
|
$body = $geocoded["error"];
|
|
}
|
|
syslog(LOG_WARNING, "GeocodeApi::geocode => ".$body);
|
|
return array(NULL, "Geocoding service call error: ".$body);
|
|
}
|
|
|
|
public static function reverseGeocode($db, $lat, $lng) {
|
|
syslog(LOG_WARNING,"GeocodeApi::reverseGeocode(${lat}, ${lng})");
|
|
syslog(LOG_WARNING,"lat=${lat},lng=${lng}");
|
|
if (($lat==NULL && $lng==NULL) || ($lat==0 && $lng==0)) {
|
|
return [NULL, "Invalid latitude and longitude"];
|
|
}
|
|
// Reverse geocode using local DB
|
|
list($res,$err) = Geocode::reverseGeocode($db->getConnect(), $lat, $lng);
|
|
if (is_array($res) && isset($res["address"])) {
|
|
return [$res["address"],NULL];
|
|
}
|
|
|
|
// Revers geocode using gps DB
|
|
list($res,$err) = Geocode::reverseGeocodeGps(
|
|
$db->getConnect(), $db->getConnectGPS(), $lat, $lng);
|
|
if (is_array($res) && isset($res["address"])) {
|
|
return [$res["address"],NULL];
|
|
}
|
|
|
|
// Reverse geocode service using Google Maps API
|
|
list($res,$err) = GeocodeApi::reverseGeocodeService($db, $lat, $lng);
|
|
if (is_array($res) && isset($res["address"]) && $res["address"]!="") {
|
|
// Cache!
|
|
Geocode::reverseGeocodeCache(
|
|
$db->getConnect(), $res["address"], $lat, $lng, $res["postal"], $res["country"], $res["timeZoneId"], $res['plus_code']
|
|
);
|
|
return [$res["address"],NULL];
|
|
} else {
|
|
syslog(LOG_WARNING,"reverseGeocodeService() failed: ${err}");
|
|
}
|
|
return [NULL, $err];
|
|
}
|
|
|
|
public static function reverseGeocodeService($db, $lat, $lng,$client_id="Unknown") {
|
|
syslog(LOG_WARNING,"GeocodeApi::reverseGeocodeService(${lat},${lng})");
|
|
global $savvyext;
|
|
$httpAuthToken = $savvyext->cfgReadChar('system.oauth2_token');
|
|
$oauth2_url = $savvyext->cfgReadChar('system.oauth2_url');
|
|
$data = http_build_query(
|
|
array(
|
|
'lat' => $lat,
|
|
'lng' => $lng
|
|
)
|
|
);
|
|
$url = $oauth2_url."reverse?" . $data;
|
|
$opts = array(
|
|
'http' => array(
|
|
'method' => "GET",
|
|
'timeout' => 60, /* 1 minute */
|
|
'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);
|
|
$headers = getallheaders();
|
|
$client_id = isset($headers['client_id'])?$headers['client_id']:$client_id;
|
|
//syslog(LOG_WARNING,"BODY: ".$body);
|
|
$log = [
|
|
'message' => 'GeocodeApi::reverse call',
|
|
'data' => ['client_id'=>$client_id, 'lat'=>$lat, 'lng'=>$lng]
|
|
];
|
|
Logger::debug($log);
|
|
$geocoded = json_decode($body,true);
|
|
if (is_array($geocoded) && is_array($geocoded["data"]) && !isset($geocoded["error"])) {
|
|
//syslog(LOG_WARNING,json_encode($geocoded));
|
|
// Cache the result in DB
|
|
/*Geocode::saveDistanceCache(
|
|
$db->getConnect(), $fromLat, $fromLng, $toLat, $toLng, $geocoded["data"]);
|
|
*/
|
|
$address_output = pg_escape_string(html_entity_decode($geocoded["data"]["address"],ENT_QUOTES|ENT_HTML5,"UTF-8"));
|
|
return [$geocoded["data"], NULL];
|
|
} else if (is_array($geocoded) && isset($geocoded["error"])) {
|
|
$body = $geocoded["error"];
|
|
}
|
|
return [NULL, "Reverse geocoding service call error: ".$body];
|
|
}
|
|
|
|
public function checkWithinTheServiceArea($country,$result,$ex=true) {
|
|
error_log("GeocodeApi::checkWithinTheServiceArea($country,\$result,".($ex?"TRUE":"FALSE").")");
|
|
$limits = Geocode::COUNTRY_LIMITS;
|
|
if (!is_array($limits) || !array_key_exists($country,$limits) || !is_array($limits[$country])) {
|
|
return true; // Error, unhandled country or no limits
|
|
}
|
|
$fromLat = $fromLng = NULL;
|
|
foreach ($result as $element) {
|
|
if ($element["type"]==1) {
|
|
$fromLat = $element["geocode"]["lat"];
|
|
$fromLng = $element["geocode"]["lng"];
|
|
|
|
}
|
|
}
|
|
if ($fromLat==NULL || $fromLng==NULL) return true; // Do not know how to handle it - throw exception?
|
|
$within = false;
|
|
foreach ($limits[$country] as $limit) {
|
|
$radius = $limit['radius']/1000.0;
|
|
$d = GeocodeApi::distanceBetweenTwoGpsCoordinates(
|
|
$fromLat,$fromLng,$limit['latitude'],$limit['longitude'],'K');
|
|
//error_log("$d <= $radius");
|
|
if ($d <= $radius) {
|
|
$within = true;
|
|
break;
|
|
}
|
|
}
|
|
if ($within) return true; // OK
|
|
if ($ex) { // soft or hard fail?
|
|
throw new RuntimeException('Origin address is out of the service area');
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private function options($db, $result, $options) {
|
|
error_log('GeocodeApi::options()');
|
|
$fromLat = $fromLng = $toLat = $toLng = NULL;
|
|
foreach ($result as $element) {
|
|
//error_log(json_encode($element));
|
|
if ($element["type"]==1) {
|
|
$fromLat = $element["geocode"]["lat"];
|
|
$fromLng = $element["geocode"]["lng"];
|
|
}
|
|
if ($element["type"]==2) {
|
|
$toLat = $element["geocode"]["lat"];
|
|
$toLng = $element["geocode"]["lng"];
|
|
}
|
|
}
|
|
//error_log(json_encode($result));
|
|
if ($fromLat==NULL || $toLat==NULL) {
|
|
$options['error'] = 'No coordinates available';
|
|
return $options;
|
|
}
|
|
if ($options["travel_time"] && !$options["route_overlay"]) {
|
|
//error_log('*** Google distance matrix ***');
|
|
// Use distance matrix
|
|
list($res, $err) = GeocodeApi::distancegps($db, $fromLat, $fromLng, $toLat, $toLng);
|
|
//error_log(json_encode($res));
|
|
if ($err!=NULL) {
|
|
$options['error'] = $err;
|
|
return $options;
|
|
}
|
|
$options["travel_time"] = $res["duration"];
|
|
$options["travel_distance"] = $res["distance"];
|
|
$options["route_overlay"] = NULL;
|
|
} else {
|
|
//error_log('*** Google directions ***');
|
|
// Use directions
|
|
list($res, $err) = GeocodeApi::route($db, $fromLat, $fromLng, $toLat, $toLng);
|
|
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;
|
|
}
|
|
$options["travel_time"] = $travel_time==PHP_INT_MAX ? NULL : $travel_time;
|
|
$options["travel_distance"] = $travel_distance==PHP_INT_MAX ? NULL : $travel_distance;
|
|
}
|
|
//https://developers.google.com/maps/documentation/utilities/polylinealgorithm
|
|
//overview_polyline
|
|
//bounds contains the viewport bounding box of the overview_polyline.
|
|
return $options;
|
|
}
|
|
|
|
public function distancegps($db, $fromLat, $fromLng, $toLat, $toLng) {
|
|
global $savvyext;
|
|
$httpAuthToken = $savvyext->cfgReadChar('system.oauth2_token');
|
|
$oauth2_url = $savvyext->cfgReadChar('system.oauth2_url');
|
|
// Check local DB cache
|
|
/*
|
|
list ($res, $err) = Geocode::checkDistanceCache(
|
|
$db->getConnect(), $fromLat, $fromLng, $toLat, $toLng);
|
|
if (is_array($res) && isset($res["duration"])) {
|
|
return array($res, NULL);
|
|
}
|
|
*/
|
|
// Call geocoding service
|
|
$data = http_build_query(
|
|
array(
|
|
'gps' => implode(",",array($fromLat, $fromLng, $toLat, $toLng)),
|
|
)
|
|
);
|
|
$url = $oauth2_url."distancegps?" . $data;
|
|
$opts = array(
|
|
'http' => array(
|
|
'method' => "GET",
|
|
'timeout' => 60, /* 1 minute */
|
|
'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);
|
|
$geocoded = json_decode($body,true);
|
|
if (is_array($geocoded) && is_array($geocoded["data"]) && !isset($geocoded["error"])) {
|
|
// Cache the result in DB
|
|
Geocode::saveDistanceCache(
|
|
$db->getConnect(), $fromLat, $fromLng, $toLat, $toLng, $geocoded["data"]);
|
|
return array($geocoded["data"], NULL);
|
|
} else if (is_array($geocoded) && isset($geocoded["error"])) {
|
|
$body = $geocoded["error"];
|
|
}
|
|
return array(NULL, "Geocoding service call error: ".$body);
|
|
}
|
|
|
|
public function route($db, $fromLat, $fromLng, $toLat, $toLng, $mode='driving',$waypoints=[]) {
|
|
syslog(LOG_WARNING,"GeocodeApi::route(\$db, $fromLat, $fromLng, $toLat, $toLng, $mode, \$waypoints=[])");
|
|
global $savvyext;
|
|
$httpAuthToken = $savvyext->cfgReadChar('system.oauth2_token');
|
|
$oauth2_url = $savvyext->cfgReadChar('system.oauth2_url');
|
|
// Check local DB cache
|
|
/*list ($res, $err) = Geocode::checkDistanceCache(
|
|
$db->getConnect(), $fromLat, $fromLng, $toLat, $toLng);
|
|
if (is_array($res) && isset($res["duration"])) {
|
|
return array($res, NULL);
|
|
}*/
|
|
// Call geocoding service
|
|
$data = http_build_query(
|
|
array(
|
|
'gps' => implode(",",array($fromLat, $fromLng, $toLat, $toLng)),
|
|
'mode' => $mode,
|
|
'waypoints' => implode(",",$waypoints)
|
|
)
|
|
);
|
|
$url = $oauth2_url."route?" . $data;
|
|
$opts = array(
|
|
'http' => array(
|
|
'method' => "GET",
|
|
'timeout' => 60, /* 1 minute */
|
|
'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);
|
|
file_put_contents('/home/savvy/FloatWeb/adminsavvy/routes.json', $body);
|
|
//file_put_contents('/home/savvy/FloatWeb/routes.json',json_encode(array('test'=>false)));
|
|
$geocoded = json_decode($body,true);
|
|
if (is_array($geocoded) && is_array($geocoded["data"]) && !isset($geocoded["error"])) {
|
|
// Cache the result in DB
|
|
/*Geocode::saveDistanceCache(
|
|
$db->getConnect(), $fromLat, $fromLng, $toLat, $toLng, $geocoded["data"]);
|
|
*/
|
|
return array($geocoded["data"], NULL);
|
|
} else if (is_array($geocoded) && isset($geocoded["error"])) {
|
|
$body = $geocoded["error"];
|
|
}
|
|
return array(NULL, "Routing service call error: ".$body);
|
|
}
|
|
|
|
public function processRoute($route) {
|
|
require_once('../common/Polyline.php');
|
|
$result = [
|
|
'polyline' => array(),
|
|
'distance' => NULL,
|
|
'duration' => NULL,
|
|
'summary' => $route["summary"],
|
|
'overview_polyline' => $route["overview_polyline"],
|
|
'bounds' => $route["bounds"]
|
|
];
|
|
// ["bounds"]=> ["northeast"] => (["lat"],["lng"])
|
|
// ["bounds"]=> ["southwest"] => (["lat"],["lng"])
|
|
$distance = 0;
|
|
$duration = 0;
|
|
foreach ($route["legs"] as $leg) {
|
|
$distance += $leg["distance"]["value"];
|
|
$duration += $leg["duration"]["value"];
|
|
// ["start_address"]=> "10 Bayfront Ave, Singapore 018956"
|
|
// ["end_address"] => "97 Meyer Rd, Singapore 437918"
|
|
// ["start_location"] => (["lat"],["lng"])
|
|
// ["end_location"]=> (["lat"],["lng"])
|
|
foreach ($leg["steps"] as $step) {
|
|
// ["distance"]=> ["value"]
|
|
// ["duration"]=> ["value"]
|
|
// ["start_location"] => (["lat"],["lng"])
|
|
// ["end_location"]=> (["lat"],["lng"])
|
|
// ["html_instructions"]=> "Head <b>north</b> on <b>Sheares Ave</b> toward <b>Exit 15</b>"
|
|
// ["polyline"]=> array(1) { ["points"] } string(45) "osyFek|xR}ASyCY}@Ga@Eo@CsDCaBAaB?u@As@?q@?q@?"
|
|
// ["travel_mode"]=> string(7) "DRIVING"
|
|
$points = Polyline::decode($step["polyline"]["points"]);
|
|
$result['polyline'] = array_merge($result['polyline'],Polyline::pair($points));
|
|
}
|
|
}
|
|
//=> array(1) { ["points"]=> string(429) "osyFek|xR}ASyCY_BMcFGcEAmEAw@NwBBsB@aADeDBoC@cEHoCNiALuARa@?O?g@HeCf@_Bn@UPOL{@b@{@p@iAnAwA~AeAfAcC`CeDtC{@r@aCrB{BpBOPMK{BmB}GyFiBgBcBeBeD}DcDaEkEaFaBiBeCkCeAkA{BaCc@m@Q_@Om@WwA_@oC_BqKi@aCi@}BSsAaAiFW{@Ui@[i@kE}FsA}Bw@{A]u@e@iB_@aBAGvBU`AMzBc@hB_@dEu@|Di@b@K`@W^a@\_AZ_AHi@Ro@x@mDr@{BvAoFt@}BZw@l@gAbBcC`@q@fA}An@u@hAmAnAmAZ[hCuA~@MjAQ~AElBGbAEZEb@MTOR]P_@HWBo@N}FLiCBu@Ly@JYp@_EdGz@hALz@FdDd@Fa@TiBh@aE^oCCe@yAeHj@GN@NRPRN?r@O"}
|
|
//=> string(14) "Mountbatten Rd"
|
|
$result['distance'] = $distance;
|
|
$result['duration'] = $duration;
|
|
return $result;
|
|
}
|
|
|
|
public function withinCity($address,$addrLat,$addrLng,$city,$cityLat,$cityLng,$radius=25) {
|
|
//error_log("public function withinCity($address,$addrLat,$addrLng,$city,$cityLat,$cityLng,$radius)");
|
|
if (stripos($address,$city)!==false) {
|
|
//error_log("'${address}' is within the '${city}'");
|
|
return true;
|
|
}
|
|
$distance = GeocodeApi::distanceBetweenTwoGpsCoordinates($addrLat,$addrLng,$cityLat,$cityLng,'K');
|
|
if ($distance!=0 && $distance<$radius) {
|
|
//error_log("${addrLat},${addrLng} is less(${distance}) than ${radius}km from ${cityLat},${cityLng}");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function distanceBetweenTwoGpsCoordinates($lat1,$lon1,$lat2,$lon2,$unit) {
|
|
//error_log("public function distanceBetweenTwoGpsCoordinates($lat1,$lon1,$lat2,$lon2,$unit)");
|
|
if (($lat1 == $lat2) && ($lon1 == $lon2)) {
|
|
return 0;
|
|
}
|
|
$theta = $lon1 - $lon2;
|
|
$dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
|
|
$dist = acos($dist);
|
|
$dist = rad2deg($dist);
|
|
$miles = $dist * 60 * 1.1515;
|
|
$unit = strtoupper($unit);
|
|
if ($unit == "K") {
|
|
return ($miles * 1.609344);
|
|
}
|
|
if ($unit == "N") {
|
|
($miles * 0.8684);
|
|
}
|
|
return $miles;
|
|
}
|
|
|
|
|
|
private function getProviders($db, $gps_country_code, $country, $options, $member_id) {
|
|
error_log("GeocodeApi::getProviders(\$db, $gps_country_code, $country, \$options, $member_id");
|
|
$service_enabled = true;
|
|
$currency_code = 'SGD';
|
|
error_log('gps_country_code='.$gps_country_code);
|
|
error_log('country='.$country);
|
|
if ($gps_country_code=='UA') { // DEBUG
|
|
$service_enabled = true;
|
|
$gps_country_code = 'US';
|
|
$country = 'US';
|
|
}
|
|
// For not we support US & SG only!
|
|
if ($gps_country_code!='US' && $gps_country_code!='SG') {
|
|
// We will show Singapore, but disable service
|
|
$service_enabled = false;
|
|
$gps_country_code = 'SG';
|
|
$country = 'SG';
|
|
}
|
|
// TODO: change to select from "country" table
|
|
if ($gps_country_code=='US') {
|
|
$currency_code = 'USD';
|
|
} else {
|
|
$currency_code = 'SGD';
|
|
}
|
|
list ($result, $error) = Geocode::getTransportProvidersByCountry(
|
|
$db->getConnect(), $gps_country_code);
|
|
if ($result==NULL) {
|
|
error_log($error);
|
|
}
|
|
//error_log("gps_country_code=$gps_country_code, service_enabled=$service_enabled, currency_code=$currency_code");
|
|
return array($result, $gps_country_code, $service_enabled, $currency_code);
|
|
}
|
|
}
|