#!/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";
print '
'."\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);
}