Skip to content

Instantly share code, notes, and snippets.

@mheadd
Created April 30, 2011 18:38
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save mheadd/949860 to your computer and use it in GitHub Desktop.
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();
}
?>
@mheadd
Copy link
Author

mheadd commented Apr 30, 2011

Admittedly still rough, lots of refactoring opportunities here. Please feel free to fork and modify.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment