Geolocalización por IP con PHP

in php published the 28 de January 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


3 comentarios

  • Naoise

    11 de May de 2010

    Hola Albert, gracias por compartir este código, es justo lo que andaba buscando. Suelo hacer la busqueda en inglés y en un momento de lucidez he pensado en hacerla en castellano y mira, perfecto. Quería pedirte si podías publicar la clase mapper_PaisMapper. Estoy en el mismo caso, dado un país, o mejor, una ciudad, extraer el locale, preferiblemente en el código que usa el setlocale() de PHP (p.e. "ca_ES" para Barcelona). Si no, sabes donde podría encontrar algo ya implementado al respecto? Merci

  • miguel

    02 de July de 2010

    muy buena tu explicacion me sirvio bastante para lo que estaba haciendo 10 puntos gracias.

  • rainner

    19 de August de 2010

    http://rainner.sytes.net/pais.php aqui tenes ejemplo funcionando y descarga el paquete aqui http://rainner.sytes.net/pais.zip

Tags

Thinking of hiring my services?

Read this

Some notes you might find useful:

  • I'm a web programmer and although I would love to design, it doesn't fall within my competences. To be able to develop a website I need the final graphic design. If you don' know a designer I can recommend you any I have worked with and I though their work was good.
  • I'm not a SEO specialist, although I use basic rules and common sense when structuring content which translates in a better ranking.
  • If possible, send me all the information you think I might need to give you a quote. “To program a website like this one” isn't enough, I won't be able to evaluate its cost as I won't know its functionalities. I need you to specify them.
  • Depending on the scope of the project, I might ask you your budget.
  • Tell me if you have a deadline. Depending on my availability I'll tell you if it's a job I can do or not.

To contact me you can use the contact form.