#!/usr/bin/perl -w #======================================================================== # This is the grand final wonderful super-duper map-drawing application # for L579, Project 4 (Spring 2005). It implements a simple scripting # language that describes how to draw the final visualization, thus # allowing the hapless developers to maintain the script rather than # this application. # # The scripting language is marginally documented below: # # ARC # BOX # COLOR # DRAW_LABEL # DRAW_LOC # DRAW_PATH # FONT # GCIRCLE # LABEL # LINE # PALETTE # PATH ... # PEN # PLOT # RECT # REGION # SET # TEXT # ZIP # # Written by Mark Meiss and Heather Roinestad, 2005-04-11. # Last modified: 2005-04-25. #======================================================================== use GD; use strict; #------------------------------------------------------------------------ # Global variables. #------------------------------------------------------------------------ my %map_color = (); # key: color name, value: GD color my %map_edge = (); # key: origin + destination, value: hash with 'tally' and 'when' keys my %map_font = (); # key: font name, value: path to TTF file my @list_label = (); # key: , value: label informaton (see &command_label) my %map_node = (); # key: location, value: hash with 'tally' and 'when' keys my %map_palette = (); # key: year, value: GD color my %map_pen = (); # key: (pen attributes), value: (various) my %map_region = (); # key: region name, value: region boundary data my %map_var = (); # key: variable name, value: variable value my %map_zip = (); # key: zip code, value: location my %command_table = ( ARC => \&command_arc, BOX => \&command_box, COLOR => \&command_color, DRAW_LABEL => \&command_draw_label, DRAW_LOC => \&command_draw_loc, DRAW_PATH => \&command_draw_path, FONT => \&command_font, GCIRCLE => \&command_gcircle, LABEL => \&command_label, LINE => \&command_line, PALETTE => \&command_palette, PATH => \&command_path, PEN => \&command_pen, PLOT => \&command_plot, RECT => \&command_rect, REGION => \&command_region, SET => \&command_set, TEXT => \&command_text, ZIP => \&command_zip ); my $PI = 3.14159265358979323846; my ($path_map, $size_x, $size_y, $path_img, $image, $line, $command, @args); my ($page_x, $page_y, %bound); #==================================================================================================== # MAIN PROGRAM. #==================================================================================================== # present usage information if necessary die "Usage: $0 [path to .map file] [x-size] [y-size] [x-page] [y-page] [target path]\n" unless @ARGV == 6; ($path_map, $size_x, $size_y, $page_x, $page_y, $path_img) = @ARGV; # figure out the x-bounds of the page being rendered if ($size_x =~ /(\d+):(\d+)/) { my ($this_x, $total_x) = ($1, $2); $bound{x1} = $this_x * $page_x; $bound{x2} = &min($this_x * ($page_x + 1), $total_x) - 1; $size_x = $total_x; } else { $bound{x1} = 0; $bound{x2} = $size_x - 1; } # figure out the y-bounds of the page being rendered if ($size_y =~ /(\d+):(\d+)/) { my ($this_y, $total_y) = ($1, $2); $bound{y1} = $this_y * $page_y; $bound{y2} = &min($this_y * ($page_y + 1), $total_y) - 1; $size_y = $total_y; } else { $bound{y1} = 0; $bound{y2} = $size_y - 1; } # create the image object GD::Image->trueColor(1); $image = new GD::Image($bound{x2} - $bound{x1} + 1, $bound{y2} - $bound{y1} + 1); # open the script file open IN, $path_map; # for each line in the script while (defined ($line = )) { # skip blank lines and comments chomp $line; $line =~ s/^\s+//o; $line =~ s/\s+$//o; next if ($line eq '') || ($line =~ /^#/); # parse the line into its parts ($command, @args) = split /\s+/, $line; $command = uc $command; # dispatch on the command die "Unknown command: $command\n" unless defined $command_table{$command}; &{$command_table{$command}}(@args); } # close the script file close IN; # write out the image open OUT, "> $path_img"; binmode OUT; print OUT $image->png(); close OUT; #==================================================================================================== # COMMAND ROUTINES. #==================================================================================================== #------------------------------------------------------------------------ # ARC # # Draws an arc in the image in pretty much the way you'd expect. #------------------------------------------------------------------------ sub command_arc { # get a hold of my parameters my ($xc, $yc, $width, $height, $start, $end) = @_; # draw the arc $image->arc(&ix(&var($xc)), &iy(&var($yc)), &ix(&var($width)), &iy(&var($height)), &var($start), &var($end), &set_pen()); } #------------------------------------------------------------------------ # BOX # # Command to draw a (hollow) box with corners at (x1, y1) and (x2, y2). #------------------------------------------------------------------------ sub command_box { # get a hold of my parameters my ($x1, $y1, $x2, $y2) = @_; # draw the rectangle $image->rectangle(&ix(&var($x1)), &iy(&var($y1)), &ix(&var($x2)), &iy(&var($y2)), &set_pen()); } #------------------------------------------------------------------------ # COLOR # # Command to define a new color and associate it with a name. #------------------------------------------------------------------------ sub command_color { # get a hold of my parameters my ($name, $r, $g, $b) = @_; # create the color $map_color{$name} = $image->colorAllocate(&var($r), &var($g), &var($b)); } #------------------------------------------------------------------------ # DRAW_LABEL # # Draws the registered labels on the map, using the given font, size, # and color. The size is specified in proportion to the drawing as a # whole. #------------------------------------------------------------------------ sub command_draw_label { # get a hold of my parameters my ($font, $size, $color) = @_; # declare local variables my ($label, $point, @bounds, $x, $y, $label_x, $label_y); # look up the attributes $font = $map_font{$font}; $color = $map_color{$color}; $point = $size_y * &var($size); # for each label foreach $label (@list_label) { # figure out the bounds of the text @bounds = GD::Image->stringFT($color, $font, $point, 0, 0, 0, $label->{text}); $label_x = (&max($bounds[2], $bounds[4]) - &min($bounds[0], $bounds[6])) / $size_x; $label_y = (&max($bounds[1], $bounds[3]) - &min($bounds[5], $bounds[7])) / $size_y; # find the projection of the location ($x, $y) = &project($map_region{$label->{region}}, $label->{lon}, $label->{lat}); if ($label->{position} eq 'UL') { $x = $x - $label_x - 0.001; $y = $y - 0.001; } elsif ($label->{position} eq 'UR') { $x = $x + 0.001; $y = $y - 0.001; } elsif ($label->{position} eq 'DL') { $x = $x - $label_x - 0.001; $y = $y + $label_y + 0.001; } elsif ($label->{position} eq 'DR') { $x = $x + 0.001; $y = $y + $label_y + 0.001; } # draw the label $image->stringFT($color, $font, $point, 0, &ix($x), &iy($y), $label->{text}); } } #------------------------------------------------------------------------ # DRAW_LOC # # Draws the locations (nodes) on the map. #------------------------------------------------------------------------ sub command_draw_loc { # get a hold of my parameters my ($color_in, $color_out, $min_size, $max_size) = @_; # declare local variables my ($max, $region, $node, $size, $lon, $lat, $x, $y, @list, $count); # for each region in the map as a whole $count = 0; foreach $region (keys %map_region) { # find the maximum number of visits @list = sort { $b <=> $a } map { $map_node{$_}->{tally}->{$region} || 0 } keys %map_node; $max = $list[1]; # for each node to draw foreach $node (keys %map_node) { # skip this node if there's no data next unless defined $map_node{$node}->{tally}->{$region}; # figure out the size $size = $min_size + ($map_node{$node}->{tally}->{$region} * ($max_size - $min_size) / $max); $size = $max_size if $size > $max_size; # figure out the coordinates ($lon, $lat) = split /\s+/, $node; ($x, $y) = &project($map_region{$region}, $lon, $lat); # and draw $image->setThickness(1); $image->filledEllipse(&ix($x), &iy($y), $size, $size, $map_color{$color_in}); $image->ellipse(&ix($x), &iy($y), $size, $size, $map_color{$color_out}); ++$count; } } # report on what we did print "Final tally: $count nodes drawn.\n"; } #------------------------------------------------------------------------ # DRAW_PATH # # Draws the trips (edges) on the map. The given color is used only if # there are no palette entries for the year of a trip. #------------------------------------------------------------------------ sub command_draw_path { # get a hold of my parameters my ($color, $min_size, $max_size) = @_; # declare local variables my ($region, $max, $lon1, $lat1, $lon2, $lat2, $x1, $y1, $x2, $y2, $size, $edge); my (@list, $i, $track, @work, $item, $width, $color2, $count); # for each region in the map as a whole $count = 0; foreach $region (keys %map_region) { # find the maximum number of trips @list = sort { $b <=> $a } map { $map_edge{$_}->{tally}->{$region} || 0 } keys %map_edge; $max = $list[1]; # for each edge to draw foreach $edge (keys %map_edge) { # skip this edge if there's no data #print "$region [$max] $edge\n"; next unless defined $map_edge{$edge}->{tally}->{$region}; # figure out the size $size = $min_size + ($map_edge{$edge}->{tally}->{$region} * ($max_size - $min_size) / $max); $size = $max_size if $size > $max_size; # for each track @list = sort { $a <=> $b } @{$map_edge{$edge}->{when}->{$region}}; for ($i = 0; $i < @list; ++$i) { # figure out the coordinates and store them ($lon1, $lat1, $lon2, $lat2) = split /\s+/, $edge; $track = $i - ((@list - 1) / 2.0); $width = &max(int(10 - 2 * @list), 3); $color2 = (defined $map_palette{$list[$i]}) ? $map_palette{$list[$i]} : $map_color{$color}; push @work, [ $list[$i], $region, $lon1, $lat1, $lon2, $lat2, $track, $width, $color2 ]; } } # sort the edges by year @work = sort { $a->[0] <=> $b->[0] } @work; # and actually draw them foreach $edge (@work) { $map_pen{width} = $edge->[7]; $map_pen{color} = $edge->[8]; &command_gcircle($edge->[1], $edge->[2], $edge->[3], $edge->[4], $edge->[5], $edge->[6]); ++$count; } } # report on what we did print "Final tally: $count edges drawn.\n"; } #------------------------------------------------------------------------ # FONT # # Associates the given name with the given font, which is specified # as a path to a True Type font file. #------------------------------------------------------------------------ sub command_font { # get a hold of my parameters my ($name, @rest) = @_; # declare local variables my ($dir); # store the path chomp($dir = `pwd`); $map_font{$name} = $dir . '/' . join(' ', @rest); } #------------------------------------------------------------------------ # GCIRCLE # # Draws a great circle route in the given region, extending from # (lon1, lat1) to (lon2, lat2). [track] specifies a positive or # negative maximam displacement in radians from the great circle; it # is used to produce paths with the same endpoints but with slightly # different courses. #------------------------------------------------------------------------ sub command_gcircle { # get a hold of my parameters my ($region, $lon1, $lat1, $lon2, $lat2, $track) = @_; # declare local variables my ($rlon1, $rlat1, $rlon2, $rlat2, $rdist, $rcourse, $d, $x_scale, $y_scale, $scale); my ($plon1, $plat1, $plon2, $plat2, $x1, $y1, $x2, $y2, $factor); # convert the endpoints to radians $rlon1 = °_to_rad(&var($lon1)); $rlat1 = °_to_rad(&var($lat1)); $rlon2 = °_to_rad(&var($lon2)); $rlat2 = °_to_rad(&var($lat2)); # figure out the distance in radians and the true course $rdist = &distance($rlon1, $rlat1, $rlon2, $rlat2); $rcourse = &course ($rlon1, $rlat1, $rlon2, $rlat2, $rdist); # figure out the degree increment $region = $map_region{$region}; $x_scale = °_to_rad(abs($region->{lon2} - $region->{lon1})) / (abs($region->{x2} - $region->{x1}) * $size_x); $y_scale = °_to_rad(abs($region->{lat2} - $region->{lat1})) / (abs($region->{y2} - $region->{y1}) * $size_y); $scale = 6 * (($x_scale > $y_scale) ? $x_scale : $y_scale); # initialize the last-point variables $plon1 = $rlon1; $plat1 = $rlat1; # plot the course for ($d = 0; $d <= $rdist; $d += $scale) { # figure out the ending point for this segment ($plon2, $plat2) = &destination($rlon1, $rlat1, $rcourse, $d); # move a little bit perpendicular to it $factor = 1.0 - 4.0 * (abs(($d / $rdist) - 0.5) ** 2); ($plon2, $plat2) = &destination($plon2, $plat2, $rcourse + ($PI / 2), $factor * $track * $scale); # figure out the endpoints in pixels ($x1, $y1) = &project($region, &rad_to_deg($plon1), &rad_to_deg($plat1)); ($x2, $y2) = &project($region, &rad_to_deg($plon2), &rad_to_deg($plat2)); # draw the segment $image->line(&ix($x1), &iy($y1), &ix($x2), &iy($y2), &set_pen()); # update the last point $plon1 = $plon2; $plat1 = $plat2; } } #------------------------------------------------------------------------ # LABEL # # Registers a label for the given geographic point in a map region. # [position] is one of "UL", "UR", "DL", "DR", depending on where you # want the label to appear. #------------------------------------------------------------------------ sub command_label { # get a hold of my parameters my ($region, $loc, $position, @rest) = @_; # declare local variables my ($lat, $lon); # store the label information ($lon, $lat) = &expand_location($loc); push @list_label, { region => $region, lon => $lon, lat => $lat, position => uc($position), text => join(' ', @rest) }; } #------------------------------------------------------------------------ # LINE # # Command to draw a line from (x1, y1) to (x2, y2). #------------------------------------------------------------------------ sub command_line { # get a hold of my parameters my ($x1, $y1, $x2, $y2) = @_; # draw the line $image->line(&ix(&var($x1)), &iy(&var($y1)), &ix(&var($x2)), &iy(&var($y2)), &set_pen()); } #------------------------------------------------------------------------ # PALETTE # # Associates the given color with the given year; this information # will be used in drawing locations and paths. #------------------------------------------------------------------------ sub command_palette { # get a hold of my parameters my ($year, $color) = @_; # store the mapping $map_palette{$year} = $map_color{$color}; } #------------------------------------------------------------------------ # PATH ... # # Logs a travel path defined by the given points. This is the main # command for accumulating data for trip itineraries. #------------------------------------------------------------------------ sub command_path { # get a hold of my parameters my ($start, $end, $region, @points) = @_; # declare local variables my ($point, @list, $i, $tag, $year1, $year2, $lon, $lat); # figure out the year associated with the trip (start of trip gets priority for now) ($year1) = $start =~ /^(\d{4})/; ($year2) = $end =~ /^(\d{4})/; $year1 = $year2; # map each point to lat/lon foreach $point (@points) { ($lon, $lat) = &expand_location($point); push @list, ((defined $lon) ? [ $lon, $lat ] : undef); } # for each edge in the path for ($i = -1; $i < (@list - 1); ++$i) { # skip edges with an undefined origin or destination next unless defined $list[$i] && defined $list[$i+1]; # figure out the tag for the edge (origin location + destination location) $tag = "$list[$i]->[0] $list[$i]->[1] $list[$i+1]->[0] $list[$i+1]->[1]"; # add to the tally and log the time $map_edge{$tag}->{tally}->{$region}++; push @{$map_edge{$tag}->{when}->{$region}}, $year1; } # for each node in the path foreach $point (@list) { # skip nodes with an undefined location next unless defined $point; # figure out the tag for the node (location) $tag = "$point->[0] $point->[1]"; # add to the tally and log the time $map_node{$tag}->{tally}->{$region}++; push @{$map_node{$tag}->{when}->{$region}}, $year1; } } #------------------------------------------------------------------------ # PEN # # Sets the current drawing state to use the given color, width, and # smoothing (anti-aliasing) setting. #------------------------------------------------------------------------ sub command_pen { # get a hold of my parameters my ($color, $width, $smooth) = @_; # set the pen state $map_pen{color} = $map_color{$color}; $map_pen{width} = $width; $map_pen{smooth} = ($smooth =~ /^yes$/i) ? 1 : 0; } #------------------------------------------------------------------------ # PLOT # # Plots the polygons in the given file in the given region, using the # default projection. #------------------------------------------------------------------------ sub command_plot { # get a hold of my parameters my ($region, $file) = @_; # declare local variables my ($color, $line, $state, $lat, $lon, $last_x, $last_y, $x, $y); # look up the region and set our drawing parameters $region = $map_region{$region}; $color = &set_pen(); # set the clipping region $image->clip(&ix($region->{x1}), &iy($region->{y1}), &ix($region->{x2}), &iy($region->{y2})); # open the polygon file open POLY, $file; $state = &STATE_READY; # process the polygons while (defined ($line = )) { # trim the line chomp $line; $line =~ s/^\s+//o; $line =~ s/\s+$//o; # the ready state: start a new polyogn if ($state == &STATE_READY) { last if $line eq 'END'; $state = &STATE_FIRST; # the first-point state: just project and store the point } elsif ($state == &STATE_FIRST) { ($lat, $lon) = split /\s+/, $line; ($last_x, $last_y) = &project($region, $lat, $lon); $state = &STATE_ACTIVE; # the active state: draw a new line in the polygon } else { if ($line eq 'END') { $state = &STATE_READY; } else { ($lat, $lon) = split /\s+/, $line; ($x, $y) = &project($region, $lat, $lon); $image->line(&ix($last_x), &iy($last_y), &ix($x), &iy($y), $color); $last_x = $x; $last_y = $y; } } } # close the polygon file close POLY; # remove the clipping region $image->clip(0, 0, $bound{x2} - $bound{x1}, $bound{y2} - $bound{y1}); } sub STATE_READY { 0 } sub STATE_FIRST { 1 } sub STATE_ACTIVE { 2 } #------------------------------------------------------------------------ # RECT # # Command to draw a solid rectangle with (x1, y1) at the upper left # and (x2, y2) at the lower right. #------------------------------------------------------------------------ sub command_rect { # get a hold of my parameters my ($x1, $y1, $x2, $y2) = @_; # draw the rectangle $image->filledRectangle(&ix(&var($x1)), &iy(&var($y1)), &ix(&var($x2)), &iy(&var($y2)), &set_pen()); } #------------------------------------------------------------------------ # REGION # # Command to specify a mapping region with boundary corners (x1, y1) # and (x2, y2) in the virtual world and (lon1, lat1), (lon2, lat2) in # the physical world. #------------------------------------------------------------------------ sub command_region { # get a hold of my parameters my ($name, $x1, $y1, $x2, $y2, $lon1, $lat1, $lon2, $lat2) = @_; # store the region $map_region{$name} = { x1 => &var($x1), y1 => &var($y1), x2 => &var($x2), y2 => &var($y2), lon1 => &var($lon1), lat1 => &var($lat1), lon2 => &var($lon2), lat2 => &var($lat2) }; } #------------------------------------------------------------------------ # SET # # Command to associate a name with a numeric value. #------------------------------------------------------------------------ sub command_set { # get a hold of my parameters my ($name, $value) = @_; # store the value $map_var{$name} = $value; } #------------------------------------------------------------------------ # TEXT # # Draws a string at the given location, using the given size, which is # expressed as a proportion of the image size rather than an absolute # value in points. #------------------------------------------------------------------------ sub command_text { # get a hold of my parameters my ($x, $y, $font, $size, @parts) = @_; # declare local variables my ($string, $point); # figure out the string and point size $string = join ' ', @parts; $point = $size_y * &var($size); # draw the string $image->stringFT($map_pen{color}, $map_font{$font}, $point, 0, &ix(&var($x)), &iy(&var($y)), $string); } #------------------------------------------------------------------------ # ZIP # # Command to load ZIP location data from the given file. #------------------------------------------------------------------------ sub command_zip { # get a hold of my parameters my ($path) = @_; # declare local variables my ($line, $zip, $lat, $lon); # read in the data open ZIP, $path; while (defined ($line = )) { ($zip, $lon, $lat) = split /\s+/, $line; $map_zip{$zip} = [ $lon, $lat ]; } } #==================================================================================================== # LIBRARY ROUTINES. #==================================================================================================== #------------------------------------------------------------------------ # double acos(x); # # Returns arccos(x) in radians. #------------------------------------------------------------------------ sub acos { # get a hold of my parameters my ($x) = @_; # calculate the value return atan2(sqrt(1.0 - ($x * $x)), $x); } #------------------------------------------------------------------------ # double asin(x); # # Returns arcsin(x) in radians. #------------------------------------------------------------------------ sub asin { # get a hold of my parameters my ($x) = @_; # calculate the value return atan2($x, sqrt(1.0 - ($x * $x))); } #------------------------------------------------------------------------ # double course(rlon1, rlat1, rlon2, rlat2, rdist); # # Given two spherical points specified as longitude/latitude pairs in # radians, and the spherical distance in radians between them, returns # the true course from the first points to the second. #------------------------------------------------------------------------ sub course { # get a hold of my parameters my ($rlon1, $rlat1, $rlon2, $rlat2, $rdist) = @_; # declare local variables my ($temp); # calculate the value $temp = &acos((sin($rlat2) - sin($rlat1) * cos($rdist)) / (sin($rdist) * cos($rlat1))); return (sin($rlon2 - $rlon1) < 0) ? $temp : (2 * $PI - $temp); } #------------------------------------------------------------------------ # double deg_to_rad(deg); # # Converts a value from degrees to radians. #------------------------------------------------------------------------ sub deg_to_rad { # get a hold of my parameters my ($deg) = @_; # return the value in degrees return ($deg * ($PI / 180.0)); } #------------------------------------------------------------------------ # (double, double) destination(rlon, rlat, bearing, rdist); # # Given a longitude/latitude pair expressed in radians, a bearing in # radians, and a distance in radians, returns the destination point # as a longitude/latitude pair in radians. #------------------------------------------------------------------------ sub destination { # get a hold of my parameters my ($rlon, $rlat, $bearing, $rdist) = @_; # declare local variables my ($dlon, $dlat); # do the calculation $dlat = &asin(sin($rlat) * cos($rdist) + cos($rlat) * sin($rdist) * cos($bearing)); $dlon = $rlon - atan2(sin($bearing) * sin($rdist) * cos($rlat), cos($rdist) - sin($rlat) * sin($dlat)); $dlon += (2 * $PI) if $dlon < -$PI; $dlon -= (2 * $PI) if $dlon > $PI; # all done! return ($dlon, $dlat); } #------------------------------------------------------------------------ # double distance(rlon1, rlat1, rlon2, rlat2); # # Given two spherical points specified as longitude/latitude pairs in # radians, returns the spherical distance between them, also in # radians. #------------------------------------------------------------------------ sub distance { # get a hold of my parameters my ($rlon1, $rlat1, $rlon2, $rlat2) = @_; # declare local variables my ($term1, $term2); # do the calculation $term1 = sin(($rlat1 - $rlat2) / 2) ** 2; $term2 = cos($rlat1) * cos($rlat2) * (sin(($rlon1 - $rlon2) / 2) ** 2); return (2 * &asin(sqrt($term1 + $term2))); } #------------------------------------------------------------------------ # (double, double) expand_location(loc); # # Expands the location specifier (a string such as "z:221312", "x", # "l:34.2:-33.22", ...) into a longitude and latitude pair, which is # then returned. #------------------------------------------------------------------------ sub expand_location { # get a hold of my parameters my ($loc) = @_; # map each point to lat/lon if ($loc =~ /z:(\d+)/) { die "Unknown ZIP code: $1\n" unless defined $map_zip{$1}; return @{$map_zip{$1}}; } elsif ($loc =~ /l:([^:]+):([^:]+)/) { return ($1, $2); } elsif ($loc eq 'x') { return undef; } } #------------------------------------------------------------------------ # double ix(x); # # Converts the x-value between 0 and 1 to an actual image coordinate. #------------------------------------------------------------------------ sub ix { # get a hold of my parameters my ($x) = @_; # return the coordinate return ($x * $size_x) - $bound{x1}; } #------------------------------------------------------------------------ # double iy(y); # # Converts the y-value between 0 and 1 to an actual image coordinate. #------------------------------------------------------------------------ sub iy { # get a hold of my parameters my ($y) = @_; # return the coordinate return ($y * $size_y) - $bound{y1}; } #------------------------------------------------------------------------ # double max(a, b); # # Returns the maximum of a and b. #------------------------------------------------------------------------ sub max { return ($_[0] > $_[1]) ? $_[0] : $_[1]; } #------------------------------------------------------------------------ # double min(a, b); # # Returns the minimum of a and b. #------------------------------------------------------------------------ sub min { return ($_[0] < $_[1]) ? $_[0] : $_[1]; } #------------------------------------------------------------------------ # (double, double) project(region, lon, lat); # # Projects the given map (longitude, latitude) point to a virtual point # in the unit square, using the clipping parameters given. #------------------------------------------------------------------------ sub project { # get a hold of my parameters my ($region, $lon, $lat) = @_; # declare local variables my ($tx, $ty, $x, $y); # perform the projection $tx = ($lon - $region->{lon1}) / ($region->{lon2} - $region->{lon1}); $ty = ($lat - $region->{lat1}) / ($region->{lat2} - $region->{lat1}); $x = $region->{x1} + $tx * ($region->{x2} - $region->{x1}); $y = $region->{y1} + $ty * ($region->{y2} - $region->{y1}); # return the projected point return ($x, $y); } #------------------------------------------------------------------------ # double rad_to_deg(rad); # # Converts a value from radians to degrees. #------------------------------------------------------------------------ sub rad_to_deg { # get a hold of my parameters my ($rad) = @_; # return the value in degrees return ($rad * (180.0 / $PI)); } #------------------------------------------------------------------------ # int set_pen(); # # Sets the state of the image to reflect the current pen settings and # returns the color to use for drawing. #------------------------------------------------------------------------ sub set_pen { # set the pen thickness $image->setThickness($map_pen{width}); # set anti-aliased mode if necessary if ($map_pen{smooth}) { $image->setAntiAliased($map_pen{color}); return gdAntiAliased; } else { return $map_pen{color}; } } #------------------------------------------------------------------------ # double var(name); # # Tries to do a substitution on the given name using the current # variable table. If the name couldn't be found, we just perform the # identity operation. #------------------------------------------------------------------------ sub var { # get a hold of my parameters my ($name) = @_; # return the mapped value, or the original return (defined $map_var{$name}) ? $map_var{$name} : $name; }