<?xml version="1.0" encoding="windows-1252"?>
<node id="89635" title="drawmap.pl" created="2001-06-19 14:10:25" updated="2005-08-15 08:43:55">
<type id="1748">
sourcecode</type>
<author id="16834">
OeufMayo</author>
<data>
<field name="doctext">
&lt;code&gt;
#!/usr/bin/perl -w
#
# drawmap - spot the monk!
# Briac 'OeufMayo' Pilpré
# 2001/06/15
# munged by Max 'Corion' Maischein
# 2001/06/17

# Great earth maps available from http://apollo.spaceports.com/~jhasting/earth.html

# A jcwren's monkmap compliant map can be found at
# http://www.pilpre.com/briac/small_earth.jpg

# jcwren's monks.xml file available at:
# http://www.tinymicros.com/pm/monks.xml

#drawmap.pl -i=northam10k.jpg  -o=monkmap_northam.jpg -x=-1280 -y=-896 -m ./monks.xml -d cross.png -w 10800 -h 5400
#drawmap.pl -i=europe10k.jpg   -o=monkmap_europe.jpg  -x=-4880 -y=-695 -m ./monks.xml -d cross.png -w 10800 -h 5400 -H monkmap_europe.html
#drawmap.pl -i=small_earth.jpg -o=monkmap_world.jpg   -x=-25   -y=-3   -m ./monks.xml -d cross.png -C

use strict;
use lib 'lib/';
use vars qw($VERSION); $VERSION = 0.04;
use XML::Simple;
use Getopt::Mixed 'nextOption';
use GD;

Getopt::Mixed::init('C:i i=s o=s d=s w=i h=i x:i y:i m=s H=s xml&gt;m dot&gt;d width&gt;w height&gt;h input&gt;i output&gt;o offsetx&gt;x offsety&gt;y nocaption&gt;C html&gt;H');

# Fetch the command line parameters
my ($input, $output, $offsetx, $offsety, $dot, $xml, $width, $height, $nocaption, $html);
while (my ($option, $value, $pretty) = nextOption()) {
	$input   = $value if $option eq 'i';
	$output  = $value if $option eq 'o';
	$offsetx = $value if $option eq 'x';
	$offsety = $value if $option eq 'y';
	$xml	 = $value if $option eq 'm';
	$dot	 = $value if $option eq 'd';
	$width	 = $value if $option eq 'w';
	$height	 = $value if $option eq 'h';
	$nocaption = 1 if $option eq 'C';
	if ($option eq 'H') {
		$html		= $value;
		$nocaption	= 0;
	};
}
Getopt::Mixed::cleanup();
&amp;usage unless ($input &amp;&amp; $output);

$offsetx ||= 0;
$offsety ||= 0;

my (%monks, %href);

# Parse the monks coordinates XML file I fetched from jcwren's stats site.
# ( code to fetch &amp; create the XML is available on request )
my $xs  =  new XML::Simple();
my $ref = $xs-&gt;XMLin($xml);

# Fill the monks hash with their respective locations
foreach (keys %{$ref-&gt;{monk}}){
	push (@{$monks{$_}}, (
				$ref-&gt;{monk}-&gt;{$_}-&gt;{location}-&gt;{latitude},
				$ref-&gt;{monk}-&gt;{$_}-&gt;{location}-&gt;{longitude},
	));
}

# Load the pictures we need.
my $map    = GD::Image-&gt;newFromJpeg($input);
my $flag   = GD::Image-&gt;newFromPng($dot);
my $white  = $map-&gt;colorResolve(255,255,255);
my $black  = $map-&gt;colorResolve(0,0,0);

unless ($width &amp;&amp; $height){
	($width, $height) = $map-&gt;getBounds();
}

my %points;	#keep track of the points for managing monks density
my %boxes;	#keep track of the caption bounding boxes

# First, lets filter out all monks not on the map :
my ($img_width,$img_height) = $map-&gt;getBounds();
my @monks_off_map = grep {
			  my ($x,$y) = coord2pix($monks{$_}-&gt;[0],$monks{$_}-&gt;[1],
						 $width, $height);

			  # Tweak the x/y to fit the picture
			  $x += $offsetx;
			  $y += $offsety;

			  # We want only the off-map monks :
			     ! (between(0,$width-$offsetx, $x) &amp;&amp; between(0,$height-$offsety,$y))
			  || ! (between(0,$img_width, $x) &amp;&amp; between(0,$img_height,$y));
			} keys %monks;

foreach (@monks_off_map) {
  delete $monks{$_};
};

# Now, we want to place all position markers :
my $f = 6;	# "closeness" factor

