. * * @category Action * @package StatusNet * @author Evan Prodromou * @copyright 2009 StatusNet Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ if (!defined('STATUSNET')) { exit(1); } /** * Plugin to convert string locations to Geonames IDs and vice versa * * This handles most of the events that Location class emits. It uses * the geonames.org Web service to convert names like 'Montreal, Quebec, Canada' * into IDs and lat/lon pairs. * * @category Plugin * @package StatusNet * @author Evan Prodromou * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ * * @seeAlso Location */ class GeonamesPlugin extends Plugin { const NAMESPACE = 1; /** * convert a name into a Location object * * @param string $name Name to convert * @param string $language ISO code for anguage the name is in * @param Location &$location Location object (may be null) * * @return boolean whether to continue (results in $location) */ function onLocationFromName($name, $language, &$location) { $client = HTTPClient::start(); // XXX: break down a name by commas, narrow by each $str = http_build_query(array('maxRows' => 1, 'q' => $name, 'lang' => $language, 'type' => 'json')); $result = $client->get('http://ws.geonames.org/search?'.$str); if ($result->code == "200") { $rj = json_decode($result->body); if (count($rj->geonames) > 0) { $n = $rj->geonames[0]; $location = new Location(); $location->lat = $n->lat; $location->lon = $n->lng; $location->names[$language] = $n->name; $location->location_id = $n->geonameId; $location->location_ns = self::NAMESPACE; // handled, don't continue processing! return false; } } // Continue processing; we don't have the answer return true; } /** * convert an id into a Location object * * @param string $id Name to convert * @param string $ns Name to convert * @param string $language ISO code for language for results * @param Location &$location Location object (may be null) * * @return boolean whether to continue (results in $location) */ function onLocationFromId($id, $ns, $language, &$location) { if ($ns != self::NAMESPACE) { // It's not one of our IDs... keep processing return true; } $client = HTTPClient::start(); $str = http_build_query(array('geonameId' => $id, 'lang' => $language)); $result = $client->get('http://ws.geonames.org/hierarchyJSON?'.$str); if ($result->code == "200") { $rj = json_decode($result->body); if (count($rj->geonames) > 0) { $parts = array(); foreach ($rj->geonames as $level) { if (in_array($level->fcode, array('PCLI', 'ADM1', 'PPL'))) { $parts[] = $level->name; } } $last = $rj->geonames[count($rj->geonames)-1]; if (!in_array($level->fcode, array('PCLI', 'ADM1', 'PPL'))) { $parts[] = $last->name; } $location = new Location(); $location->location_id = $last->geonameId; $location->location_ns = self::NAMESPACE; $location->lat = $last->lat; $location->lon = $last->lng; $location->names[$language] = implode(', ', array_reverse($parts)); } } // We're responsible for this NAMESPACE; nobody else // can resolve it return false; } /** * convert a lat/lon pair into a Location object * * Given a lat/lon, we try to find a Location that's around * it or nearby. We prefer populated places (cities, towns, villages). * * @param string $lat Latitude * @param string $lon Longitude * @param string $language ISO code for language for results * @param Location &$location Location object (may be null) * * @return boolean whether to continue (results in $location) */ function onLocationFromLatLon($lat, $lon, $language, &$location) { $client = HTTPClient::start(); $str = http_build_query(array('lat' => $lat, 'lng' => $lon, 'lang' => $language)); $result = $client->get('http://ws.geonames.org/findNearbyPlaceNameJSON?'.$str); if ($result->code == "200") { $rj = json_decode($result->body); if (count($rj->geonames) > 0) { $n = $rj->geonames[0]; $parts = array(); $location = new Location(); $parts[] = $n->name; if (!empty($n->adminName1)) { $parts[] = $n->adminName1; } if (!empty($n->countryName)) { $parts[] = $n->countryName; } $location->location_id = $n->geonameId; $location->location_ns = self::NAMESPACE; $location->lat = $lat; $location->lon = $lon; $location->names[$language] = implode(', ', $parts); // Success! We handled it, so no further processing return false; } } // For some reason we don't know, so pass. return true; } /** * Human-readable name for a location * * Given a location, we try to retrieve a human-readable name * in the target language. * * @param Location $location Location to get the name for * @param string $language ISO code for language to find name in * @param string &$name Place to put the name * * @return boolean whether to continue */ function onLocationNameLanguage($location, $language, &$name) { if ($location->location_ns != self::NAMESPACE) { // It's not one of our IDs... keep processing return true; } $client = HTTPClient::start(); $str = http_build_query(array('geonameId' => $id, 'lang' => $language)); $result = $client->get('http://ws.geonames.org/hierarchyJSON?'.$str); if ($result->code == "200") { $rj = json_decode($result->body); if (count($rj->geonames) > 0) { $parts = array(); foreach ($rj->geonames as $level) { if (in_array($level->fcode, array('PCLI', 'ADM1', 'PPL'))) { $parts[] = $level->name; } } $last = $rj->geonames[count($rj->geonames)-1]; if (!in_array($level->fcode, array('PCLI', 'ADM1', 'PPL'))) { $parts[] = $last->name; } if (count($parts)) { $name = implode(', ', array_reverse($parts)); return false; } } } return true; } /** * Human-readable name for a location * * Given a location, we try to retrieve a geonames.org URL. * * @param Location $location Location to get the url for * @param string &$url Place to put the url * * @return boolean whether to continue */ function onLocationUrl($location, &$url) { if ($location->location_ns != self::NAMESPACE) { // It's not one of our IDs... keep processing return true; } $url = 'http://www.geonames.org/' . $location->location_id; // it's been filled, so don't process further. return false; } }