Geolocalización por IP con PHP
en php publicado el 28 de Enero de 2010
Geolocalización por IP es un mecanismo que a partir de una dirección IP es capaz de saber en qué lugar se encuentra el ordenador con dicha dirección. Existen varios servicios gratuitos que permiten obtener esta información, siendo bastante precisa a nivel de país y menos a nivel de ciudad. Funcionan como una API que devuelve una estructura de datos mediante una petición GET. Yo utilizo IPInfoDB porque devuelve la información en formatos diferentes (XML, JSON o CSV) y porque a diferencia de otros servicios similares acierta la ciudad donde vivo.
Estructura XML
Por ejemplo para obtener información del país en formato XML, la url es http://ipinfodb.com/ip_query_country.php. Esta llamada devuelve una estructura como esta:
<Response>
<Ip>212.58.253.68</Ip>
<Status>OK</Status>
<CountryCode>GB</CountryCode>
<CountryName>United Kingdom</CountryName>
</Response>
Ejemplo sencillo
Para poder procesar esta estructura de datos, PHP tiene un par de métodos muy útiles. file_get_contents convierto un fichero en una cadena, incluso que el fichero sea remoto, y SimpleXMLElement, una clase para el manejo de estructuras de datos con formato XML.
$xml = file_get_contents("http://ipinfodb.com/ip_query_country.php");
$sxe = new SimpleXMLElement($xml);
echo $sxe->CountryName;
Caso práctico
¿Pero en la práctica para qué sirve conocer el lugar desde que visitan una página web? balneospaunioforms es una web multi idioma con 5 idiomas actualmente y utilizo el país de origen del visitante para servir el contenido en su idioma. Si no existe se muestra en el idioma del país por defecto. Para ello necesito una tabla en base de datos con el código del país ISO 3166-1-alpha-2, que es el que utiliza IPInfoDB, el nombre del país y el idioma.
El comando SQL para crear la tabla sería el siguiente:
CREATE TABLE `pais` (
`PaisID` int(11) unsigned NOT NULL AUTO_INCREMENT,
`Codigo` char(2) NOT NULL DEFAULT '',
`Nombre` text NOT NULL,
`IdiomaID` tinyint(1) unsigned DEFAULT NULL,
PRIMARY KEY (`PaisID`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
En una segunda tabla tendría la lista de idiomas que está relacionada con la tabla país por el campo IdiomaID. Aparte del nombre, incluyo el código ISO para poderlo utilizar con las locales y así poder internacionalizar con gettext.
CREATE TABLE `idioma` (
`IdiomaID` tinyint(1) unsigned NOT NULL AUTO_INCREMENT,
`Codigo` char(2) NOT NULL DEFAULT '',
`Nombre` varchar(20) NOT NULL DEFAULT '',
PRIMARY KEY (`IdiomaID`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
La lista oficial de países con el código 3166-1-alpha-2 la gestiona ISO International Organisation for Standardization y se puede descargar en formato CSV para importar a MySQL.
El siguiente paso sería crear los idiomas en la tabla idioma que va a manejar la web y asociar países con uno de los idiomas posibles. No hace falta hacer esta asignación para todos los países ya que se puede determinar que para los que no esté informado se mostrará en el idioma por defecto.
Ahora viene la parte PHP, básicamente una función que devuelve una instancia de una clase del tipo idioma. Además se guarda en la sesión con lo cual se evita hacer llamadas innecesarias a la API.
function getIdioma() {
if (!isset($_SESSION['idioma'])) {
// leer la respuesta de la API
$xml = file_get_contents("http://ipinfodb.com/ip_query_country.php");
$pais = null;
if ($xml) {
// se ha conseguido abrir una conexión, parser el XML
$sxe = new SimpleXMLElement($xml);
// consultar la base de datos con el código de país devuelto y crear una instancia de la clase país
$paisMapper = new mapper_PaisMapper();
$pais = $paisMapper->findByCodigo($sxe->CountryCode);
}
// si la variable $pais está vacía (no se pudo abrir una conexión, no se encontró el país en la tabla de países) o no hay ningún idioma definido para ese país, se consulta la base de datos con el país definido como defecto
if (!$pais || !$pais->getIdioma()) $pais = $paisMapper->findByCodigo('ES');
// recupera una instancia de la clase idioma
$idioma = $pais->getIdioma();
} else {
$idioma = $_SESSION['idioma'];
}
// devuelve una instancia de la clase idioma
return $idioma;
}
Yo utilizo clases para "mapear" resultados de consultas a la base de datos con clases que representan los modelos pero se podrían utilizar consultas SQL directamente.
Otras funcionalidades de la API
Como he explicado al principio, la API de IPInfoDB permite recuperar los datos en formatos diferentes y incluso obtener información de la ciudad, la región, etc. Está todo explicado en http://ipinfodb.com/ip_location_api.php