foreach (keys %monks){

	# Convert the lat/long to x/y
	my ($x,$y) = coord2pix($monks{$_}-&gt;[0],$monks{$_}-&gt;[1],
							 $width, $height);

	# Tweak the x/y to fit the picture
	$x += $offsetx;
	$y += $offsety;

	$points{$_}    = [$x-$f, $y-$f, $x+$f, $y+$f]; # store the current pos
	$boxes{"__$_"} = [$x-$f, $y-$f, $x+$f, $y+$f]; # store the current pos of the bbox

	# Pinpoints the monk location on the map
	$map-&gt;copy($flag, $x, $y, 0,0,7,7);
};

foreach (keys %monks){

	# Convert the lat/long to x/y
	my ($x,$y) = coord2pix($monks{$_}-&gt;[0],$monks{$_}-&gt;[1],
							 $width, $height);
	# Tweak the x/y to fit the picture
	$x += $offsetx;
	$y += $offsety;

	# Let's find if we have a monk close to the current one
	unless ($nocaption){
		my ($x1,$y1);
		my ($radius, $angle) = (10,0);
		my $textl = 7 * length($_);	#length of the caption

		# Create a box for the label
		my @box = (int($x-$textl/2), $y-17, int($x+$textl/2), $y-18+13);

		if (find_density(\%points, $_, $x,$y) ||      # If true the monk is too close
		    find_intersect(\%boxes, $_, @box)	 # or the place has been taken already
		   )
		{
		  CLOSE :{
		    $radius += 5;
		    $angle  += 10 % 360;

		    # Find a point on a circle.
		    # provided by CheeseLord: (x+r cos a, y+r sin a)
		    ($x1,$y1)=(int($x + ($radius * cos $angle)), int($y+($radius * sin $angle)));

		    # Move the label
		    @box = (int($x1-$textl/2), $y1, int($x1+$textl/2), $y1+13);

		    # Check to see if it intersects with a previous caption
		    redo CLOSE if find_intersect(\%boxes, $_, @box);

		    $map-&gt;line($x+4, $y+4, $x1+4, $y1+4, $white);
		    $map-&gt;string(gdMediumBoldFont, $x1 - $textl/2 + 2, $y1, $_, $black);
		    $map-&gt;string(gdMediumBoldFont, $x1 - $textl/2 + 3, $y1, $_, $white);
		  }
		}
		else {
		    $map-&gt;string(gdMediumBoldFont, int($x - $textl/2)+1, $y-17, $_, $black);
		    $map-&gt;string(gdMediumBoldFont, int($x - $textl/2),   $y-18, $_, $white);
		}
		$boxes{$_} = [@box];
	}

}

# We now save our masterpiece on a storage device
open	JPGOUT, "&gt;  $output" or die $!;
binmode JPGOUT;
print	JPGOUT $map-&gt;jpeg(75);

if ($html){
	open HTML, "&gt; $html" or die $!;
	print HTML &lt;&lt;"__HTML__";
	&lt;html&gt;
		&lt;head&gt;
			&lt;title&gt;drawmap - $output&lt;/title&gt;
		&lt;/head&gt;
		&lt;body&gt;
		&lt;h1&gt;drawmap - $output&lt;/h1&gt;
		&lt;p&gt;&lt;img border="0" src="$output" usemap="#drawmap" alt="$output" /&gt;&lt;/p&gt;
		&lt;map name="drawmap"&gt;
__HTML__
	foreach (keys %boxes){
	print HTML qq'\t\t&lt;area ';
	print HTML 'coords="', join( ',', @{$boxes{$_}} ), '" ';
	s/^__//;
	print HTML qq'href="http://www.perlmonks.org/index.pl?node=$_" shape="rect" alt="$_" /&gt;\n';
	}
	print HTML '&lt;/map&gt;\n&lt;/body&gt;\n&lt;/html&gt;';
}

sub between {
  my ($a1,$a2,$b) = @_;
  return ($a1 &lt;= $b) &amp;&amp; ($b &lt;= $a2);
};

sub point_in_rectangle {
  my ($left,$top,$right,$bottom,$x,$y) = @_;
  return between($left,$right,$x) &amp;&amp; between($top,$bottom,$y)
};

