Newer
Older
Digital_Repository / Repositories / Maps / gd_map.pl
  1. #!/usr/bin/env perl
  2. use strict;
  3. use Time::HiRes qw( gettimeofday );
  4. use CGI;
  5. use DBI;
  6. use GD;
  7. use Geo::IP;
  8. use Geo::Proj4;
  9.  
  10. my ( $start_sec, $start_micro ) = gettimeofday;
  11. my ($start_time) = ( $start_sec * 1000 ) + round( $start_micro / 1000 );
  12.  
  13. my ($page);
  14.  
  15. # Database connection.
  16. my ($dsn) = "DBI:mysql:database=eprintstats;host=localhost";
  17. my ($user_name) = "eprintstatspriv";
  18. my ($password) = "AuldGrizzel";
  19. my ( $connect, $query, %types, %unmapped, $stat, $row, $num_rows, $vtype );
  20. my ($where) = '';
  21.  
  22. # GD image stuff.
  23. my ( $mapimage, $mapimagefile );
  24. my ( $red, $white, $black, $blue, $tc );
  25. my ( $width, $height );
  26.  
  27. # Geolocation database.
  28. my ( $gi, $proj );
  29. my ($gidb) = '/usr/local/share/GeoIP/GeoLiteCity.dat';
  30.  
  31. # Miscellaneous variables.
  32. my ($x_offset) = 16986796.16;
  33. my ($y_offset) = 8615499.05;
  34. my ($max_x) = $x_offset * 2;
  35. my ($max_y) = $y_offset * 2;
  36. my ( %cities, %IPs );
  37. my ($num_entries) = -1; # include all entries from database
  38. my ($num_hits) = 0;
  39. my ( $ip, $count, $location );
  40. my ( $city, $lat, $long, $x, $y ) = ( 0, 0, '', 0, 0 );
  41. my ($show_only) = 'both'; # include both abstracts & downloads
  42. my ($eprint) = '';
  43.  
  44. $page = new CGI;
  45. print $page->header(
  46. -type => "image/jpeg",
  47. -Pragma => 'no-cache',
  48. -Cache-Control => 'no-cache'
  49. );
  50. $num_entries = $page->param('top') if ( defined $page->param('top') );
  51. $show_only = $page->param('show') if ( defined $page->param('show') );
  52. $eprint = $page->param('eprint') if ( defined $page->param('eprint') );
  53.  
  54. $gi = Geo::IP->open( $gidb, GEOIP_STANDARD )
  55. or die "Unable to open GeoIP database $gidb\n";
  56.  
  57. $proj = Geo::Proj4->new( proj => "robin", ellps => "sphere", lon_0 => 10 )
  58. or die "parameter error: " . Geo::Proj4->error . "\n";
  59.  
  60. $width = $page->param('width');
  61. $height = $page->param('height');
  62. $mapimagefile = "/Users/nstanger/Sites/maps/map_${width}x${height}.png";
  63. $mapimage = GD::Image->newFromPng( $mapimagefile, 1 );
  64. $white = $mapimage->colorAllocate( 255, 255, 255 );
  65. $black = $mapimage->colorAllocate( 0, 0, 0 );
  66. $red = $mapimage->colorAllocate( 255, 0, 0 );
  67. $blue = $mapimage->colorAllocate( 0, 0, 255 );
  68. for ( my $i = 1; $i < 255; $i++ )
  69. {
  70. $tc = $mapimage->colorAllocate( $i, 0, ( 255 - $i ) );
  71. }
  72.  
  73. $connect = DBI->connect( $dsn, $user_name, $password, { RaiseError => 1 } );
  74.  
  75. $types{'download'} = $types{'abstract'} = 0;
  76. $unmapped{'download'} = $unmapped{'abstract'} = 0;
  77.  
  78. # Set up query.
  79. if ( $show_only eq 'both')
  80. {
  81. $where = "view_type IN ('download', 'abstract')";
  82. }
  83. else
  84. {
  85. $where = "view_type = '$show_only'";
  86. }
  87.  
  88. $where .= " AND archiveid IN ($eprint)" unless ( $eprint eq '' );
  89.  
  90. $query = "SELECT ip, view_type, COUNT(*) AS count
  91. FROM view
  92. WHERE $where
  93. GROUP BY ip, view_type
  94. ORDER BY count DESC" . ( ( $num_entries > 0 ) ? " LIMIT $num_entries" : '' );
  95.  
  96. $stat = $connect->prepare($query);
  97. $stat->execute();
  98. $num_rows = $stat->rows;
  99.  
  100. if ( $num_rows > 0 )
  101. {
  102. $num_entries = $num_rows if ( $num_entries < 1 );
  103.  
  104. while ( $row = $stat->fetchrow_hashref() )
  105. {
  106. $ip = $row->{'ip'};
  107. $count = $row->{'count'};
  108. $vtype = $row->{'view_type'};
  109.  
  110. $IPs{$ip} = 1;
  111.  
  112. $location = $gi->record_by_addr($ip);
  113.  
  114. if ( defined($location) )
  115. {
  116. $lat = $location->latitude;
  117. $long = $location->longitude;
  118. $city = (
  119. ( $location->city eq '' )
  120. ? 'Unknown'
  121. : $location->city
  122. )
  123. . " ($lat, $long)";
  124.  
  125. ( $x, $y ) = $proj->forward( $lat, $long );
  126. $x = round( ( $x + $x_offset ) / $max_x * $width );
  127. $y = round( ( $y_offset - $y ) / $max_y * $height );
  128.  
  129. if ( !defined( $cities{$city} ) )
  130. {
  131. $cities{$city}{'lat'} = $lat;
  132. $cities{$city}{'long'} = $long;
  133. $cities{$city}{'abstract'} = 0;
  134. $cities{$city}{'download'} = 0;
  135. $cities{$city}{'count'} = 0;
  136. }
  137. $cities{$city}{$vtype} += $count;
  138. $cities{$city}{'count'} += $count;
  139. $types{$vtype} += $count;
  140.  
  141. $cities{$city}{'x'} = $x;
  142. $cities{$city}{'y'} = $y;
  143. }
  144. else
  145. {
  146. $unmapped{$vtype} += $count;
  147. }
  148. }
  149. }
  150.  
  151. $stat->finish();
  152. $connect->disconnect();
  153.  
  154. # Generate dots for each city.
  155. CITY: foreach $city ( keys %cities )
  156. {
  157. if ( $show_only eq 'both' )
  158. {
  159. # Blend colour according to the ratio of abstracts to downloads.
  160. $tc = $mapimage->colorClosest(
  161. round( $cities{$city}{'download'} / $cities{$city}{'count'} * 255 ),
  162. 0,
  163. round( $cities{$city}{'abstract'} / $cities{$city}{'count'} * 255 )
  164. );
  165. }
  166. elsif ( $show_only eq 'download' )
  167. {
  168. next CITY if ( $cities{$city}{'download'} == 0 );
  169. $tc = $red;
  170. }
  171. elsif ( $show_only eq 'abstract' )
  172. {
  173. next CITY if ( $cities{$city}{'abstract'} == 0 );
  174. $tc = $blue;
  175. }
  176. else # ack, boom
  177. {
  178. last CITY;
  179. }
  180. $mapimage->filledRectangle(
  181. $cities{$city}{'x'} - 1,
  182. $cities{$city}{'y'} - 1,
  183. $cities{$city}{'x'} + 1,
  184. $cities{$city}{'y'} + 1,
  185. $tc
  186. );
  187. }
  188.  
  189. # Output summary data.
  190. if ( ( $show_only eq 'both' ) || ( $show_only eq 'download' ) )
  191. {
  192. $mapimage->string( gdSmallFont,
  193. 3, 3,
  194. "$types{'download'} downloads"
  195. . (
  196. ( $unmapped{'download'} > 0 )
  197. ? " (+$unmapped{'download'} unmappable)"
  198. : ''
  199. ),
  200. $red
  201. );
  202. }
  203. if ( ( $show_only eq 'both' ) || ( $show_only eq 'abstract' ) )
  204. {
  205. $mapimage->string( gdSmallFont,
  206. 3, 15,
  207. "$types{'abstract'} abstracts"
  208. . (
  209. ( $unmapped{'abstract'} > 0 )
  210. ? " (+$unmapped{'abstract'} unmappable)"
  211. : ''
  212. ),
  213. $blue
  214. );
  215. }
  216. $mapimage->string( gdSmallFont, 3, 27,
  217. 'from ' . scalar( keys %cities ) . ' cities', $black );
  218. $mapimage->string( gdSmallFont, 3, 39,
  219. '(' . scalar( keys %IPs ) . ' IP addresses)', $black );
  220.  
  221. my ( $finish_sec, $finish_micro ) = gettimeofday();
  222. my ($finish_time) = ( $finish_sec * 1000 ) + round( $finish_micro / 1000 );
  223. $mapimage->string( gdSmallFont, 3,
  224. $height - 15,
  225. 'Map generated in ' . ( $finish_time - $start_time ) . ' ms',
  226. $black );
  227.  
  228. open RESULTS, ">>/tmp/gd_results_$num_entries.txt" or die "Argh!\n";
  229. print RESULTS ( $finish_time - $start_time ) . "\n";
  230. close RESULTS;
  231.  
  232. binmode(STDOUT);
  233.  
  234. print $mapimage->jpeg();
  235.  
  236. sub round
  237. {
  238. my ($n) = shift;
  239. return int( $n + 0.5 * ( $n <=> 0 ) );
  240. }