#!/usr/local/bin/perl5.8.8 ############################################### # wgraph.html (parsed as cgi) # http://www.panix.com/~steveo/cgi/wgraph.html # prints weather forecast graphs # # by Steven M. O'Neill # steveo@panix.com # March 3, 2004 ############################################### use CGI::Carp qw(fatalsToBrowser); #debug only use LWP::UserAgent; my $query = $ENV{QUERY_STRING}; $query =~ s/city=//; &parse_query_string_and_set_vars; my $ua = LWP::UserAgent->new; $ua->timeout(9); my $response = $ua->get($URL); unless ($response->is_success) { print "Content-type: text/html\n\n"; print "
"; print "
/) {
@lines = split (/\
/);
last;
}
}
}
$start = 0;
foreach (@lines) {
$_ =~ s/^ //;
if ($start) {
last if (/^ *\$\$$/);
if ( /^ *\d{3,4} [AP]M/ ) { # this is the timestamp line
# so compare to timestamp file
$timestamp = $_;
$counterfile = "$lcloc/timestamp";
# &lock_file; #keep it locked until you generate the png, (or skip it)
# if ($locked==1) {
if (1) {
open (CTR, "<$counterfile") or die "can't open file: $!";
chomp (@old_timestamp = ); #file should always be one line long
if ($timestamp eq $old_timestamp[0]) {
$skip_png = 1;
} else { # it's a new file
# close CTR or die $!;
`echo $timestamp > $counterfile`;
}
} else { &print_lock_timeout; } #end if locked
}#end timestamp regex
close CTR; #if it's open...
if ( /^$tz 3HRLY\s*((\d{2} )*\d{2}$)/i ) { #always 2-digit
$precip .= $_ . "\n";
@times = split (/\s+/,$1);
next;
}
if ( /^TEMP\s{9}([\d\s-]+)$/i ) { #1-3 chars, signed integer
if ($temps[0]) {
$on_temps2 = 1; #flag for this case
@temps2 = &split_values;
$on_temps2 = 0;
} else {
@temps = &split_values;
}
next;
}
if ( /^DATE\s{9}([\d\s\w\/]+)$/i ) {
$tail = $1;
if ($1 =~ /\d\s{12}\w/) {
$precip .= "$_\n";
} else {
$precip2 .= "$_\n";
}
@tempor = split /\s\s+/, $tail ;
push @dates, @tempor;
next;
}
if ( /^DEWPT\s{8}([\d\s-]+)$/i ) {
if ($dewpts[0]) {
$on_temps2 = 1;
@dewpts2 = &split_values;
$on_temps2 = 0;
} else {
@dewpts = &split_values;
}
next;
}
if ( /^RH\s{11}([\d\s]+)$/ ) {
@rhs = &split_values;
next;
}
if ( /^WIND DIR\s*(([NWSE]{1,2} {1,2})*[NWSE]{1,2}$)/i ){
@dirs = split (/\s+/,$1);
# $precip .= $_ . "\n";
next;
}
if ( /^WIND SPD\s{5}([\d\s]+)$/i ) {
@speeds = &split_values;
next;
}
if ( /^CLOUDS/i) {
$precip .= $_ ."\n";
$getprecip = 1;
next;
}
if ( /^AVG CLOUDS/i) {
$precip2 .= $_ ."\n";
$getprecip2 = 1;
next;
}
# if ( /^POP 12HR\s{5}\s*((\d{1,3}\s{9,11})+\d{1,3}$)/ ) { #1st POP 12HR
# @pops = split (/\s+/,$1);
# $getprecip = 1;
# next;
# }
if ( /^$tz 6HRLY\s*((\d{2} +)*\d{2}$)/i ) { #always 2-digit
$precip2 .= $_ . "\n";
@times2 = split (/\s+/,$1);
# @times2 = split (/ /,$1);
next;
}
if (/^MIN.MAX\s{7}\s*((\d{1,3}\s*)+\d{1,3}$)/i ) {
next;
}
if (/^WIND CHILL/i or /^$/) { #sometimes it's wind chill,
$getprecip = 0; # sometimes a blank line
next;
}
if ($getprecip) {
unless ( /^SNOW 12HR\s{5}(.*)$/i) {
$precip .= $_ . "\n";
} else { #if it's a SNOW 12HR line
$precip .= $_ . "\n" if ($1 =~ /[1-9]/); # don't add non-zero
# snow amounts
}
$print_precip = 1;
next;
}
if ($getprecip2) {
$precip2 .= $_ . "\n" unless (/^$/);
$print_precip2 = 1;
}
} else {
$start = 1 if (/^$caploc/i);
}
} #end foreach (@lines)
#unless ($skip_png) {
unless (0) {
$diff = $#times - $#temps;
for $i (1..$diff) { #sometimes all the times aren't filled, so
#shift some out if there are more temps than times
shift (@times);
}
$max_y = 0;
$min_y = 0;
#distribute @pops, start at end
for ($i = $#times; $i>=0; $i--) {
if ( ($times[$i] == 06) or ($times[$i] == 18)) {
$current = pop @pops if (defined $pops[0]); #if there's anything left in the
#array, pop off last, & remember it
# $max_y = $current if ($current > $max_y); #check for max_y while we're here
}
$spaced_pops[$i] = $current;
}
$i=0;
#find max and min vals for y axis
foreach $temp (@temps) {
if ($temp < $min_y) { $min_y = $temp} else { #if not neg, could be max
$max_y = $temp if ($temp > $max_y);}
$max_y = $speeds[$i] if ($speeds[$i] > $max_y); #never < 0
if ($dewpts[$i] < $min_y) { $min_y = $dewpts[$i]} else {
$max_y = $dewpts[$i] if ($dewpts[$i] > $max_y);}
# $max_y = $rhs[$i] if ($rhs[$i] > $max_y); #never < 0
++$i;
}
$max_y += 10 - ($max_y % 10); #round up to nearest 10
$min_y -= ($min_y % 10) if ($min_y < 0); #round down to nearest 10
&print_background;
&print_graph;
&print_second_graph
}# end unless skip
#unlink("$counterfile.lock") or die $!;# remove the lock, we're done with it
&print_page;
#end main
####################
####################
sub print_graph {
use CGI ':standard';
use GD;
use GD::Graph::lines;
# All of the arrays should same number of entries.
#my @data = (\@times,\@temps,\@speeds,\@dewpts,\@spaced_pops,\@rhs);
#my @data = (\@times,\@temps,\@speeds,\@dewpts,\@spaced_pops);
my @data = (\@times,\@temps,\@speeds,\@dewpts);
#print "Content-type: text/plain\n\n";
#print "times\ttemps\tspeeds\tdewpts\tspaced_pops\trhs";
#print "$#times\t$#temps\t$#speeds\t$#dewpts\t$#spaced_pops\t$#rhs";
my $mygraph = GD::Graph::lines->new(750, 525);
$mygraph->set(
x_label => "$dates[1] $dates[2] $dates[3]",
y_label => '',
# title => "Forecast Data for $location",
y_max_value => "$max_y",
y_min_value => "$min_y",
y_long_ticks => '1',
show_values => '1',
line_width => '3',
dclrs => [ qw(lred lgreen dpink lyellow lorange)],
transparent => '0',
bgclr => 'lgray',
#white, lgray, gray, dgray, black, lblue, blue, dblue, gold,
#lyellow, yellow, dyellow, lgreen, green, dgreen, lred, red,
#dred, lpurple, purple, dpurple, lorange, orange, pink, dpink,
#marine, cyan, lbrown, dbrown -- the possible colors
logo => "$lcloc/${lcloc}_background.png",
# some more options I might set, if I wanted
# cumulate => '1',
# x_labels_vertical => '1',
# y_tick_number => '10',
# transparent => '0',
# bgclr => 'dgreen',
# fgclr => 'green',
# labelclr => 'white',
# axislabelclr => 'white', legendclr =>'white', valuesclr => 'white',
# textclr => 'white',
) or warn $mygraph->error;
#$mygraph->set_legend("temperature (F)","wind speed (MPH)","dew point (F)","chance of precip. (%)","rel. humidity (%)");
#$mygraph->set_legend("temperature (F)","wind speed (MPH)","dew point (F)","12 h. chance of precip. (%)");
$mygraph->set_legend("temperature (F)","wind speed (MPH)","dew point (F)");
$mygraph->set_title_font(gdLargeFont);
$mygraph->set_legend_font(gdSmallFont);
#sometimes we see a brooklyn
print "Location: wgraph.html\n\n" if ( $lcloc eq 'brooklyn' && !defined($times[0]) );
# (see 'man GD::Graph' for more options you could set)
if (!defined($times[0])) {
print "Content-type: text/html\n\n";
print "
";
print "$URL is returning invalid or
strangely formatted data. Please try again later.";
print "
";
exit;
}
my $myimage = $mygraph->plot(\@data) or die $mygraph->error;
open OUT, ">$lcloc/$lcloc.png" or die;
print OUT $myimage->png;
} #end sub
########################
sub print_page {
chomp $precip;
chomp $precip2;
chomp $timestamp;
$precip = "" unless ($print_precip);
$precip2 = "" unless ($print_precip2);
print "Content-type: text/html\n\n";
print <
$location Weather Forecast Chart
Weather Forecast Graph for $location
$timestamp
$precip
$precip2
Source data: $URL
Key to Abbreviations:
PFM/AFM User's Guide
EOF
print "
";
$init = "M." if ($skip_png);
print "Program by Steven $init O'Neill";
print <
More Forecast Charts:
Bath, ME
Baltimore
Bennington, VT
Boston
Chicago
Hartford
Houston
New York
Orlando
Philadelphia
San Francisco
Seattle
Cincinnati
Send email to request other cities: steveo [at] panix [dot] com.
NYC Weather Links:
NOAA's Hourly Weather Graph (Similar to this page; more
options.)
NOAA NYC Forecast
NOAA NYC Current Readings
NOAA "Zone" Forecast for NYC
Intellicast NYC Radar Loop
Is it Friday Yet? |
NYC MetroCard Bonus
Calculator |
Home Page
EOF
} #end sub print_page
#####################
sub split_values {
$template = "";
#if ($on_temps2) { #there's extra spaces in the 2nd temp line
if (0) {
#have to use unpack, in case of 100+ temps
foreach $val (@times2) {
if ($val eq '06') { #this is always the wide field
$template .= "a5"
} else {
$template .= "a3"
}
}
} else { # for lines other than the 2nd temp line
# fields are always 3 spaces wide
# $template .= "a3" for (1.. (length ($1) / 3) ) ;
}
#@array = unpack ("$template", $1);
$line = $1;
@array = split /\s+/, $line;
foreach $field (@array) {
$field =~ s/ //g;
}
while ($array[0] eq "") {
shift @array;
}
while ($array[$#array] eq "") {
pop @array;
}
return @array;
}#end sub split_values
######################
sub lock_file {
# Lock the counter file. This is a quick-and-dirty implementation of nfs-safe
# locking derived from Alexis' nfslock.c code. It assumes that any lock older
# than 20 seconds is stale and breaks it.
$locked=0;# Assume locking failure. Will be set to 1 when we succeed
$ltmp="$counterfile.lck.$$";# First, make a temporary uniquely-named file.
open(LTMP, ">$ltmp") || die "Can't create counter lock-temp file: $!\n";
select(LTMP);# Force immediate writes on LTMP so the mod time...
$|=1;# ...will change after each "print LTMP" below.
select(STDOUT);
# Now, try up to ten times to link it to the lock file
foreach $i (1..10) {
print LTMP ".";# force current mod time on file
if (link ($ltmp, "$counterfile.lock")) {# got it
$locked=1;
last
} else {
# Check age of lock. Use the mtime of $ltmp as "now"
$countertime=(stat("$counterfile.lock"))[10];
$ltmptime=(stat($ltmp))[10];
if ($countertime < $ltmptime - 20) {
# The lockfile is more than 20 seconds old
# so break it, it must be stale
unlink "$counterfile.lock" or die $!;
}
sleep (int(rand(4)+1));# wait 1-4 seconds
}
}
close LTMP;
unlink($ltmp);# remove the temp file
} # end sub
######################
sub print_lock_timeout {
print "Content-type: text/plain\n\n";
print "Sorry, the operation has timed out -- please try again.";
exit;
} # end sub
######################
sub print_background { #this draws the arrows for wind speed
# create a new image
$im = new GD::Image(750,525);
#close PNG;
# allocate some colors
$grey = $im->colorAllocate(160,160,160); #sets background color
$ltgrey = $im->colorAllocate(200,200,200);
$white = $im->colorAllocate(255,255,255);
$green = $im->colorAllocate(0,255,0);
# make the background transparent and interlaced
$im->transparent($white);
# $im->interlaced('true');
# Create a brush
$brush = new GD::Image(2,2) or die $!;
$white = $brush->colorAllocate(255,255,255);
# $green = $brush->colorAllocate(0,255,0);
$black = $brush->colorAllocate(0,0,0);
$brush->transparent($white);
#$image->arc($cx,$cy,$width,$height,$start,$end,$color)
#$circular_brush->arc(0,0,3,3,0,360,$green);
$brush->filledRectangle(0,0,2,2,$black) ;
# Set the brush
$im->setBrush($brush);
$x = 750; $y_init = 475; #starting position
$ticks = $#times + 1; #index of last + 1
$spaces = $ticks + 1; #fenceposts + 1
$degree = 504 / ($max_y - $min_y); #pixels per degree (or other unit)
$offset = ((750-19)/$spaces);
$i=0;
#make rectangles for alternation -- should be 4 spaces long
# $last_times = $times[$#times];
# $first_times = $times[0];
# $rt_sp = 6 if $last_times == '18';
# $rt_sp = 3 if $last_times == '06';
# $rt_sp = 8 if $last_times == '03';
#
# $im->filledRectangle(750-(($rt_sp+4)*$offset),21,750-($offset*$rt_sp),473,$ltgrey);
# $im->filledRectangle(750-(($rt_sp+12)*$offset),21,750-($offset*($rt_sp+8)),473,$ltgrey);
#loop thru @speeds, start at end
for ($i = $#speeds; $i>=0; $i--) {
$speed = $speeds[$i];
$x -= $offset;
$y = $y_init - ($speed*$degree) - 20; #move up 20 to get away from the pt.
$y += ($min_y * $degree) if ($min_y < 0); #move up to zero
#if $min_y is negative
if ($dirs[$i] eq "W") { $x2 = $x + $speed; $y2 = $y;
&make_poly($x2+3,$y2,$x2,$y2-3,$x2,$y2+3)} #draw little triangle
if ($dirs[$i] eq "E") { $x2 = $x - $speed; $y2 = $y;
&make_poly($x2-3,$y2,$x2,$y2-3,$x2,$y2+3)}
if ($dirs[$i] eq "S") { $x2 = $x ; $y2 = $y - $speed;
&make_poly($x2,$y2-3,$x2-3,$y2,$x2+3,$y2)}
if ($dirs[$i] eq "N") { $x2 = $x ; $y2 = $y + $speed;
&make_poly($x2,$y2+3,$x2-3,$y2,$x2+3,$y2)}
if ($dirs[$i] eq "SW") { $x2 = $x + ($speed/sqrt(2)) ;
$y2 = $y - ($speed/sqrt(2)) ;
&make_poly($x2-2,$y2-2,$x2+2,$y2+2,$x2+2,$y2-2)}
if ($dirs[$i] eq "SE") { $x2 = $x - ($speed/sqrt(2)) ;
$y2 = $y - ($speed/sqrt(2)) ;
&make_poly($x2-2,$y2+2,$x2-2,$y2-2,$x2+2,$y2-2)}
if ($dirs[$i] eq "NE") { $x2 = $x - ($speed/sqrt(2)) ;
$y2 = $y + ($speed/sqrt(2)) ;
&make_poly($x2-2,$y2-2,$x2+2,$y2+2,$x2-2,$y2+2)}
if ($dirs[$i] eq "NW") { $x2 = $x + ($speed/sqrt(2)) ;
$y2 = $y + ($speed/sqrt(2)) ;
&make_poly($x2-2,$y2+2,$x2+2,$y2+2,$x2+2,$y2-2)}
$im->line($x,$y,$x2,$y2,gdBrushed) ; #draw the line for the speed vector
}
# make sure we are writing to a binary stream
binmode STDOUT;
open BACKGD, ">$lcloc/${lcloc}_background.png" or die $!;
# Convert the image to PNG and print it to the file
print BACKGD $im->png;
close BACKGD or die $!;
} #end sub
#####################
sub make_poly {
my($pt1, $pt2, $pt3, $pt4, $pt5, $pt6) = @_;
#print "\n$pt1, $pt2, $pt3, $pt4, $pt5, $pt6";
# make a polygon
$poly = new GD::Polygon;
$poly->addPt($pt1,$pt2);
$poly->addPt($pt3,$pt4);
$poly->addPt($pt5,$pt6);
# draw the polygon, filling it with a color
$im->filledPolygon($poly,$green);
}
sub print_second_graph {
for $i (0..4) {shift @dates;} #first 4 values are no good now
my $extraspace = '';
$max_y = -400;
$min_y = 400;
$i = 0;
foreach $temp (@temps2) {
if ($temp > $max_y) {
$max_y = $temp;
} elsif ($temp < $min_y) {
$min_y = $temp;
}
if ($dewpts2[$i] > $max_y) {
$max_y = $dewpts2[$i];
} elsif ($dewpts2[$i] < $min_y) {
$min_y = $dewpts2[$i];
}
++$i;
}
$max_y += 5 - ($max_y % 5); #round up to nearest 10
$min_y -= ($min_y % 5) if ($min_y > 0);
$min_y -= ($min_y % 10) if ($min_y < 0); #round down to nearest 10
$days = $#dates + 1 ;
$extraspace = ' ' if ($days == 5);
my @data = (\@times2,\@temps2,\@dewpts2);
my $mygraph = GD::Graph::lines->new(750, 175);
$mygraph->set(
x_label => "$dates[0] $dates[1] $dates[2] $dates[3] $extraspace $dates[4] ",
y_label => '',
title => "Forecast for Next $days Days",
y_max_value => "$max_y",
y_min_value => "$min_y",
y_long_ticks => '1',
show_values => '1',
line_width => '3',
dclrs => [ qw(lred dpink lyellow lorange)],
bgclr => '$ltgrey',
logo => "background_2.png",
transparent => '0',
bgclr => 'lgray',
) or warn $mygraph->error;
$mygraph->set_legend("temperature (F)","dew point (F)");
$mygraph->set_title_font(gdLargeFont);
$mygraph->set_legend_font(gdSmallFont);
# (see 'man GD::Graph' for more options you could set)
my $myimage = $mygraph->plot(\@data) or die $mygraph->error;
open OUT, ">$lcloc/${lcloc}_2.png" or die;
print OUT $myimage->png;
} #end sub
sub parse_query_string_and_set_vars {
if ($query =~ /^seattle/i) {
#$URL = 'http://kamala.cod.edu/wa/latest.fous51.KSEW.html';
$URL = 'http://www.nws.noaa.gov/data/SEW/PFMSEW';
$location="Seattle";
$lcloc='seattle';
$caploc='SEATTLE';
$sel_seattle = 'selected';
$tz = 'P[DS]T';
} elsif ($query =~ /^boston/i) {
#$URL = 'http://kamala.cod.edu/ri/latest.fous51.KBOX.html';
#$URL = 'http://www.nws.noaa.gov/data/BOX/PFMBOX';
$URL = 'http://forecast.weather.gov/product.php?site=CRH&product=PFM&issuedby=BOX';
$location="Boston";
$lcloc='boston';
# $caploc='BOSTON|EASTERN ESSEX';
$caploc='BOSTON|SUFFOLK MA|EASTERN ESSEX';
$sel_boston = 'selected';
$tz = 'E[DS]T';
} elsif ($query =~ /^chicago/i) {
$URL = 'http://forecast.weather.gov/product.php?site=CRH&product=PFM&issuedby=LOT';
$location="Chicago";
$lcloc='chicago';
$caploc='CHICAGO';
$sel_chicago = 'selected';
$tz = 'C[DS]T';
} elsif ($query =~ /^sanfrancisco/i) {
$URL = 'http://forecast.weather.gov/product.php?site=CRH&product=PFM&issuedby=MTR';
$location="San Francisco";
$lcloc='sanfrancisco';
$caploc='SAN FRANCISCO';
$sel_sanfrancisco = 'selected';
$tz = 'P[DS]T';
} elsif ($query =~ /^hartford/i) {
$URL = 'http://www.weather.gov/data/BOX/AFMBOX';
$location="Hartford";
$lcloc='hartford';
$caploc='WINDSOR LAKES|HARTFORD';
$sel_hartford = 'selected';
$tz = 'E[DS]T';
} elsif ($query =~ /^houston/i) {
$URL = 'http://forecast.weather.gov/product.php?site=CRH&product=PFM&issuedby=HGX';
$location="Houston";
$lcloc='houston';
$caploc='HOUSTON|Intercontinental';
$sel_houston = 'selected';
$tz = 'C[DS]T';
} elsif ($query =~ /^orlando/i) {
# $URL = 'http://kamala.cod.edu/fl/latest.fous52.KMLB.html';
#$URL = 'http://www.srh.noaa.gov/mlb/cgi-bin/productviewer.php?product=PFMMLB&version=0';
$URL =
'http://forecast.weather.gov/product.php?site=NWS&issuedby=MLB&product=PFM&format=txt&version=1&glossary=1';
$location="Orlando, FL";
$lcloc='orlando';
$caploc='ORLANDO';
$sel_orlando = 'selected';
$tz = 'E[DS]T';
} elsif ($query =~ /^bath/i) {
$URL = 'http://www.nws.noaa.gov/data/GYX/AFMGYX';
$location="Bath, ME";
$lcloc='bath';
$caploc='.*BATH';
$sel_bath = 'selected';
$tz = 'E[DS]T';
} elsif ($query =~ /^philadelphia/i) {
#$URL = 'http://kamala.cod.edu/pa/latest.fous51.KPHI.html';
$URL = 'http://www.nws.noaa.gov/data/PHI/PFMPHI';
$location="Philadelphia";
$lcloc='philadelphia';
$caploc='PHILADELPHIA';
$sel_philadelphia = 'selected';
$tz = 'E[DS]T';
} elsif ($query =~ /^bennington/i) {
$URL = 'http://www.nws.noaa.gov/data/ALY/AFMALY';
$location="Bennington, VT";
$lcloc='bennington';
$caploc='BENNINGTON';
$sel_bennington = "selected";
$tz = 'E[DS]T';
} elsif ($query =~ /^rutland/i) {
$URL = 'http://www.nws.noaa.gov/data/BTV/AFMBTV';
$location="Rutland, VT";
$lcloc='rutland';
$caploc='EASTERN RUTLAND';
$tz = 'E[DS]T';
} elsif ($query =~ /^baltimore/i) {
$URL = 'http://www.nws.noaa.gov/data/LWX/AFMLWX';
$location="Baltimore";
$lcloc='baltimore';
$caploc='.*BALTIMORE(-|$)';
$sel_baltimore="selected";
$tz = 'E[DS]T';
} elsif ($query =~ /^brooklyn/i) {
$URL = 'http://www.nws.noaa.gov/data/OKX/AFMOKX';
$location="Brooklyn";
$lcloc='brooklyn';
$caploc='KINGS';
$tz = 'E[DS]T';
} elsif ($query =~ /^cincinnati/i) {
$URL = 'http://www.nws.noaa.gov/data/ILN/PFMILN';
$location="Cincinnati";
$lcloc='cincinnati';
$caploc='CINCINNATI';
$sel_cincinnati="selected";
$tz = 'E[DS]T';
} elsif ($query =~ /^dallas/i) {
$URL = 'http://forecast.weather.gov/product.php?site=CRH&product=PFM&issuedby=FWD';
$location="Dallas, TX";
$lcloc='dallas';
$caploc='DFW';
$sel_dallas="selected";
$tz = 'C[DS]T';
} elsif ($query =~ /^test/i) {
$URL = 'http://www.panix.com/~steveo/cgi/test/AFMOKX.2';
$location="Test";
$lcloc='test';
$caploc='BRONX|NEW YORK';
$tz = 'E[DS]T';
} else { #default is New York
# $URL = 'http://www.nwnws.noaa.gov/data/OKX/AFMOKX';
# $URL = 'http://kamala.cod.edu/ct/latest.fous51.KOKX.html';
$URL ='http://www.nws.noaa.gov/data/OKX/AFMOKX';
$URL ='https://forecast.weather.gov/product.php?site=NWS&issuedby=OKX&product=PFM&format=CI&version=1&glossary=1&highlight=off';
$URL ='http://forecast.weather.gov/product.php?site=CRH&product=AFM&issuedby=OKX';
$location="New York City";
$lcloc='ny';
$caploc='Bronx|New York|Hudson'; # remember, it's the string to look for in the input file
$sel_newyork = 'selected';
$tz = 'E[DS]T';
}
}#end parse_query_string_and_set_vars
#########################
# vim:syntax=perl