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


3 comentarios

  • Naoise

    11 de mayo 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 julio de 2010

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

  • rainner

    19 de agosto de 2010

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

¿Pensando en contratar mis servicios?

Lee esto

Algunos apuntes que te pueden resultar útiles:

  • Soy programador web y aunque me encantaría diseñar, no entra dentro de mis competencias. Para poder desarrollar una página web necesito el diseño gráfico final. Si no conoces a ningún diseñador te puedo recomendar alguno con el que haya colaborado y su trabajo me haya parecido bueno.
  • No soy especialista SEO, aunque uso reglas básicas y sentido común a la hora de estructurar el contenido lo que se traduce en una mejora en el posicionamiento.
  • Si es posible envíame toda la información que creas que necesite para presentarte un presupuesto. "Programar una web como esta" no es suficiente, no podré evaluar el coste porque no sabré que funcionalidades tiene. Necesito que me las especifiques.
  • Si el proyecto es de gran envergadura, seguramente que te pida cuál es tu presupuesto. La razón es que si se aleja mucho de lo que a primera vista considero puede costar, te contestaré enseguida que no es un trabajo que pueda hacer.
  • Si existe una fecha de entrega del proyecto, comunícamela. En función de mi disponibilidad te contestaré si es un trabajo que pueda hacer o no.

Para contactarme puedes usar el formulario de contacto.