summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--actions/profilesettings.php10
-rw-r--r--classes/Location_namespace.php46
-rw-r--r--classes/Notice.php31
-rw-r--r--classes/Profile.php8
-rw-r--r--classes/User.php9
-rw-r--r--classes/statusnet.ini18
-rw-r--r--db/location_namespace.sql5
-rw-r--r--db/statusnet.sql19
-rw-r--r--lib/default.php2
-rw-r--r--lib/location.php160
-rw-r--r--plugins/GeonamesPlugin.php281
-rw-r--r--tests/LocationTest.php87
12 files changed, 673 insertions, 3 deletions
diff --git a/actions/profilesettings.php b/actions/profilesettings.php
index 5445d9bb2..0a0cc5997 100644
--- a/actions/profilesettings.php
+++ b/actions/profilesettings.php
@@ -306,6 +306,16 @@ class ProfilesettingsAction extends AccountSettingsAction
$profile->homepage = $homepage;
$profile->bio = $bio;
$profile->location = $location;
+
+ $loc = Location::fromName($location);
+
+ if (!empty($loc)) {
+ $profile->lat = $loc->lat;
+ $profile->lon = $loc->lon;
+ $profile->location_id = $loc->location_id;
+ $profile->location_ns = $loc->location_ns;
+ }
+
$profile->profileurl = common_profile_url($nickname);
common_debug('Old profile: ' . common_log_objstring($orig_profile), __FILE__);
diff --git a/classes/Location_namespace.php b/classes/Location_namespace.php
new file mode 100644
index 000000000..5ab95d9ef
--- /dev/null
+++ b/classes/Location_namespace.php
@@ -0,0 +1,46 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
+
+/**
+ * Table Definition for location_namespace
+ */
+
+require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
+
+class Location_namespace extends Memcached_DataObject
+{
+ ###START_AUTOCODE
+ /* the code below is auto generated do not remove the above tag */
+
+ public $__table = 'location_namespace'; // table name
+ public $id; // int(4) primary_key not_null
+ public $description; // varchar(255)
+ public $created; // datetime() not_null
+ public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
+
+ /* Static get */
+ function staticGet($k,$v=NULL) {
+ return Memcached_DataObject::staticGet('Location_namespace',$k,$v);
+ }
+
+ /* the code above is auto generated do not remove the tag below */
+ ###END_AUTOCODE
+}
diff --git a/classes/Notice.php b/classes/Notice.php
index cd3906ca1..7478f945d 100644
--- a/classes/Notice.php
+++ b/classes/Notice.php
@@ -66,9 +66,15 @@ class Notice extends Memcached_DataObject
public $is_local; // tinyint(1)
public $source; // varchar(32)
public $conversation; // int(4)
+ public $lat; // decimal(10,7)
+ public $lon; // decimal(10,7)
+ public $location_id; // int(4)
+ public $location_ns; // int(4)
/* Static get */
- function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Notice',$k,$v); }
+ function staticGet($k,$v=NULL) {
+ return Memcached_DataObject::staticGet('Notice',$k,$v);
+ }
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
@@ -162,7 +168,8 @@ class Notice extends Memcached_DataObject
}
static function saveNew($profile_id, $content, $source=null,
- $is_local=Notice::LOCAL_PUBLIC, $reply_to=null, $uri=null, $created=null) {
+ $is_local=Notice::LOCAL_PUBLIC, $reply_to=null, $uri=null, $created=null,
+ $lat=null, $lon=null, $location_id=null, $location_ns=null) {
$profile = Profile::staticGet($profile_id);
@@ -228,6 +235,26 @@ class Notice extends Memcached_DataObject
$notice->conversation = $reply->conversation;
}
+ if (!empty($lat) && !empty($lon)) {
+ $notice->lat = $lat;
+ $notice->lon = $lon;
+ $notice->location_id = $location_id;
+ $notice->location_ns = $location_ns;
+ } else if (!empty($location_ns) && !empty($location_id)) {
+ $location = Location::fromId($location_id, $location_ns);
+ if (!empty($location)) {
+ $notice->lat = $location->lat;
+ $notice->lon = $location->lon;
+ $notice->location_id = $location_id;
+ $notice->location_ns = $location_ns;
+ }
+ } else {
+ $notice->lat = $profile->lat;
+ $notice->lon = $profile->lon;
+ $notice->location_id = $profile->location_id;
+ $notice->location_ns = $profile->location_ns;
+ }
+
if (Event::handle('StartNoticeSave', array(&$notice))) {
// XXX: some of these functions write to the DB
diff --git a/classes/Profile.php b/classes/Profile.php
index a78a27f4a..53d07fb2f 100644
--- a/classes/Profile.php
+++ b/classes/Profile.php
@@ -37,11 +37,17 @@ class Profile extends Memcached_DataObject
public $homepage; // varchar(255) multiple_key
public $bio; // text() multiple_key
public $location; // varchar(255) multiple_key
+ public $lat; // decimal(10,7)
+ public $lon; // decimal(10,7)
+ public $location_id; // int(4)
+ public $location_ns; // int(4)
public $created; // datetime() not_null
public $modified; // timestamp() not_null default_CURRENT_TIMESTAMP
/* Static get */
- function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('Profile',$k,$v); }
+ function staticGet($k,$v=NULL) {
+ return Memcached_DataObject::staticGet('Profile',$k,$v);
+ }
/* the code above is auto generated do not remove the tag below */
###END_AUTOCODE
diff --git a/classes/User.php b/classes/User.php
index 0a70c9801..b300c9edb 100644
--- a/classes/User.php
+++ b/classes/User.php
@@ -198,6 +198,15 @@ class User extends Memcached_DataObject
}
if (!empty($location)) {
$profile->location = $location;
+
+ $loc = Location::fromName($location);
+
+ if (!empty($loc)) {
+ $profile->lat = $loc->lat;
+ $profile->lon = $loc->lon;
+ $profile->location_id = $loc->location_id;
+ $profile->location_ns = $loc->location_ns;
+ }
}
$profile->created = common_sql_now();
diff --git a/classes/statusnet.ini b/classes/statusnet.ini
index 453981cd6..7931c7bcd 100644
--- a/classes/statusnet.ini
+++ b/classes/statusnet.ini
@@ -1,3 +1,4 @@
+
[avatar]
profile_id = 129
original = 17
@@ -243,6 +244,15 @@ created = 142
[invitation__keys]
code = K
+[location_namespace]
+id = 129
+description = 2
+created = 142
+modified = 384
+
+[location_namespace__keys]
+id = K
+
[message]
id = 129
uri = 2
@@ -284,6 +294,10 @@ reply_to = 1
is_local = 17
source = 2
conversation = 1
+lat = 1
+lon = 1
+location_id = 1
+location_ns = 1
[notice__keys]
id = N
@@ -325,6 +339,10 @@ profileurl = 2
homepage = 2
bio = 34
location = 2
+lat = 1
+lon = 1
+location_id = 1
+location_ns = 1
created = 142
modified = 384
diff --git a/db/location_namespace.sql b/db/location_namespace.sql
new file mode 100644
index 000000000..59b2ce67c
--- /dev/null
+++ b/db/location_namespace.sql
@@ -0,0 +1,5 @@
+insert into location_namespace
+ (id, description, created)
+values
+ (1, 'Geonames', now()),
+ (2, 'Where on Earth', now());
diff --git a/db/statusnet.sql b/db/statusnet.sql
index dfcddb643..1524d8395 100644
--- a/db/statusnet.sql
+++ b/db/statusnet.sql
@@ -1,6 +1,7 @@
/* local and remote users have profiles */
create table profile (
+
id integer auto_increment primary key comment 'unique identifier',
nickname varchar(64) not null comment 'nickname or username',
fullname varchar(255) comment 'display name',
@@ -8,6 +9,11 @@ create table profile (
homepage varchar(255) comment 'identifying URL',
bio text comment 'descriptive biography',
location varchar(255) comment 'physical location',
+ lat decimal(10,7) comment 'latitude',
+ lon decimal(10,7) comment 'longitude',
+ location_id integer comment 'location id if possible',
+ location_ns integer comment 'namespace for location',
+
created datetime not null comment 'date this record was created',
modified timestamp comment 'date this record was modified',
@@ -119,6 +125,10 @@ create table notice (
is_local tinyint default 0 comment 'notice was generated by a user',
source varchar(32) comment 'source of comment, like "web", "im", or "clientname"',
conversation integer comment 'id of root notice in this conversation' references notice (id),
+ lat decimal(10,7) comment 'latitude',
+ lon decimal(10,7) comment 'longitude',
+ location_id integer comment 'location id if possible',
+ location_ns integer comment 'namespace for location',
index notice_profile_id_idx (profile_id),
index notice_conversation_idx (conversation),
@@ -556,3 +566,12 @@ create table user_role (
constraint primary key (user_id, role)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
+
+create table location_namespace (
+
+ id integer primary key comment 'identity for this namespace',
+ description varchar(255) comment 'description of the namespace',
+ created datetime not null comment 'date the record was created',
+ modified timestamp comment 'date this record was modified'
+
+) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin;
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
new file mode 100644
index 000000000..048554f0f
--- /dev/null
+++ b/lib/location.php
@@ -0,0 +1,160 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Class for locations
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Location
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @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
+ *
+ * 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 <evan@status.net>
+ * @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 = array();
+
+ /**
+ * 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)
+ {
+ if (is_null($language)) {
+ $language = common_language();
+ }
+
+ $location = null;
+
+ // Let a third-party handle it
+
+ Event::handle('LocationFromName', array($name, $language, &$location));
+
+ return $location;
+ }
+
+ /**
+ * 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;
+
+ // Let a third-party handle it
+
+ Event::handle('LocationFromId', array($id, $ns, $language, &$location));
+
+ return $location;
+ }
+
+ /**
+ * 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($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)) {
+ $language = common_language();
+ }
+
+ if (array_key_exists($language, $this->names)) {
+ 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..745cd4126
--- /dev/null
+++ b/plugins/GeonamesPlugin.php
@@ -0,0 +1,281 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Plugin to convert string locations to Geonames IDs and vice versa
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Action
+ * @package StatusNet
+ * @author Evan Prodromou <evan@status.net>
+ * @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 <evan@status.net>
+ * @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;
+ }
+}
diff --git a/tests/LocationTest.php b/tests/LocationTest.php
new file mode 100644
index 000000000..62849eb9f
--- /dev/null
+++ b/tests/LocationTest.php
@@ -0,0 +1,87 @@
+<?php
+
+if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) {
+ print "This script must be run from the command line\n";
+ exit();
+}
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
+define('STATUSNET', true);
+
+require_once INSTALLDIR . '/lib/common.php';
+
+// Make sure this is loaded
+// XXX: how to test other plugins...?
+
+addPlugin('Geonames');
+
+class LocationTest extends PHPUnit_Framework_TestCase
+{
+
+ /**
+ * @dataProvider locationNames
+ */
+
+ public function testLocationFromName($name, $language, $location)
+ {
+ $result = Location::fromName($name, $language);
+ $this->assertEquals($result, $location);
+ }
+
+ static public function locationNames()
+ {
+ return array(array('Montreal', 'en', null),
+ array('San Francisco, CA', 'en', null),
+ array('Paris, France', 'en', null),
+ array('Paris, Texas', 'en', null));
+ }
+
+ /**
+ * @dataProvider locationIds
+ */
+
+ public function testLocationFromId($id, $ns, $language, $location)
+ {
+ $result = Location::fromId($id, $ns, $language);
+ $this->assertEquals($result, $location);
+ }
+
+ static public function locationIds()
+ {
+ return array(array(6077243, GeonamesPlugin::NAMESPACE, 'en', null),
+ array(5391959, GeonamesPlugin::NAMESPACE, 'en', null));
+ }
+
+ /**
+ * @dataProvider locationLatLons
+ */
+
+ public function testLocationFromLatLon($lat, $lon, $language, $location)
+ {
+ $result = Location::fromLatLon($lat, $lon, $language);
+ $this->assertEquals($result, $location);
+ }
+
+ static public function locationLatLons()
+ {
+ return array(array(37.77493, -122.41942, 'en', null),
+ array(45.509, -73.588, 'en', null));
+ }
+
+ /**
+ * @dataProvider nameOfLocation
+ */
+
+ public function testLocationGetName($location, $language, $name)
+ {
+ $result = $location->getName($language);
+ $this->assertEquals($result, $name);
+ }
+
+ static public function nameOfLocation()
+ {
+ return array(array(new Location(), 'en', 'Montreal'),
+ array(new Location(), 'fr', 'Montréal'));
+ }
+}
+