Archivo

Archivo para Domingo, 15 de enero de 2006

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

Domingo, 15 de enero de 2006

Google Maps KodeGeek
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&oacute;nn IP:</b>" +  36:                markerInfo.getAttribute("ip") +  37:                ", <b>Pa&iacute;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:

Sin categoría

Opinión de pelicula: Secuestro Express

Domingo, 15 de enero de 2006

Si, ya todo el mundo la vió hace meses y ya todo el mundo sabe que están tratando de nominarla al Oscar; Yo en su momento no la pude ver y hoy (de manera impulsiva) me decidí a comprar el DVD, el cual está siendo distribuido por Miramax.

Para quienes no saben de la trama, se trata de la historia de un secuestro rápido, en el cual una pareja joven es secuestrada por 4 ampones en la ciudad de Caracas. Y es allí en donde se complican las cosas.

Cosas buenas de la pelicula: Buena producción, acción fluida y la actuación (de la mayoría de los actores, excepto Ruben Blades el cual luce aburrido y sin energia) es convincente. Hay un mensaje interesante y es que las instituciones no van a solucionar el problema, es la gente y los que tienen quienes tienen que acercarse y promover el dialogo. Si hay hambre no hay dialogo. Si me restriegas en la cara tu dinero, te jodo. Pero más adelante les hablaré más de eso.

Cosas malas: Está demasiada cargada de estereotipos. Las instituciones están corrompidas, son criminales además de que se pueden comprar y son ineficaces; Los ricos son una pila de drogadictos y maricones, Los Venezolanos sólo usan la droga pero son los Colombianos quienes la distribuyen y los marginales (pobres) son resentidos sociales, sin educación y quienes explotan el problema social para su provecho (de manera violenta y con impunidad). Caracas es pintada como un sitio sucio y decadente en el cual hay peligro en todos sus rincones.

El DVD viene con un “mini documental” de el director y de que fué lo que trataron de expresar. Lo curioso es que no está orientado a Venezolanos o a Latinoamericanos, sino a Americanos y que llaman a Latinoamerica “el patio trasero” de los Estados Unidos y hay una invitación muy sútil a intervenir y no ser pasivos (exactamente como es algo que simplemente no explican en el DVD. Muy conveniente).

¿Que es lo que exactamente quiere decir el director con esto? La historia nos enseña como ha sido la participación de los Estados Unidos en otros paises y de cierta manera diluye el mensaje original de la pelicula. Por un momento sentí que la pelicula estaba siendo utilizada de otra manera.

Lo otro es que la clase media no está representada por ningún lado, solamente hay ricos o pobres. Es una visión muy “blanco y negro” ya que uno o dos de los personajes se identifica como clase media, pero uno de ellos roba por placer y el otro es medico pero consume drogas y vive una vida de ricos. No me parece ya que yo fuí uno de los “huevones” como muchos otros que por años agarró el metro y autobus para ir al trabajo, mientras vivia en una habitación (no apartamento ojo) alquilado en Caracas. Hay mucha pobreza en Caracas y hay mucha gente rica, pero me ladilla ver que la clase media de nuevo es usada como mejor le parece al cineasta.

Me gustó mucho ver la calidad de producción y las actuaciones; Como siempre hay humor negro en los lugares menos obvios pero la explotación descarada de los estereotipos hace que la visión de el director se convierta en algo bastante surrealista. Obviamente no es un documental ya que en muchos casos la ficción se mofa de la realidad (por ejemplo los secuestradores son atacados por malandros callejeros en varias ocasiones) .

Pero, ¿y es una buena pelicula? Si lo es, pero se queda corta en su proposito ya que se simplifican demasiado ciertos aspectos de la realidad Venezolana, además de que se vende a Venezuela como un lugar hostil que requiere la intervención Americana (el titulo de el DVD dice así: “In Caracas, the most dangerous city on earth, kidnapping is a profitable and thriving business”).

Creo que hace falta más dialogo, mejor educación y es entonces que quizas tengamos más opciones para salir adelante como país. Me molesta el lenguaje ambiguo de la pelicula.

Por otro lado, si la intención fué crear incomodidad y cuestionar valores, entonces podemos decir que la pelicula dió en el clavo. Al menos va a tener hablando a más de uno, aunque estemos de acuerdo o no con su mensaje…

Buscar en Technorati:

Sin categoría