geofence_area_country $geofence_area_country = []; $q = "SELECT * FROM (SELECT *,ST_DistanceSphere(location::geometry,ST_SetSRID(ST_MakePoint("; $q.= $lng.",".$lat."),4326)::geometry) AS distance FROM geofence_area_country "; $q.= " WHERE ST_DWithin(location, ST_SetSRID(ST_MakePoint("; $q.= $lng.",".$lat."),4326)::geography,radius)) AS a ORDER BY a.distance ASC LIMIT 1"; $r = pg_query($db, $q); if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) { $geofence_area_country = $f; } else { // Fatal $err = pg_last_error($db); $err = $err ? $err : $q; error_log('Options::getTransportOptions => '.$err); throw new Exception("Failed to match input origin to a country"); } // geofence_area_country => country_servies $country_services = []; if (array_key_exists("id",$geofence_area_country) && $geofence_area_country["id"]>0) { $q = "SELECT * FROM country_services WHERE country_id=".$geofence_area_country["id"]." AND status=1"; $r = pg_query($db, $q); if ($r && pg_num_rows($r)) { while ($f=pg_fetch_assoc($r)) { $country_services[] = $f; } } else { // Non-fatal, try to get city services next $err = pg_last_error($db); $err = $err ? $err : $q; error_log('Options::getTransportOptions => '.$err); } } // $lat,$lng => geofence_area_city $geofence_area_city = []; $q = "SELECT * FROM (SELECT *,ST_DistanceSphere(location::geometry,ST_SetSRID(ST_MakePoint("; $q.= $lng.",".$lat."),4326)::geometry) AS distance FROM geofence_area_city "; $q.= " WHERE ST_DWithin(location, ST_SetSRID(ST_MakePoint("; $q.= $lng.",".$lat."),4326)::geography,radius)) AS a ORDER BY a.distance ASC LIMIT 1"; $r = pg_query($db, $q); if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) { $geofence_area_city = $f; } else { // Non-fatal, at least we know the country $err = pg_last_error($db); $err = $err ? $err : $q; error_log('Options::getTransportOptions => '.$err); } // geofence_area_city => city_services $city_services = []; if (array_key_exists("id",$geofence_area_city) && $geofence_area_city["id"]>0) { $q = "SELECT * FROM city_services WHERE city_id=".$geofence_area_city["id"]." AND status=1"; $r = pg_query($db, $q); if ($r && pg_num_rows($r)) { while ($f=pg_fetch_assoc($r)) { $city_services[] = $f; } } else { // Non-fatal, if we have country services (let upstream decide what to do) $err = pg_last_error($db); $err = $err ? $err : $q; error_log('Options::getTransportOptions => '.$err); } } if (is_array($country_services) && count($country_services)>0 && (!is_array($city_services) || count($city_services)<1)) { $services = $country_services; } else if (is_array($city_services) && count($city_services)>0 && (!is_array($country_services) || count($country_services)<1)) { $services = $city_services; } else { $services = $city_services; foreach ($country_services as $country_service) { $exists = false; foreach($services as $service) { if ($service["transport_provider_id"]>0 && $service["transport_provider_id"]==$country_service["transport_provider_id"]) { // The service is available on city level - skip $exists = true; break; } } if (!$exists) { // Add country service $services[] = $country_service; } } } return [ $services, [ 'geofence_area_country' => $geofence_area_country, 'country_services' => $country_services, 'geofence_area_city' => $geofence_area_city, 'city_services' => $city_services ] ]; } public static function processGooglePlace($db, $data) { // data.address // data.place // data.lat // data.lng // data.utc_offset_minutes // data.postal // data.vicinity // data.city // data.country $db_address = pg_escape_string(html_entity_decode($data["address"],ENT_QUOTES|ENT_HTML5,"UTF-8")); $db_lat = floatval($data["lat"]); $db_lng = floatval($data["lng"]); $db_city = pg_escape_string($data["city"]); $db_postal = pg_escape_string($data["postal"]); $db_country = pg_escape_string($data["country"]); // Step 1: Check address $q = "SELECT a.*,b.timezone AS \"timeZoneId\" FROM address a LEFT JOIN address_timezone b ON (b.id=a.timezone) "; $q .= " WHERE LOWER(a.address)=LOWER('${db_address}')"; $q .= "AND latitude<>0 AND longitude<>0 AND geocoding_date IS NOT NULL AND postal IS NOT NULL "; $q .= "ORDER BY geocoding_date DESC LIMIT 1"; $r = pg_query($db, $q); if ($r && pg_num_rows($r) && $f = pg_fetch_assoc($r)) { $f["address"] = html_entity_decode ($f["address"],ENT_QUOTES|ENT_HTML5,"UTF-8"); foreach ($f as $key=>$val) { $data[$key] = $val; } return $data; } // Step 2: Get timezone date_default_timezone_set('UTC'); $seconds = $data["utc_offset_minutes"] * 60; $timezone = timezone_name_from_abbr('', $seconds, 1); $q = "SELECT * FROM address_timezone WHERE LOWER(timezone)=LOWER('${timezone}')"; $r = pg_query($db, $q); if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) { $data["timezone"] = $f["id"]; $data["timeZoneId"] = $f["timezone"]; } else { $q = "INSERT INTO address_timezone (timezone) VALUES('".pg_escape_string($timezone)."') RETURNING *"; $r = pg_query($db, $q); if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) { $data["timezone"] = $f["id"]; $data["timeZoneId"] = $f["timezone"]; } else { // Most likely this is very wrong... $data["timezone"] = 1; // Singapore $data["timeZoneId"] = 'Asia/Singapore'; } } // Step 3: Get city $q = "SELECT id FROM geofence_area_city WHERE LOWER(city) ILIKE '" . strtolower($db_city) . "' AND country='${db_country}' LIMIT 1"; $r = pg_query($db, $q); if ($r && pg_num_rows($r) && $f = pg_fetch_row($r)) { $data["city_id"] = $f[0]; } else { $q = "INSERT INTO geofence_area_city (city,country,latitude,longitude,location) VALUES ("; $q.= "'${db_city}','${db_country}',${db_lat},${db_lng},ST_SetSRID(ST_MakePoint(${db_lng},${db_lat}), 4326)) RETURNING id"; $r = pg_query($db, $q); if ($r && pg_num_rows($r) && $f = pg_fetch_row($r)) { $data["city_id"] = $f[0]; } else { // Most likely this is very wrong... $data["city_id"] = 1; // Singapore } } // Step 4: Get closest postal if missing... $db_tz = $data["timezone"]; if (empty($db_postal)) { $distance = 50000; // 50 km $q = "SELECT postal, ST_DistanceSphere(geometry::geometry, ST_SetSRID(ST_MakePoint(${db_lng},${db_lat}),4326)) AS distance FROM geoname_postal_code WHERE ST_DistanceSphere(geometry::geometry, ST_SetSRID(ST_MakePoint(${db_lng},${db_lat}),4326)) BETWEEN 0 AND ${distance} AND country='${db_country}' ORDER BY distance LIMIT 1;"; $r = pg_query($db, $q); if ($r && pg_num_rows($r) && $f = pg_fetch_assoc($r)) { $db_postal = $f["postal"]; $data["postal"] = $f["postal"]; } } // Step 5: Save address $q = "INSERT INTO address (address,latitude,longitude,timezone,geocoding_date,postal,country,geometry, city_id) VALUES ("; $q.= "'${db_address}',${db_lat},${db_lng}," . ($db_tz == null ? "NULL" : $db_tz); $q.= ",now(),'${db_postal}','${db_country}',ST_SetSRID(ST_MakePoint(${db_lng},${db_lat}), 4326),'".$data["city_id"]."')"; $q.= " RETURNING id, address, latitude as lat, longitude as lng, postal, country"; $r = pg_query($db, $q); if ($r && pg_num_rows($r) && $f = pg_fetch_assoc($r)) { $data["id"] = $f["id"]; $data["address"] = $f["address"]; $data["lat"] = $f["lat"]; $data["lng"] = $f["lng"]; $data["geometry"] = $f["geometry"]; $data["postal"] = $f["postal"]; } return $data; } public static function checkValidLocations($db, $origin, $destination) { // Check for Google places result if (is_array($origin) && array_key_exists('place',$origin) && is_array($origin['place'])) { $origin = Options::processGooglePlace($db, $origin); } // Basic input sanity check: origin if (!is_array($origin) || !array_key_exists('address',$origin) || !array_key_exists('lat',$origin) || !array_key_exists('lng',$origin) || !array_key_exists('id',$origin) || $origin['id']<1) { throw new Exception('Invalid input origin'); } // Check for Google places result if (is_array($destination) && array_key_exists('place',$destination) && is_array($destination['place'])) { $destination = Options::processGooglePlace($db, $destination); } // Basic input sanity check: destination if (!is_array($destination) || !array_key_exists('address',$destination) || !array_key_exists('lat',$destination) || !array_key_exists('lng',$destination) || !array_key_exists('id',$destination) || $destination['id']<1) { throw new Exception('Invalid input destination'); } // Basic input sanity check: GPS coordinates if ($origin['lat']==null || $origin['lng']==null || $destination['lat']==null || $destination['lng']==null || ($origin['lat']==0 && $origin['lng']==0) || ($destination['lat']==0 && $destination['lng']==0)) { throw new Exception('Invalid origin and/or destination coordinates'); } return [$origin, $destination]; } public static function checkValidOption($db, $option) { // Basic input sanity check: option if (!is_array($option) || !array_key_exists('name',$option) || !array_key_exists('transport_provider_id',$option) || $option['transport_provider_id']<1 || !array_key_exists('id',$option) || $option['id']<1) { throw new Exception('Invalid transport option'); } } public static function checkValidTrip($db, $trip) { // TODO! } public static function createTrip($db, $country, $duration, $distance, $location_start_id, $location_end_id) { error_log("Options::createTrip(\$db, $country, $duration, $distance, $location_start_id, $location_end_id)"); $data = [ 'travel_date' => Options::getDate($country), 'duration' => ((int)($duration/60)), 'cost_raw' => NULL, 'trackedemail_item_id' => NULL, 'cost' => NULL, 'distance' => sprintf("%0.02f",$distance/1000.0), 'transport_provider_id' => NULL, 'scheduled' => NULL, 'travel_date_end' => Options::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 */ 'booking' => 't' /* this is a booking entry */ ]; list($id, $err) = Options::saveTrip($db->getConnect(), $data); if ($id && $id>0) { $trip = Options::getTripById($db->getConnect(), $id); return $trip; } return NULL; // WTF? } public static function getTripById($db, $id) { $result = array(); $q = "SELECT a.*, b.address AS location_start, b.latitude AS location_start_lat, b.longitude AS location_start_lng, c.address AS location_end, c.latitude AS location_end_lat, c.longitude AS location_end_lng, a.id AS parsedemail_item_id FROM parsedemail_item a LEFT JOIN address b ON (b.id=a.location_start_id) LEFT JOIN address c ON (c.id=a.location_end_id) WHERE a.id=${id}"; $r = pg_query($db, $q); if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) { $f["location_start"] = html_entity_decode ($f["location_start"],ENT_QUOTES|ENT_HTML5,"UTF-8"); $f["location_end"] = html_entity_decode ($f["location_end"],ENT_QUOTES|ENT_HTML5,"UTF-8"); $result = $f; } return $result; } public static function saveTrip($db, $data) { $fields = array( 'travel_date' => false, 'duration' => false, 'cost_raw' => false, 'trackedemail_item_id' => false, 'cost' => false, 'updated' => false, 'distance' => false, 'transport_provider_id' => false, 'scheduled' => false, 'travel_date_end' => false, 'dup_id' => false, 'location_start_id' => true, 'location_end_id' => true, 'private' => false, 'booking' => false ); $field_key_list = []; $field_val_list = []; foreach ($fields as $key=>$required) { if ($required && !isset($data[$key])) { return [NULL, "Missing required field '${key}'"]; } if (!isset($data[$key])) continue; $field_key_list[] = "${key}"; $field_val_list[] = (($data[$key]==NULL || $data[$key]=="")?"NULL":"'".pg_escape_string($data[$key])."'"); } $q = "INSERT INTO parsedemail_item (".implode(",",$field_key_list).") VALUES(".implode(",",$field_val_list).") RETURNING id"; $r = pg_query($db, $q); if ($r && pg_num_rows($r)) { $f = pg_fetch_row($r); return [$f[0], NULL]; } return [NULL, pg_last_error($db)]; } public static function validateTrip($db, $trip) { if (!is_array($trip) || !array_key_exists("id",$trip) || $trip["id"]<1) { return false; } if (!array_key_exists("options",$trip) || !is_array($trip["options"]) || !array_key_exists("legs",$trip["options"]) || !is_array($trip["options"]["legs"]) || count($trip["options"]["legs"])<1 || !array_key_exists("id",$trip["options"]["legs"][0]) || $trip["options"]["legs"][0]["id"]<1) { return false; } return true; } public static function saveTripAdvice($db, $trip) { error_log('Options::saveTripAdvice($db, $trip)'); if (!is_array($trip) || !array_key_exists("id",$trip) || $trip["id"]<1) { return NULL; } $q = "INSERT INTO parsedemail_item_advice_google (parsedemail_item_id,routes,source) VALUES (".$trip["id"].",1,'".$trip["source"]."') RETURNING id"; $r = pg_query($db, $q); if ($r && pg_num_rows($r) && $f=pg_fetch_row($r)) { return $f[0]; } return NULL; } public static function saveTripAdviceLeg($db, $trip, $leg) { error_log('Options::saveTripAdviceLeg($db, $trip, $leg)'); $fare_raw = "NULL"; if (array_key_exists("fare_raw",$leg) && $leg["fare_raw"]>0) { $fare_raw = (int)$leg["fare_raw"]; } $q = "INSERT INTO google_directions_legs (parsedemail_item_advice_google_id,arrival_time,arrival_time_zone,"; $q.= "departure_time,departure_time_zone,distance,duration,steps,fare_raw,polyline) VALUES("; $q.= $leg["parsedemail_item_advice_google_id"].",'".pg_escape_string($leg["arrival_time"])."',".((int)$leg["arrival_time_zone"]); $q.= ",'".pg_escape_string($leg["departure_time"])."',".((int)$leg["departure_time_zone"]).","; $q.= ((int)$leg["distance"]).",".((int)$leg["duration"]).",1,${fare_raw},'".pg_escape_string($leg["polyline"])."') RETURNING id"; $r = pg_query($db, $q); error_log($q); if ($r && pg_num_rows($r) && $f=pg_fetch_row($r)) { return $f[0]; } return NULL; } public static function saveTripAdviceLegStep($db, $trip, $leg, $step) { error_log('Options::saveTripAdviceLegStep($db, $trip, $leg, $step)'); $q = "INSERT INTO google_directions_leg_steps (google_directions_leg_id,distance,duration,travel_mode,"; $q.= "location_start_lat,location_start_lng,location_end_lat,location_end_lng,html_instructions,polyline) VALUES ("; $q.= $leg["id"].",".((int)$step["distance"]).",".((int)$step["duration"]).","; $q.= "'".pg_escape_string($step["travel_mode"])."',".$step["location_start_lat"].",".$step["location_start_lng"].","; $q.= $step["location_end_lat"].",".$step["location_end_lng"].",'".pg_escape_string($step["html_instructions"])."',"; $q.= "'".pg_escape_string($step["polyline"])."') RETURNING id"; $r = pg_query($db, $q); error_log($q); if ($r && pg_num_rows($r) && $f=pg_fetch_row($r)) { return $f[0]; } return NULL; } public static function saveTripAdviceLegStepQuote($db, $trip, $leg, $step) { $fare_raw = NULL; if (array_key_exists("fare_raw",$step) && $step["fare_raw"]>0) { $fare_raw = (int)$step["fare_raw"]; } if ($fare_raw>0) { $q = "INSERT INTO leg_step_quote (google_directions_leg_step_id,name,service,board,alight,"; $q.= "distance,fare,fare_raw,distance_raw) VALUES (".$step["id"].","; $q.= "'".pg_escape_string($trip["source"])."','".pg_escape_string($trip["source"])."',"; $q.= "'".pg_escape_string($trip["location_start"])."','".pg_escape_string($trip["location_end"])."',"; $q.= "'".sprintf("%0.02f",$step["distance"]/1000.0)."','".sprintf("%0.02f",$fare_raw/100.0)."',"; $q.= ((int)$fare_raw).",".((int)$step["distance"]).") RETURNING id"; $r = pg_query($db, $q); if ($r && pg_num_rows($r) && $f=pg_fetch_row($r)) { return $f[0]; } } return NULL; } public static function getDate($country) { $tz = 'Asia/Singapore'; if ($country=='US') { $tz = 'America/Los_Angeles'; } $timestamp = time(); $dt = new DateTime("now", new DateTimeZone($tz)); //first argument "must" be a string $dt->setTimestamp($timestamp); //adjust the object to correct timestamp return $dt->format('Y-m-d, H:i:s'); } public static function getTimezoneById($db, $id, $def='Asia/Singapore') { $q = "SELECT timezone FROM address_timezone WHERE id=".((int)$id); $r = pg_query($db, $q); if ($r && pg_num_rows($r) && $f=pg_fetch_assoc($r)) { return $f['timezone']; } return $def; //$id; } } // vi:ts=2