From b6b245dc0be75a3358fee560a40a54e280e4f2a7 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Tue, 15 Sep 2009 20:27:11 -0400 Subject: start of a location class --- lib/location.php | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 lib/location.php (limited to 'lib/location.php') diff --git a/lib/location.php b/lib/location.php new file mode 100644 index 000000000..cbd03f33a --- /dev/null +++ b/lib/location.php @@ -0,0 +1,77 @@ +. + * + * @category Location + * @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') && !defined('LACONICA')) { + exit(1); +} + +/** + * class for locations + * + * @category Location + * @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/ + */ + +class Location +{ + public $lat; + public $lon; + public $location_id; + public $location_ns; + + var $names; + + static function fromName($name, $language=null) + { + if (is_null($language)) { + $language = common_language(); + } + } + + static function fromId($location_id, $location_ns = null) + { + if (is_null($location_ns)) { + $location_ns = common_config('location', 'namespace'); + } + } + + function getName($language=null) + { + if (is_null($language)) { + $language = common_language(); + } + + if (array_key_exists($this->names, $language)) { + return $this->names[$language]; + } + } +} -- cgit v1.2.3-54-g00ecf From 7b227fd1c06ff6f3ba078b73348dd02419d44e38 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 16 Sep 2009 11:46:10 -0400 Subject: start getting locations from remote services --- lib/default.php | 2 ++ lib/location.php | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) (limited to 'lib/location.php') diff --git a/lib/default.php b/lib/default.php index 30e43eefb..c896d5408 100644 --- a/lib/default.php +++ b/lib/default.php @@ -229,4 +229,6 @@ $default = array('contentlimit' => null), 'http' => array('client' => 'curl'), // XXX: should this be the default? + 'location' => + array('namespace' => 1), // 1 = geonames, 2 = Yahoo Where on Earth ); diff --git a/lib/location.php b/lib/location.php index cbd03f33a..f4ce7f67a 100644 --- a/lib/location.php +++ b/lib/location.php @@ -50,11 +50,63 @@ class Location var $names; - static function fromName($name, $language=null) + const geonames = 1; + const whereOnEarth = 2; + + static function fromName($name, $language=null, $location_ns=null) { if (is_null($language)) { $language = common_language(); } + if (is_null($location_ns)) { + $location_ns = common_config('location', 'namespace'); + } + + $location = null; + + if (Event::handle('LocationFromName', array($name, $language, $location_ns, &$location))) { + + switch ($location_ns) { + case Location::geonames: + return Location::fromGeonamesName($name, $language); + break; + case Location::whereOnEarth: + return Location::fromWhereOnEarthName($name, $language); + break; + } + } + + return $location; + } + + static function fromGeonamesName($name, $language) + { + $location = null; + $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->lon; + $location->name = $n->name; + $location->location_id = $n->geonameId; + $location->location_ns = Location:geonames; + } + } + + return $location; } static function fromId($location_id, $location_ns = null) -- cgit v1.2.3-54-g00ecf From 9c983c383029c9aaa7d0cf04a5fc9973d514dff7 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 22 Oct 2009 15:44:36 -0400 Subject: extract Geonames stuff to a plugin --- lib/location.php | 119 ++++++++++++------- plugins/GeonamesPlugin.php | 281 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 356 insertions(+), 44 deletions(-) create mode 100644 plugins/GeonamesPlugin.php (limited to 'lib/location.php') diff --git a/lib/location.php b/lib/location.php index f4ce7f67a..5b7f47102 100644 --- a/lib/location.php +++ b/lib/location.php @@ -34,6 +34,10 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { /** * class for locations * + * These are stored in the DB as part of notice and profile records, + * but since they're about the same in both, we have a separate class + * for them. + * * @category Location * @package StatusNet * @author Evan Prodromou @@ -48,74 +52,94 @@ class Location public $location_id; public $location_ns; - var $names; + var $names = array(); - const geonames = 1; - const whereOnEarth = 2; + /** + * Constructor that makes a Location from a string name + * + * @param string $name Human-readable name (any kind) + * @param string $language Language, default = common_language() + * + * @return Location Location with that name (or null if not found) + */ - static function fromName($name, $language=null, $location_ns=null) + static function fromName($name, $language=null) { if (is_null($language)) { $language = common_language(); } - if (is_null($location_ns)) { - $location_ns = common_config('location', 'namespace'); - } $location = null; - if (Event::handle('LocationFromName', array($name, $language, $location_ns, &$location))) { + // Let a third-party handle it - switch ($location_ns) { - case Location::geonames: - return Location::fromGeonamesName($name, $language); - break; - case Location::whereOnEarth: - return Location::fromWhereOnEarthName($name, $language); - break; - } - } + Event::handle('LocationFromName', array($name, $language, &$location)); return $location; } - static function fromGeonamesName($name, $language) + /** + * Constructor that makes a Location from an ID + * + * @param integer $id Identifier ID + * @param integer $ns Namespace of the identifier + * @param string $language Language to return name in (default is common) + * + * @return Location The location with this ID (or null if none) + */ + + static function fromId($id, $ns, $language=null) { $location = null; - $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->lon; - $location->name = $n->name; - $location->location_id = $n->geonameId; - $location->location_ns = Location:geonames; - } - } + + // Let a third-party handle it + + Event::handle('LocationFromId', array($id, $ns, $language, &$location)); return $location; } - static function fromId($location_id, $location_ns = null) + /** + * Constructor that finds the nearest location to a lat/lon pair + * + * @param float $lat Latitude + * @param float $lon Longitude + * @param string $language Language for results, default = current + * + * @return Location the location found, or null if none found + */ + + static function fromLatLon($lat, $lon, $language=null) { - if (is_null($location_ns)) { - $location_ns = common_config('location', 'namespace'); + if (is_null($language)) { + $language = common_language(); } + + $location = null; + + // Let a third-party handle it + + if (Event::handle('LocationFromLatLon', + array($lat, $lon, $language, &$location))) { + // Default is just the lat/lon pair + + $location = new Location(); + + $location->lat = $lat; + $location->lon = $lon; + } + + return $location; } + /** + * Get the name for this location in the given language + * + * @param string $language language to use, default = current + * + * @return string location name or null if not found + */ + function getName($language=null) { if (is_null($language)) { @@ -124,6 +148,13 @@ class Location if (array_key_exists($this->names, $language)) { return $this->names[$language]; + } else { + $name = null; + Event::handle('LocationNameLanguage', array($this, $language, &$name)); + if (!empty($name)) { + $this->names[$language] = $name; + return $name; + } } } } diff --git a/plugins/GeonamesPlugin.php b/plugins/GeonamesPlugin.php new file mode 100644 index 000000000..934e998c7 --- /dev/null +++ b/plugins/GeonamesPlugin.php @@ -0,0 +1,281 @@ +. + * + * @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; + } +} -- cgit v1.2.3-54-g00ecf From 69357c49167679043d36a1ae2df4bf2d117cbcd0 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 22 Oct 2009 16:19:56 -0400 Subject: error in order of arguments to array_key_exists in location.php --- lib/location.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/location.php') diff --git a/lib/location.php b/lib/location.php index 5b7f47102..048554f0f 100644 --- a/lib/location.php +++ b/lib/location.php @@ -146,7 +146,7 @@ class Location $language = common_language(); } - if (array_key_exists($this->names, $language)) { + if (array_key_exists($language, $this->names)) { return $this->names[$language]; } else { $name = null; -- cgit v1.2.3-54-g00ecf From a6ed4e5bf7e7c15bc649c91451bbe7b6aa1f0735 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 29 Oct 2009 14:49:00 -0400 Subject: a location method for getting an URL --- lib/location.php | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) (limited to 'lib/location.php') diff --git a/lib/location.php b/lib/location.php index 048554f0f..c9411b55d 100644 --- a/lib/location.php +++ b/lib/location.php @@ -47,10 +47,11 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { class Location { - public $lat; - public $lon; - public $location_id; - public $location_ns; + public $lat; + public $lon; + public $location_id; + public $location_ns; + private $_url; var $names = array(); @@ -157,4 +158,33 @@ class Location } } } + + /** + * Get an URL suitable for this location + * + * @return string URL for this location or NULL + */ + + function getURL() + { + if ($this->_url == false) { // cached failure + return null; + } else if (is_string($this->_url)) { // cached value + return $this->_url; + } + + $url = null; + + Event::handle('LocationUrl', array($this, &$url)); + + // Save it for later + + if (is_null($url)) { + $this->_url = false; + } else { + $this->_url = $url; + } + + return $this->_url; + } } -- cgit v1.2.3-54-g00ecf From 5b0809f4a3619e8123b486f7e910913dc4e10086 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Thu, 29 Oct 2009 16:15:49 -0400 Subject: fix caching in location.php --- lib/location.php | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) (limited to 'lib/location.php') diff --git a/lib/location.php b/lib/location.php index c9411b55d..bbfc15a36 100644 --- a/lib/location.php +++ b/lib/location.php @@ -91,6 +91,10 @@ class Location static function fromId($id, $ns, $language=null) { + if (is_null($language)) { + $language = common_language(); + } + $location = null; // Let a third-party handle it @@ -167,9 +171,9 @@ class Location function getURL() { - if ($this->_url == false) { // cached failure - return null; - } else if (is_string($this->_url)) { // cached value + // Keep one cached + + if (is_string($this->_url)) { return $this->_url; } @@ -177,14 +181,8 @@ class Location Event::handle('LocationUrl', array($this, &$url)); - // Save it for later - - if (is_null($url)) { - $this->_url = false; - } else { - $this->_url = $url; - } + $this->_url = $url; - return $this->_url; + return $url; } } -- cgit v1.2.3-54-g00ecf From 4463768baed036b487d473a60b30f0c314ee1673 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Thu, 19 Nov 2009 12:00:25 -0500 Subject: tobyink's location RDF patch --- actions/foaf.php | 26 ++++++++++++++++++++++---- lib/location.php | 24 ++++++++++++++++++++++++ lib/rssaction.php | 12 ++++++++++++ plugins/GeonamesPlugin.php | 24 ++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 4 deletions(-) (limited to 'lib/location.php') diff --git a/actions/foaf.php b/actions/foaf.php index 356393304..e9f67b7f2 100644 --- a/actions/foaf.php +++ b/actions/foaf.php @@ -108,11 +108,29 @@ class FoafAction extends Action if ($this->profile->bio) { $this->element('bio:olb', null, $this->profile->bio); } - // XXX: more structured location data - if ($this->profile->location) { + + $location = $this->profile->getLocation(); + if ($location) { + $attr = array(); + if ($location->getRdfURL()) { + $attr['rdf:about'] = $location->getRdfURL(); + } + $location_name = $location->getName(); + $this->elementStart('based_near'); - $this->elementStart('geo:SpatialThing'); - $this->element('name', null, $this->profile->location); + $this->elementStart('geo:SpatialThing', $attr); + if ($location_name) { + $this->element('name', null, $location_name); + } + if ($location->lat) { + $this->element('geo:lat', null, $location->lat); + } + if ($location->lon) { + $this->element('geo:long', null, $location->lat); + } + if ($location->getURL()) { + $this->element('page', array('rdf:resource'=>$location->getURL())); + } $this->elementEnd('geo:SpatialThing'); $this->elementEnd('based_near'); } diff --git a/lib/location.php b/lib/location.php index bbfc15a36..191550d6d 100644 --- a/lib/location.php +++ b/lib/location.php @@ -52,6 +52,7 @@ class Location public $location_id; public $location_ns; private $_url; + private $_rdfurl; var $names = array(); @@ -185,4 +186,27 @@ class Location return $url; } + + /** + * Get an URL for this location, suitable for embedding in RDF + * + * @return string URL for this location or NULL + */ + + function getRdfURL() + { + // Keep one cached + + if (is_string($this->_rdfurl)) { + return $this->_rdfurl; + } + + $url = null; + + Event::handle('LocationRdfUrl', array($this, &$url)); + + $this->_rdfurl = $url; + + return $url; + } } diff --git a/lib/rssaction.php b/lib/rssaction.php index faf6bec7d..cd4c8b51c 100644 --- a/lib/rssaction.php +++ b/lib/rssaction.php @@ -244,6 +244,16 @@ class Rss10Action extends Action $this->element('dc:creator', null, ($profile->fullname) ? $profile->fullname : $profile->nickname); $this->element('foaf:maker', array('rdf:resource' => $creator_uri)); $this->element('sioc:has_creator', array('rdf:resource' => $creator_uri.'#acct')); + $location = $notice->getLocation(); + if ($location && isset($location->lat) && isset($location->lon)) { + $location_uri = $location->getRdfURL(); + $attrs = array('geo:lat' => $location->lat, + 'geo:long' => $location->lon); + if (strlen($location_uri)) { + $attrs['rdf:resource'] = $location_uri; + } + $this->element('statusnet:origin', $attrs); + } $this->element('statusnet:postIcon', array('rdf:resource' => $profile->avatarUrl())); $this->element('cc:licence', array('rdf:resource' => common_config('license', 'url'))); if ($notice->reply_to) { @@ -354,6 +364,8 @@ class Rss10Action extends Action 'http://rdfs.org/sioc/types#', 'xmlns:rdfs' => 'http://www.w3.org/2000/01/rdf-schema#', + 'xmlns:geo' => + 'http://www.w3.org/2003/01/geo/wgs84_pos#', 'xmlns:statusnet' => 'http://status.net/ont/', 'xmlns' => 'http://purl.org/rss/1.0/')); diff --git a/plugins/GeonamesPlugin.php b/plugins/GeonamesPlugin.php index 1d7381a80..59232c1c5 100644 --- a/plugins/GeonamesPlugin.php +++ b/plugins/GeonamesPlugin.php @@ -302,4 +302,28 @@ class GeonamesPlugin extends Plugin // it's been filled, so don't process further. return false; } + + /** + * Machine-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 onLocationRdfUrl($location, &$url) + { + if ($location->location_ns != self::LOCATION_NS) { + // It's not one of our IDs... keep processing + return true; + } + + $url = 'http://sw.geonames.org/' . $location->location_id . '/'; + + // it's been filled, so don't process further. + return false; + } } -- cgit v1.2.3-54-g00ecf