sub rectangles_intersect {
  my ($a_lft,$a_top,$a_rgt,$a_btm,$b_lft,$b_top,$b_rgt,$b_btm) = @_;
  return (
			# One of the four corners within the other rectangle
			point_in_rectangle($a_lft,$a_top,$a_rgt,$a_btm,$b_lft,$b_top) ||
			point_in_rectangle($a_lft,$a_top,$a_rgt,$a_btm,$b_rgt,$b_top) ||
			point_in_rectangle($a_lft,$a_top,$a_rgt,$a_btm,$b_lft,$b_btm) ||
			point_in_rectangle($a_lft,$a_top,$a_rgt,$a_btm,$b_rgt,$b_btm) ||
			point_in_rectangle($b_lft,$b_top,$b_rgt,$b_btm,$a_lft,$a_top) ||
			point_in_rectangle($b_lft,$b_top,$b_rgt,$b_btm,$a_rgt,$a_top) ||
			point_in_rectangle($b_lft,$b_top,$b_rgt,$b_btm,$a_lft,$a_btm) ||
			point_in_rectangle($b_lft,$b_top,$b_rgt,$b_btm,$a_rgt,$a_btm) ||
			# Or an intersection where no corner is within the other rectangle
			(
			    between( $a_lft, $a_rgt, $b_lft )
			 &amp;&amp; between( $a_lft, $a_rgt, $b_rgt )
			 &amp;&amp; between( $b_top, $b_btm, $a_top )
			 &amp;&amp; between( $b_top, $b_btm, $a_btm )
			) ||
			(
			    between( $b_lft, $b_rgt, $a_lft )
			 &amp;&amp; between( $b_lft, $b_rgt, $a_rgt )
			 &amp;&amp; between( $a_top, $a_btm, $b_top )
			 &amp;&amp; between( $a_top, $a_btm, $b_btm )
			)
		);
};

sub find_intersect {
	my $boxes   = shift;
	my $current = shift;
	my ($a_lft,$a_top, $a_rgt, $a_btm) = @_;

	my $overlap;

	foreach (keys %{$boxes}){
		next if $_ eq $current;
		next if $_ eq "__$current";		 # The own location marker is never "too close"

		my ($b_lft,$b_top, $b_rgt, $b_btm) = @{$boxes-&gt;{$_}};

		# Collison tests provided by Corion. I probably left some out.
		if (rectangles_intersect($a_lft,$a_top,$a_rgt,$a_btm,$b_lft,$b_top,$b_rgt,$b_btm)){
			$overlap++;
			last;
		}
	}
	return $overlap;
}

sub find_density {
	my $dens    = shift;
	my $current = shift;
	my ($x,$y)  = @_;

	my $too_close;

	foreach (keys %{$dens}){
		next if $_ eq $current;
		my ($x1,$y1,$x2,$y2) = @{$dens-&gt;{$_}};
		if (point_in_rectangle($x1,$y1,$x2,$y2, $x,$y)){
			$too_close++;
			last;
		}
	}
	return $too_close;
}

sub coord2pix {
	# Convert the lat/long to their actual coordinates in the
	# picture (thanks to jcwren for the tips!)
	my ($lat, $long, $width, $height) = @_;
	my $x =  $width / 2 + ($long / 360 * $width);
	my $y = $height / 2 - ($lat  / 180 * $height);
	return ( int $x, int $y );
}

sub usage {
	print STDERR &lt;&lt;"__USAGE__";
drawmap - v.$VERSION
 perl drawmap.pl -i inputfile.jpg -o outputfile.jpg -m ./locat.xml -d dot.png

 Required arguments:
 -i --input   : Name of the map base.
 -o --output  : Name of the output file created by drawmap

 -d --dot     : Location of the png used as location marker
 -m --xml     : Location of the xml coordinates file

 Optional arguments:
 -x --offsetx : Offset of the x axis
 -y --offsety : Offset of the y axis

 -C --nocaption : Does not draw the caption above the marker
 -H --html    : Generate a HTML with an IMAGEMAP (cancels the -C) 
                requires the name of the html file to create

 -w --width   : Width of the original whole earth map (useful when zooming)
 -h --height  : Height of the original whole earth map
__USAGE__

	 die "\n";
}

__DATA__
&lt;!-- monks.xml DTD --&gt;
&lt;!ELEMENT monkmap (monk+)&gt;
	&lt;!ATTLIST monk source CDATA #REQUIRED&gt;
&lt;!ELEMENT monk (name, location)&gt;
	&lt;!ATTLIST monk id CDATA #REQUIRED&gt;
&lt;!ELEMENT name (#PCDATA)&gt;
&lt;!ELEMENT location (latitude, longitude)&gt;
	&lt;!ELEMENT latitude (#PCDATA)&gt;
	&lt;!ELEMENT longitude (#PCDATA)&gt;
&lt;/code&gt;</field>
<field name="codedescription">
see node [id://88780] - The code is posted here just for ease of use and update. (no need to vote on this, got enough XP with the first one!) &lt;!-- Allright, ++ it if you really want to spend your votes, I won't mmind too much --&gt;</field>
<field name="codecategory">
Perlmonks.org Related Scripts</field>
<field name="codeauthor">
Briac Pilpr&amp;eacute;</field>
</data>
</node>
