Echando código: ¿Como ver geográficamente con quienes visitan el Blog?: El API de Google MAPS

Usando Google Maps para mostrar quienes visitan a KodeGeek.com
Desde hace tiempo que había querido jugar con el API de Yahoo para hacer mapas; No sólo no te ponen la restricción estupida de cuantas veces lo puedes llamar (como Google Maps), sino que también es muy fácil de usar. La idea es mostrar en un mapa de Yahoo en donde está la gente que visita este blog. Sin embargo ni Google ni Yahoo Maps soportan mapas de las calles fuera de los Estados Unidos o Inglaterra, mientras que Google Maps si soporta una vista satelital. Así que al final vamos a trabajar con Google Maps.
Si la vida te da limones, te sale hacer limonada (o en pocas palabras, suck it up!)
Ya hablando en serio, para hacer un mapa en Google maps usted necesita hacer lo siguiente:
- Obtener una clave para poder usar el servicio de Google Maps. Es grátis.
- Obtener la lista de direcciones IP a colocar en el mapa. Esas las saco yo de mi bitacora de Apache
- Hacer el mapa entre direcciones IP y Pais con Latitud y Longitud. Más de eso adelante, este paso es crítico.
- Escribir las coordenadas en un formato XML que el parser de AJAX de Google maps pueda entender.
- Crear el mapa con Google maps y bajarse las coordenadas utilizando XML-RPC. De esa manera no tocamos el código, solamente necesitamos actualizar los “marcadores” (markers) de Google Maps cada vez que queramos mostrar los datos. Es obligatorio que se lea como usar el API de Google Maps, no es nada complicado.
- ¡Exito!
La cosa se complica aquí, ya que la correspondencia entre una dirección IP y un par de coordenadas (latitud, longitud) y el país no es fácil de conseguir. Sin embargo, un aproximado debería servir, así que:
- Utilizando NetNeo, de la gente de el proyecto CAIDA me consigo la longitud y latitud de los sistemas autonomos.
- La base de datos de paises y direcciones IP de el proyecto LUDOS me da la otra parte de el rompecabezas.
- Tengo que bajarme ambos archivos, y también tengo que preprocesar los archivos antes de tratar de meterlos en la base de datos en sus respectivas tablas.
- Luego tengo que importar la bitacora de Apache en la base de datos. Hay preprocesamiento.
Sin embargo la información de LUDOS no es muy completa, ya que no tengo los códigos de las ciudades, lo cual es una ladilla. Sería genial poder decir que alguien me visita de alguna ciudad en particular, como Mérida en Venezuela.
Las buenas noticias es que si se puede, para ello nos olvidamos de LUDO y CAIDA y en vez de eso utilizamos el servicio gratuito de HostIP.info. Ellos te permiten bajarte la base de datos para que tu hagas tu propio procesamiento, pero dado que son pocas (menos de 10000) diferentes direcciones IP que vamos a procesar entonces utilizamos su API por HTTP. Les recomiendo que los visiten y que contribuyan para que así la base de datos siga creciendo.
El código que utilizamos para generar los marcadores en formato XML (Direcciones IP únicas, con todos los datos) es el siguiente:
1:#!/usr/bin/perl 2: 3:use strict; 4:use LWP::UserAgent; 5:use Date::Manip; 6: 7:# For example: $HOME/logs/kodegeek.com-Jan-2006.gz 8:use constant LOG_FILE => 9: "$ENV{HOME}/logs/kodegeek.com-" . 10: &UnixDate("today", "%b") . 11: "-" . 12: &UnixDate("today", "%Y") . 13: ".gz"; 14: 15:my $MARKER_FILE = 16: "$ENV{HOME}/public_html/kodegeek_marker.xml"; 17: 18:my %ips = &parseLogFile(LOG_FILE); 19: 20:# Get the latitude and longitude information 21:# and construct the map information. 22:# 23:# Make a backup 24:if ( -f "${MARKER_FILE}") { 25: system("mv ${MARKER_FILE} ${MARKER_FILE}.old"); 26:} 27:open(OUT_XML, ">${MARKER_FILE}") || die "$!, $MARKER_FILE"; 28:print OUT_XML "<markers description=\"kodegeek access log markers\">\n"; 29:my $total=0.0; 30:my $used=0.0; 31:foreach my $ip (values %ips) { 32: $total++; 33: &getIpGeoData($ip); 34: if ( ($ip->getLatitude() != "") 35: && ($ip->getLongitude() != "")) { 36: $used++; 37: printf "[INFO]: Adding ip=%s, country=%s, city=%s, latitude=%s, longitude=%s\n", 38: $ip->getIp(), 39: $ip->getCountry(), 40: $ip->getCity(), 41: $ip->getLatitude(), 42: $ip->getLongitude(); 43: print OUT_XML "<marker ip=\"" . 44: $ip->getIp() . 45: "\" country=\"" . 46: $ip->getCountry() . 47: "\" city=\"" . 48: $ip->getCity() . 49: "\" lat=\"" . 50: $ip->getLatitude() . 51: "\" lng=\"" . 52: $ip->getLongitude() . 53: "\"/>\n"; 54: } 55:} 56:print OUT_XML "</markers>\n"; 57:close(OUT_XML); 58: 59:if (($used < $total) && ($total > 0)) { 60: printf "[WARNING]: %s of the IP addresses were rejected due insufficient data\n", 61: ($used / $total); 62:} 63: 64:# Download the file from the desired URL 65:# using the free service at http://www.hostip.info 66:# 67:sub getIpGeoData { 68: my ($ip) = $_[0]; 69: 70: my $ua = LWP::UserAgent->new( 71: agent => "kodeGeek/visitor_geo_map_generator.pl 1.0", 72: from => 'josevnz@kodegeek.com' 73: ); 74: my $response = $ua->get("http://api.hostip.info/get_html.php?ip=" . 75: $ip->getIp() ."&position=true"); 76: if ($response->is_success) { 77: my($country, $city, $latitude, $longitude) = 78: split("\n", $response->content); 79: $ip->setCountry((split("\:", $country))[1]); 80: $ip->setCity((split("\:", $city))[1]); 81: $ip->setLatitude((split("\:", $latitude))[1]); 82: $ip->setLongitude((split("\:", $longitude))[1]); 83: } else { 84: die $response->status_line; 85: } 86: return $response->status_line; 87:} 88: 89:# Parse the GeoFile 90:# @param The file to parse 91:# @return An hastable with the unique IP addresses and 92:# the number of ocurrences 93:sub parseLogFile { 94: my %IpMap = (); 95: my $file = $_[0]; 96: printf "[INFO]: Parsing LogFile %s\n", $file; 97: open(INPUT_FILE, "/bin/zcat $file |") || die "$!"; 98: # Not portable, but who cares! 99: while(<INPUT_FILE>) { 100: chomp($_); 101: my @tokens = split('\s', $_); 102: my $ip = undef; 103: if (! defined $IpMap{$tokens[0]}) { 104: $ip = IpInfo::new($tokens[0]); 105: $IpMap{$tokens[0]}=$ip; 106: } else { 107: $ip = $IpMap{$tokens[0]}; 108: } 109: $ip->addVisit(); 110: 111: } 112: close(INPUT_FILE); 113: return %IpMap; 114:} 115: 116:# Models an Ip address information 117:package IpInfo; 118: 119:sub new { 120: if (!defined $_[0]) { 121: die "[ERROR]: Please pass an IP address."; 122: } 123: my $ref = {}; 124: $ref->{ip}=$_[0]; 125: bless $ref; 126:} 127: 128:sub addVisit { 129: my $ref = shift; 130: $ref->{count}++; 131:} 132: 133:sub getVisits { 134: my $ref = shift; 135: return $ref->{count}; 136:} 137: 138:sub getIp { 139: my $ref = shift; 140: return $ref->{ip}; 141:} 142: 143:sub getLatitude { 144: my $ref = shift; 145: return $ref->{latidude}; 146:} 147: 148:sub getLongitude { 149: my $ref = shift; 150: return $ref->{longitude}; 151:} 152: 153:sub getCountry { 154: my $ref = shift; 155: return $ref->{country}; 156:} 157: 158:sub getCity { 159: my $ref = shift; 160: return $ref->{city}; 161:} 162: 163:sub setLatitude { 164: my $ref = shift; 165: $_[0] =~ s/^\s|$\s//; 166: $ref->{latidude}=$_[0]; 167:} 168: 169:sub setLongitude { 170: my $ref = shift; 171: $_[0] =~ s/^\s|$\s//; 172: $ref->{longitude}=$_[0]; 173:} 174: 175:sub setCountry { 176: my $ref = shift; 177: $_[0] =~ s/^\s|$\s//; 178: $ref->{country}=$_[0]; 179:} 180: 181:sub setCity { 182: my $ref = shift; 183: $_[0] =~ s/^\s|$\s//; 184: $ref->{city}=$_[0]; 185:} 186: 187:1; 188: 189:__END__ 190:=head1 NAME 191:visitor_geo_map_generator.pl - Create a map with the location of the visitors 192:to the KodeGeek weblog. 193: 194:=head1 DESCRIPTION 195: 196:The script downloads and prepares for import data from several free sources. 197: 198:=head1 LICENSE 199: 200:GPL 201: 202:=head1 AUTHOR 203: 204:Jose Vicente Nunez Zuleta (josevnz@kodegeek.com) 205: 206:=cut
El programa cuando corre se ve así:
[josevnz@localhost bin]$ ./visitor_geo_map_generator.pl
[INFO]: Parsing LogFile /home/josevnz/logs/kodegeek.com-Jan-2006.gz
[INFO]: Adding ip=66.249.71.50, country=UNITED STATES (US), city=Manassas, VA, latitude=38.7474, longitude=-77.4854
[INFO]: Adding ip=201.243.240.170, country=NETHERLANDS (NL), city=The Hague, latitude=52.0833, longitude=4.3
[INFO]: Adding ip=64.76.62.58, country=UNITED STATES (US), city=Miami, FL, latitude=25.7757, longitude=-80.2108
[INFO]: Adding ip=66.142.40.220, country=UNITED STATES (US), city=Olivette, MO, latitude=38.6723, longitude=-90.3772
[INFO]: Adding ip=84.121.72.243, country=SPAIN (ES), city=Murcia, latitude=37.9833, longitude=-1.11667
[INFO]: Adding ip=207.46.98.80, country=UNITED STATES (US), city=CALEDONIA, MI, latitude=42.7939, longitude=-85.5132
[INFO]: Adding ip=206.48.96.113, country=VENEZUELA (VE), city=Caracas, latitude=10.4667, longitude=-67.0333
[INFO]: Adding ip=82.51.190.12, country=ITALY (IT), city=Salerno, latitude=39.3667, longitude=16.4
[INFO]: Adding ip=68.142.249.86, country=UNITED STATES (US), city=Sunnyvale, CA, latitude=37.3857, longitude=-122.026
[INFO]: Adding ip=66.196.91.121, country=UNITED STATES (US), city=MAYSVILLE, KY, latitude=38.6295, longitude=-83.7801
[WARNING]: 0.334332833583208 of the IP addresses were rejected due insufficient data
%77 de efectividad traduciendo las direcciones, todo por grátis. Bastante aceptable en mi opinión.
El XML generado se ve similar a esto:
1:<markers description="kodegeek access log markers"> 2:<marker ip="66.249.71.50" country="UNITED STATES (US)"city="Manassas, VA" lat="38.7474" lng="-77.4854"/> 3:<marker ip="201.243.240.170" country="NETHERLANDS (NL)"city="The Hague" lat="52.0833" lng="4.3"/>... 108:<marker ip="68.142.249.86" country="UNITED STATES (US)" city="Sunnyvale, CA" lat="37.3857" lng="-122.026"/> 109:<marker ip="194.179.83.87" country="SPAIN (ES)" city="Madrid" lat="40.4333" lng="-3.7"/> 110:</markers>
Y de el lado de el cliente solamente necesitamos poner un HTML con el respectivo Javascript que llama las librerías de Google Map, se baja nuestro archivo de marcadores y genera el mapa en todo su esplendor:
1:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 2:"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 3:<html xmlns="http://www.w3.org/1999/xhtml"> 4: <head> 5: <script src="http://maps.google.com/maps?file=api&v=1&key=AAA" 6: type="text/javascript"></script> 7: </head> 8: <body> 9: <div id="map" style="width: 600px; height: 500px"></div> 10: <script type="text/javascript"> 11: //<![CDATA[ 12: /* 13: * Construct a map based on the Apache visitor access log 14: for the site KodeGeek.com 15: * Author: Jose Vicente Nunez Zuleta (josevnz@kodegeek.com) 16: * License: GPL 17: * Blog: http://kodegeek.com 18: */ 19: 20: /* 21: * Create a marker whose info window displays the 22: * ip address, country and state of the visitor 23: * @param XML fragment with the required attributes 24: * @return A marker object with the appropriate event handlers 25: */ 26: function customMarker(markerInfo) { 27: 28: var point = new GPoint( 29: parseFloat(markerInfo.getAttribute("lng")), 30: parseFloat(markerInfo.getAttribute("lat"))); 31: 32: var marker = new GMarker(point); 33: 34: var infoHtml = 35: "<b>Direcciónn IP:</b>" + 36: markerInfo.getAttribute("ip") + 37: ", <b>Paíns:</b>" + 38: markerInfo.getAttribute("country") + 39: ", <b>Ciudad:</b>" + 40: markerInfo.getAttribute("city"); 41: "</b>"; 42: 43: GEvent.addListener( 44: marker, 45: 'click', 46: function() { 47: marker.openInfoWindowHtml(infoHtml); 48: } 49: ); 50: return marker; 51: } 52: 53: /* 54: * Retrieve the marker information from the 55: * given request and creater the proper event handlers 56: * @param map Global map object 57: * @param request Global request object 58: */ 59: function stateChange(request, map) { 60: if (request.readyState == 4) { 61: var xmlDoc = request.responseXML; 62: var markers = 63: xmlDoc.documentElement.getElementsByTagName("marker"); 64: for (var i = 0; i < markers.length; i++) { 65: map.addOverlay(customMarker(markers[i])); 66: } 67: } // end if 68: } // end request 69: 70: var map = new GMap(document.getElementById("map")); 71: map.setMapType(G_HYBRID_TYPE); 72: map.addControl(new GSmallMapControl()); 73: map.centerAndZoom(new GPoint(0.0, 0.0), 16); 74: map.openInfoWindow(map.getCenterLatLng(), 75: document.createTextNode("KodeGeek, 76: http://KodeGeek.com")); 77: var request = GXmlHttp.create(); 78: request.open('GET', 'kodegeek_marker.xml', true); 79: request.onreadystatechange = stateChange(request, map); 80: request.send(null); 81: 82: //]]> 83: </script> 84: </body> 85: 86:</html>
Es increiblement lo fácil que resulta utilizar el API de Google Maps. Tienen muchos ejemplos y grupos de soporte, les recomiendo que juegen un poco con ella, saldrán gratamente sorprendidos.
El código como siempre está en CVS.
Bueno, esto va a correr de ahora en adelante, así que aqui les dejo el enlace para que se diviertan
Buscar en Technorati: Google Maps
Sin categoría

Comentarios recientes