#!/usr/bin/env perl
# ziptoloc - Maps U.S. ZIP codes to locations
# License:  BSD-style [for this file only]
# Revision: 070806

#---------------------------------------------------------------------
#                              overview
#---------------------------------------------------------------------

my $USAGE_TEXT = << 'END_OF_USAGE';
__META_NAME__ rev. __META_REVISION__ - Maps U.S. ZIP codes to locations

Usage: __META_NAME__ 10504 94010 ...

Replace 10504 94010 ... with one or more U.S. ZIP codes. The ZIP codes
should be five digits long.

__META_NAME__ outputs one or more lines in the following format:

zipcode 10504  latitude 41.129908    longitude -73.703521

Locations are  expressed in degrees.  Positive latitudes are  "degrees
north".  Negative latitudes are  "degrees south".  Positive longitudes
are "degrees east". Negative longitudes are "degrees west".

Note:  If invalid ZIP codes are specified,  the output lines will show
"???" in the corresponding location fields.

END_OF_USAGE

#---------------------------------------------------------------------
#                        standard module setup
#---------------------------------------------------------------------

require 5.8.1;
use strict;
use Carp;
use warnings;
                                # Trap warnings
$SIG{__WARN__} = sub { die @_; };

#---------------------------------------------------------------------
#                     additional standard modules
#---------------------------------------------------------------------

use DB_File;
use Fcntl;

#---------------------------------------------------------------------
#                           basic constants
#---------------------------------------------------------------------

use constant ZERO  => 0;        # Zero
use constant ONE   => 1;        # One
use constant TWO   => 2;        # Two

use constant FALSE => 0;        # Boolean FALSE
use constant TRUE  => 1;        # Boolean TRUE

#---------------------------------------------------------------------
#                         program parameters
#---------------------------------------------------------------------

                                # Absolute pathname for database file
my $DATAFILE = "__META_DATADIR__/ziptoloc.db";

my $REVISION = '070806';        # Revision string

#---------------------------------------------------------------------
#                         global variable[s]
#---------------------------------------------------------------------

my $PROGNAME;                   # Single-word program name

#---------------------------------------------------------------------
#                          misc. routine[s]
#---------------------------------------------------------------------

# "UsageError" prints the program "usage" text and exits.

sub UsageError
{
    $USAGE_TEXT =~ s@^\s+@@s;
    $USAGE_TEXT =~ s@__META_NAME__@$PROGNAME@g;
    $USAGE_TEXT =~ s@__META_REVISION__@$REVISION@g;
    $USAGE_TEXT =~ s@\s*\z@\n@s;

    print "\n", $USAGE_TEXT, "\n";
    exit ONE;
}

#---------------------------------------------------------------------
#                            main routine
#---------------------------------------------------------------------

sub Main
{
    my @ZipCode = ();           # ZIP-code list
    my $latitude;               # Latitude  (South = negative)
    my $longitude;              # Longitude (West  = negative)
    my $str;                    # Scratch

#---------------------------------------------------------------------
# Initial setup.

    select STDERR; $| = ONE;
    select STDOUT; $| = ONE;

    $PROGNAME =  $0;
    $PROGNAME =~ s@.*/@@;

#---------------------------------------------------------------------
# Process the command line.

    for my $arg (@ARGV)
    {
        if ($arg =~ m@^(\d{5})(|-\d{4})\z@)
        {
            push (@ZipCode, $1);
        }
        else
        {
            &UsageError();
        }
    }

    if (!scalar @ZipCode)
    {
        &UsageError();
    }

#---------------------------------------------------------------------
# Process the specified ZIP codes.

    my  %ZipToLoc = ();
    tie %ZipToLoc, "DB_File", $DATAFILE;

    for my $ZipCode (@ZipCode)
    {
        $str = $ZipToLoc {$ZipCode};
        $str = "" unless defined $str;

        ($latitude, $longitude) = $str =~
            m@^([0-9\.\-]+)\000([0-9\.\-]+)\z@;

        $latitude  = '???' unless defined $latitude;
        $longitude = '???' unless defined $longitude;

        printf STDOUT
            'zipcode %5d  latitude %-11s  longitude %-11s' . "\n",
            $ZipCode, $latitude, $longitude;
    }

    untie %ZipToLoc;
    undef;
}

#---------------------------------------------------------------------
#                            main program
#---------------------------------------------------------------------

&Main();                        # Call the main routine
exit ZERO;                      # Normal exit
