Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A Tropo script to find locations in Philadelphia.
<?php
/**
* PhindItForMe - an IM, SMS, Twitter and Phone application that finds locations in Philadelphia.
*
* @copyright 2011 Mark J. Headd (http://www.voiceingov.org)
* @author Mark Headd
*/
// Constants used to geocode an address.
define("MAPS_LOOKUP_BASE_URL", "http://maps.googleapis.com/maps/api/geocode/json");
define("MAPS_API_KEY", "");
// CouchDB Details.
define("COUCH_BASE_URL", "http://phlapi.com/");
define("COUCH_DESIGN_DOC_PATH", "_design/geojson/_spatial/points");
define("LOCATION_NUMBER_LIMIT", 3);
// Exception classes.
class GeoCodeException extends Exception {}
class CouchException extends Exception {}
// An array to hold locations returned from CouchDB instances.
$phl_locations = array();
// Get list of DB's in CouchDB instance.
function getDatabaseNames() {
$url = COUCH_BASE_URL."_all_dbs";
return makeCurlCall($url, CouchException, "Could not connect to PHLAPI instance. :-(");
}
// Get the lat / lon for an address.
function geoCodeAddress($address) {
// Format address and URL.
$address = str_replace(" ", "+", $address);
$url = MAPS_LOOKUP_BASE_URL."?address=".$address."&sensor=false&key=".MAPS_API_KEY;
return makeCurlCall($url, GeoCodeException, "Could not Geocode that address. Try again.");
}
// Mak call to Couch and get locations.
function getLocations($bb_array, $db_name) {
// Format URL to Couch instance.
$url = COUCH_BASE_URL."/$db_name/".COUCH_DESIGN_DOC_PATH."?bbox=";
$url .= implode(",", $bb_array);
return makeCurlCall($url, CouchException, "Could not connect to PHLAPI instance. :-(");
}
// Heler method to make an HTTP request.
function makeCurlCall($url, $exceptionType, $exceptionMessage) {
// Set up cURL call.
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// Execute.
$output = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
// Return results.
if($code != '200') {
throw new $exceptionType($exceptionMessage);
}
else {
return $output;
}
}
// Make a bounding box from a lat / lon pair
function makeBoundingBox($lon, $lat, $factor=.015) {
$bb_array = Array($lon-$factor, $lat-$factor, $lon+$factor, $lat+$factor);
return $bb_array;
}
// Calculate the distance between 2 points.
function calculateDistance($lat1, $lon1, $lat2, $lon2) {
return 3958*3.1415926*sqrt(($lat2-$lat1)*($lat2-$lat1) + cos($lat2/57.29578)*cos($lat1/57.29578)*($lon2-$lon1)*($lon2-$lon1))/180;
}
// Helper method to parse the properties of a specific location.
function makeLocation($location_type, $properties) {
$location = "";
switch($location_type) {
case "libraries":
$location = $properties->BRANCH_NAM . " " . $properties->ADDRESS;
break;
case "schools":
$location = $properties->LOCNAMESHO . " " . $properties->LOCTYPE . " " . $properties->LOCADDRESS;
break;
case "police":
$location = $properties->LOCATION . " " . $properties->PHONE__;
break;
case "fire":
$location = $properties->LOCATION;
break;
}
return $location;
}
try {
// Get the address submited by the user.
$result = ask("", array("choices" => "[ANY]"));
if(strstr($result->value, " #")) {
$message = explode(" #", $result->value);
$address = trim($message[0]);
$location_type = trim($message[1]);
// Get all of the CouchDB DB names that hold locational data.
$all_db_names = json_decode(getDatabaseNames(), true);
$db_names = array();
foreach($all_db_names as $key => $value) {
if(substr($value, 0, 1) == "_") {
continue;
}
array_push($db_names, $value);
}
// If the user sent in a valid DB name, get the locations.
if(in_array($location_type, $db_names)) {
// Geocode address and get lat / lon.
$geocoded_address = json_decode(geoCodeAddress($address));
$lat = $geocoded_address->results[0]->geometry->location->lat;
$lon = $geocoded_address->results[0]->geometry->location->lng;
// Create a bound box from the lat / lon pair.
$bb_array = makeBoundingBox($lon, $lat);
// Get the locations from geocouch.
$locations = json_decode(getLocations($bb_array, $location_type));
// If locations are found,iterate over JSON and render to user.
if($locations->rows) {
foreach($locations->rows as $location) {
$properties = $location->value->properties;
$geometry = $location->value->geometry->coordinates;
$place = "Location: " . makeLocation($location_type, $properties);
$distance = round(calculateDistance($lat, $lon, $geometry[1], $geometry[0]), 2);
$place .= " Distance: $distance miles.";
array_push($phl_locations, array("distance" => $distance, "place" => $place));
}
// Use the LOCATION_NUMBER_LIMIT constant to throttle numbr of messages sent to user.
$limit = LOCATION_NUMBER_LIMIT < count($phl_locations) ? LOCATION_NUMBER_LIMIT : count($phl_locations);
for($i = 0; $i<$limit; $i++) {
say($phl_locations[$i]["place"]);
}
}
// Otherwise, tell user no locations found.
else {
say("No locations found.");
}
}
// Otherwise, tell them to only send valid resource names.
else {
say("Send address followed by #".implode(", #", $db_names));
}
}
// No location type sent.
else {
say("You must sent a location type to use this service.");
}
}
// If address can not be Geocoded.
catch (GeoCodeException $ex) {
say("An error occured. ". $ex->getMessage());
hangup();
}
// If call can not be made to GeoCoud
catch (CouchException $ex) {
say("An error occured. ". $ex->getMessage());
hangup();
}
// Last ditch exception handler.
catch (Exception $ex) {
say("Sorry. Something really bad happened.");
_log("*** ". $ex->getMessage() . " ***");
hangup();
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment