#!/usr/bin/perl -w # # VW - map maker v0.1 # by f roque # #Reads in data that vw-spider has generated, draws map from data, #updates data with coords of map. # #see vw-spider for description of the mysql tables necessary # #todo: #better matching of what pages fall under what categories. #output to some sort of vector based gfx - current png's too pixelated #implement different visualization methods? #i think the resizing is broken use strict; use GD; use DBI; use Math::Trig qw/deg2rad/; use Getopt::Std; use Data::Dumper; require("/home/frisco/bin/dbgeneric"); $|=1; my %opt = ( 'm' => -1, 'f' => '/tmp/default.png', 'r' => '/', 'A' => 360, 's' => 1, 'u' => 0, 'C' => 0, 'a' => 0, 'l' => 0, 'n' => 0, 'c' => 0, 'd' => 0, 'v' => 0, 'D' => 0, 'w' => 0, 'h' => 0, ); unless (getopts('m:f:r:A:s:uCalncdvDwh', \%opt) && !$opt{'h'}) { print STDERR <<"EOF"; usage: $0 -m # map run id (defaults to max(map_run_id)) -f file filename of map being made -r root root to start at -A # total angle degree to work with -s # scale by factor -u update the db -C use predetermined coords -a draw all links -l draw logical links -n draw page names -c draw coordinates -d draw page dots -D draw Descriptive line -w print web imagemap data -v verbose -h this message Common Uses: $0 -alduvm3 draw map after calculating coordinates and writing coordinates to the database, using run 3 data $0 -aldCvm3 draw map using pre-calculated coordinates from run 3 $0 -aldCvwm3 -s .2853 draw map using pre-calculated coordinates from run 3 scale it down by a .2853 multiplier and print out a corresponding html imagemap $0 -aldpvm3 -r /other draw map after calculating coordinates from run data 3 using a root of /other EOF exit; } $opt{'u'} = 0 if ($opt{'C'} && $opt{'u'}); print STDERR "Connecting to database..." if $opt{'v'}; print STDERR $main::webdb.' '.$main::user.' '.$main::password."\n" if 0; my $dbh = DBI->connect("dbi:mysql:".$main::webdb, $main::user, $main::password) or die $DBI::errstr; print STDERR "...done\n" if $opt{'v'}; my $sql = ''; my $sth = ''; my @nar = (); #generic array used through the recursive function my @loglinks = (); #the logical links my @actlinks = (); #the actual links my %dots = (); #the x,y points my %pages = (); my $width = int(3000*$opt{'s'}); my $height = int(3000*$opt{'s'}); my $immarginr = int(200*$opt{'s'}); my $immarginl = int(30*$opt{'s'}); my $immargint = int(30*$opt{'s'}); my $immarginb = int(30*$opt{'s'}); #added to prevent my site from getting slammed when people run this. my $host = 'http://www.example.com/'; if ($opt{'m'} < 1) { print STDERR "Determining max(map_run)..." if $opt{'v'}; $sql = "SELECT MAX(map_run_id) FROM map_run"; $opt{'m'} = $dbh->selectrow_array($sql); print STDERR "...done\n" if $opt{'v'}; } if ($opt{'C'}) { print STDERR "Reading in x,y data..." if $opt{'v'}; $sql = "SELECT DISTINCT page, x, y FROM map_pages WHERE map_run= "; $sql.= $opt{'m'}." AND x IS NOT NULL AND y IS NOT NULL AND page LIKE '"; $sql.= $opt{'r'}."%' "; my $data = $dbh->selectall_arrayref($sql); foreach my $arr (@$data) { $dots{$$arr[0]} = [ $$arr[1], $$arr[2] ]; } print STDERR "...done\n" if $opt{'v'}; if ($opt{'a'}) { print STDERR "Reading in all link info..." if $opt{'v'}; $sql = "SELECT DISTINCT page, link FROM map_pages WHERE "; $sql.= "map_run=".$opt{'m'}." AND type=1 AND page LIKE '"; $sql.= $opt{'r'}."%' "; $data = $dbh->selectall_arrayref($sql); foreach my $arr (@$data) { push @actlinks, [ $$arr[0], $$arr[1] ]; } print STDERR "...done\n" if $opt{'v'}; } if ($opt{'l'}) { print STDERR "Reading in logical link info..." if $opt{'v'}; $sql = "SELECT DISTINCT page, link FROM map_pages WHERE "; $sql.= "map_run=".$opt{'m'}." AND type=2 AND page LIKE '"; $sql.= $opt{'r'}."%' "; $data = $dbh->selectall_arrayref($sql); foreach my $arr (@$data) { my ($to, $from) = @$arr; next if $to eq '' or $from eq '' or !defined($dots{$to}) or !defined($dots{$from}); my ($tx, $ty) = @{$dots{$to}}; my ($fx, $fy) = @{$dots{$from}}; push @loglinks, [ $fx, $fy, $tx, $ty, $to, $from ]; } print STDERR "...done\n" if $opt{'v'}; } } else { $sql = "SELECT DISTINCT a.link FROM map_pages AS a, map_pages AS b"; $sql .= " WHERE a.page = ? AND a.type != 0 AND a.map_run = ".$opt{'m'}; $sql .= " AND a.link=b.page AND a.page LIKE '".$opt{'r'}; $sql .= "%' ORDER BY a.link"; $sth = $dbh->prepare($sql); $sql = "SELECT DISTINCT page,link FROM map_pages WHERE map_run="; $sql .=$opt{'m'}." AND page LIKE '".$opt{'r'}."%' AND link LIKE '"; $sql .=$opt{'r'}."%' AND type != 0 "; $sql .=" ORDER BY page,link"; my $data = $dbh->selectall_arrayref($sql); foreach my $arr (@$data) { push @{$pages{$$arr[0]}}, $$arr[1]; } print STDERR "Recursing through all pages...\n" if $opt{'v'}; DetermineAngle($opt{'r'}, $opt{'r'}, 0, 0, $opt{'A'}, 0, 0, \@nar, $opt{'A'}); print STDERR "...done\n" if $opt{'v'}; } print STDERR "Working out image dimensions..." if $opt{'v'}; my ($hix, $lox, $hiy, $loy) = (0,0,0,0); foreach my $node ( keys %dots ) { my ($x, $y) = @{$dots{$node}}; $hix = $x if $x > $hix; $lox = $x if $x < $lox; $hiy = $y if $y > $hiy; $loy = $y if $y < $loy; } $width = int(($hix + abs($lox) + $immarginl + $immarginr) * $opt{'s'}); $height = int(($hiy + abs($loy) + $immargint + $immarginb) * $opt{'s'}); my $midx = int((abs($lox) + ($immarginl)) * $opt{'s'}); my $midy = int((abs($loy) + ($immargint)) * $opt{'s'}); print STDERR "...done\n" if $opt{'v'}; if ($opt{'u'}) { print STDERR "Inserting data to db..." if $opt{'v'}; InsertDataToDB(); print STDERR "...done\n" if $opt{'v'}; } print STDERR "Drawing stuff to the image..." if $opt{'v'}; my $im = new GD::Image($width, $height); my $white = $im->colorAllocate(255,255,255); my $black = $im->colorAllocate(0,0,0); my $grey = $im->colorAllocate(172,172,172); my $red = $im->colorAllocate(255,0,0); my $blue = $im->colorAllocate(0, 0, 255); DrawActual($grey) if $opt{'a'}; DrawLogical($black) if $opt{'l'}; DrawPages($red) if $opt{'d'}; InsertPageNames($blue) if $opt{'n'}; InsertCoords($blue) if $opt{'c'}; InsertDString($blue) if $opt{'D'}; print STDERR "...done\n" if $opt{'v'}; if ($opt{'w'}) { print STDERR "Printing imagemap data..." if $opt{'v'}; PrintImageMap(); print STDERR "...done\n" if $opt{'v'}; } print STDERR "Writing out the image..." if $opt{'v'}; open(PNG, "> ".$opt{'f'}) or die "Error opening $opt{'f'}: $!"; print PNG $im->png(); close(PNG); print STDERR "...done\n" if $opt{'v'}; $dbh->disconnect(); exit; sub DetermineAngle { my $ppage = shift; my $page = shift; my $distance = shift; my $total = shift; my $cangle = shift; my $px = shift; my $py = shift; my $branch = shift; my $langle = shift; my @links = (); print STDERR $page, $/ if $opt{'v'}; foreach my $link (@{$pages{$page}}) { #our map isn't at all concerned with this link #next unless $link =~ /^$opt{'r'}/i; push @actlinks, [ $page, $link ]; #we've already followed this link next if in_array($link, $branch); #basedir of the original page my $bpage = $page; $bpage =~ s/\/[^\/]+$/\//; #basedir of the link my $blink = $link; $blink =~ s/\/[^\/]+\/?$/\//; #the base of the orig is not part of the base of the link next if ($link !~ /^$bpage/i); #the base links to this links as well as this page next if $link =~ /^$bpage/i && $bpage ne $page && link_exists($bpage, $link); #link is 2 dirs down from bpage and the dir 1 down #has a link to link next if ($link =~ /^$bpage([^\/]+\/)[^\/]+$/ && link_exists($bpage.$1, $link) ); next if $bpage ne $blink && link_exists($blink, $link); next if $link eq $bpage; next unless exists($pages{$link}); push @links, $link; } my $totalpages = $#links; $totalpages += $page eq $opt{'r'} ? 1 : 2; my $mangle = $totalpages == 0 ? 0 : $langle/$totalpages; $cangle = $langle if $page eq $opt{'r'}; $cangle %= 360; #my $mod = 50 * (1.2*$distance); #$mod = 300 if $mod > 300; my $mod = 40; if ($distance == 1 ) { $mod /= 2; } elsif ($distance == 2 ) { $mod *= 1.5; } elsif ($distance == 3 ) { $mod *= 4; } elsif ($distance == 4 ) { $mod *= 1.5; } elsif ($distance >= 25 ) { $mod /= 5; } elsif ($distance >= 16 ) { $mod /= 4; } elsif ($distance >= 8 ) { $mod /= 2; } elsif ($distance >= 6 ) { $mod /= 1.5; } my $x = int( sin(deg2rad($cangle))*$mod + $px ); my $y = int( cos(deg2rad($cangle))*$mod + $py ); $x=$px, $y=$py if $page eq $opt{'r'}; push @loglinks, [ $px, $py, $x, $y, $page, $ppage ]; $dots{$page} = [ $x, $y ]; $cangle -= $langle/2; my $tot = 0; foreach my $link (@links) { my $branch = $branch; push @$branch, $link; $cangle += $mangle; DetermineAngle($page, $link, $distance+1, $tot++, $cangle, $x, $y, $branch, $mangle); } } sub link_exists { my $page = shift; my $link = shift; return in_array($link, \@{$pages{$page}}); } sub in_array { my $link = shift; my $array = shift; foreach my $val (@$array) { return 1 if ($val eq $link); } return 0; } sub DrawActual { my $colour = shift; foreach my $node (@actlinks) { my ($from, $to) = @$node; next if $to eq '' or $from eq '' or !defined($dots{$from}) or !defined($dots{$to}); my ($fx, $fy) = @{$dots{$from}}; my ($tx, $ty) = @{$dots{$to}}; $fx += $midx; $tx += $midx; $fy += $midy; $ty += $midy; $fx *= $opt{'s'}; $tx *= $opt{'s'}; $fy *= $opt{'s'}; $ty *= $opt{'s'}; next if $fx > $width-1 or $fx < 0 or $fy > $height-1 or $fy < 0 or $tx > $width-1 or $tx < 0 or $ty > $height-1 or $ty < 0; $im->line($fx, $fy, $tx, $ty, $colour); } } sub DrawLogical { my $colour = shift; foreach my $node (@loglinks) { my ($fx, $fy, $tx, $ty, $page, $ppage) = @$node; $fx += $midx; $tx += $midx; $fy += $midy; $ty += $midy; $fx *= $opt{'s'}; $tx *= $opt{'s'}; $fy *= $opt{'s'}; $ty *= $opt{'s'}; next if $fx >= $width or $fx < 0 or $fy >= $height or $fy < 0 or $tx >= $width or $tx < 0 or $ty >= $height or $ty < 0; $im->line($fx, $fy, $tx, $ty, $colour); $im->string(gdTinyFont,$tx,$ty,$page.' '.$ppage, $colour) if $tx > 500 && $ty > 500; } } sub DrawPages { my $colour = shift; foreach my $node ( keys %dots ) { my ($x, $y) = @{$dots{$node}}; $x += $midx; $y += $midy; $x *= $opt{'s'}; $y *= $opt{'s'}; next if $x >= $width or $x < 0 or $y >= $height or $y < 0; $im->arc($x, $y, 4, 4, 0, 360, $colour); $im->fill($x, $y, $colour); } } sub InsertPageNames { my $colour = shift; foreach my $node (keys %dots) { my ($x, $y) = @{$dots{$node}}; $x += $midx; $y += $midy; $x *= $opt{'s'}; $y *= $opt{'s'}; next if $x >= $width or $x < 0 or $y >= $height or $y < 0; $im->string(gdTinyFont,$x,$y,$node, $colour); } } sub InsertCoords { my $colour = shift; foreach my $node (keys %dots) { my ($x, $y) = @{$dots{$node}}; $x += $midx; $y += $midy; $x *= $opt{'s'}; $y *= $opt{'s'}; next if $x >= $width or $x < 0 or $y >= $height or $y < 0; $im->string(gdTinyFont,$x,$y,"$x,$y", $colour); } } sub PrintImageMap { print "\n"; foreach my $node (keys %dots) { my ($x, $y) = @{$dots{$node}}; $x += $midx; $y += $midy; $x *= $opt{'s'}; $y *= $opt{'s'}; next if $x >= $width or $x < 0 or $y >= $height or $y < 0; print '\n"; } print "\n"; print 'vw'."\n"; } sub InsertDataToDB { my $sql = "UPDATE map_pages SET x=?, y=?, type=? WHERE page=? "; $sql .= " AND link=? AND map_run=".$opt{'m'}; my $sth = $dbh->prepare($sql); foreach my $node (keys %dots) { my ($x, $y) = @{$dots{$node}}; $x += $midx; $y += $midy; } my $type = 1; foreach my $link (@actlinks) { my ($from, $to) = @$link; next if $to eq '' or $from eq '' or !defined($dots{$from}) or !defined($dots{$to}); my ($x, $y) = @{$dots{$from}}; next if !defined($x) or !defined($y); $x += $midx; $y += $midy; my $r = $sth->execute($x, $y, $type, $from, $to) or die $sth->errstr; } $type = 2; foreach my $link (@loglinks) { my ($x, $y, $n, $m, $to, $from) = @$link; $x += $midx; $y += $midy; my $r = $sth->execute($x, $y, $type, $from, $to) or die $sth->errstr; } } sub InsertDString { my $colour = shift; my $date = $dbh->selectrow_array("SELECT date FROM map_run WHERE map_run_id=".$opt{'m'}); my $string = $host - $date "; $im->string(gdSmallFont, $midx+100, $midy+($hiy*$opt{'s'})+int($immarginb/2), $string, $blue); }