Geolocalización por IP con PHP

en php publicat el 28 de gener 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 maig 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 juliol de 2010

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

  • rainner

    19 de agost de 2010

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

Pensant en contractar els meus serveis?

Llegeix això;

Alguns apunts que et poden resultar útils.

  • Soc programador web i encara que m'encantaria dissenyar, no entra dins de les meves competències. Per poder desenvolupar una pàgina web necessito el disseny gràfic final. Si no coneixes a cap dissenyador puc recomanar-te'n algun amb el que hagi col·laborat i la seva feina m'hagi semblat bona.
  • No soc especialista SEO, encara que faig servir regles bàsiques i sentit comú a l'hora d'estructurar el contingut el que es tradueix amb una millora en el posicionament.
  • Si es possible, envia'm tota la informació que creguis que necessito per preparar-te un pressupost. "Programar una web com aquesta" no es suficient, no podré avaluar el cost per que no sabré quines funcionalitats te. Necessito que les especifiquis.
  • Si el projecte es de gran envergadura, segurament que et demani quin es el teu pressupost. La raó es que si s'allunya molt del que a primera vista considero pot costar, et contestaré de seguit que no es un treball que pugui fer.
  • Si existeix una data d'entrega del projecte, comunica-me-la. En funció de la meva disponibilitat et contestaré si es un treball que pugui fer o no.

Per contactar-me pots utilitzar el formulari de contacte.