Weather Station

For your first Perl glue feat, you're going to create your own weather application. You might be familiar with those desktop applications that run on the desktop toolbar and display the current temperature. You're going to build your own (at least, a text version) in Perl.

Your requirements will be the following:

  • Your data sources must be readily available, free (no money required), and freely redistributable (no license required).

  • You want this to be easy for users to use. They'll supply just the ZIP code, and you'll figure out the rest.

  • You'd like at least the current conditions (rainy, cloudy, sunny, and so on) and the temperature.

Not too difficult, eh? Now you have to construct how you're going to get from the ZIP code to a weather report:

  • Hourly weather observations are freely available from NOAA (National Oceanic and Atmospheric Administration) at http://www.nws.noaa.gov/data/current_obs. They're arranged by observation station names, called METAR codes. These observation stations are frequently airports.

  • A list of airports, locations, and their corresponding METAR codes is available from NOAA as well at http://www.nws.noaa.gov/data/current_obs/index.xml.

  • The U.S. Census Bureau maintains a database that will roughly correlate a ZIP code to latitude and longitude. You can use the latitude and longitude to find the closest airport.

So reversing your steps gives you the procedure you need to write your program:

1.
Use the ZIP code to find out the rough latitude and longitude of where that ZIP code is from the U.S. Census Bureau.

2.
Take the list of airports and compute the distance between each airport and the ZIP code using a Great Circle formula—this is used to find the distance between two points on the surface of the Earth given latitude and longitude. Remember the closest airport and its METAR code.

3.
Use that METAR code to look up the weather at NOAA.

4.
Display the information you've collected.

Let's do this program in three different parts. Of course, it should all be combined at the end into one large program.

Part 1: Finding Out Where You Are

The first part of your problem is going from a ZIP code to a latitude and longitude. The U.S. Census Bureau maintains the U.S. Gazetteer Place and ZIP code database. To query the database, go to http://www.census.gov/cgi-bin/gazetteer and enter a ZIP Code. If I use 48220, the resulting web page is shown in Figure 20.1.

Figure 20.1. US Census Bureau's Gazetteer web interface.


The first thing to notice is the URL in the browser's Address window. Notice how the ZIP code 48220 stands out in the URL? If you were to type in the URL

http://www.census.gov/cgi-bin/gazetteer?city=&state=&zip=90210

the web site returns information on Beverly Hills, CA.

The module XML::Simple has a function called get that works like this:

    my $page =
get("http://www.census.gov/cgi-bin/gazetteer?city=&state=&zip=48220");

The web page will be fetched and stored in the variable $page. What's $page going to look like? It will contain the HTML source for the web page. If you right-click in your browser and select View Source you can look at the source. It will look something like Figure 20.2.

Figure 20.2. HTML output from a Gazetteer query.


Because it's just text in $page, you can use regular expressions to get the Location information you need. Listing 20.1 will retrieve a prompt for a ZIP code, retrieve the page, and get the location information.

Listing 20.1. Retrieving the ZIP Information
1:  #!/usr/bin/perl -w
2:
3:  # Part 1 Retrieve the Latitude & Longitude based on the ZIP Code
4: #      Also, retrieve the modules you need for this Exercise
5:  use strict;
6:  use LWP::Simple;
7:  use XML::Simple;
8:  use Math::Trig;
9:
10: print "Weather for what ZIP code?";
11: my $zip = <>;
12: chomp $zip;
13:
14: my $zipdata = get("http://www.census.gov/cgibin/gazetteer?city=&state=&zip=$zip");
15:
16: if ($zipdata =~ /no matches found/) {
17:     die "Invalid zip code";
18: }
19:
20: my ($ziplat, $ziplon) = $zipdata=~/Location: (d+.d+) N, (d+.d+) W/;
21: my ($poName) = $zipdata=~ /PO Name: ([^<]+)/;

Lines 6–8: Pull in the modules you're going to need for this program. LWP::Simple will retrieve web pages, XML::Simple does your XML parsing, and Math::Trig contains the trig functions necessary to do the distance calculations in Part 2.

Line 14: Fetch the web page for the given ZIP code, and stuff the results into $zipdata.

Lines 16–18: This is a safety precaution in case the user types in a bad ZIP code.

Line 20: The HTML source for the web page is searched for the Location information. Figure 20.2 shows you what you're looking for. These latitude and longitude coordinates are in degrees and fractions of degrees.

Line 21: You're also pulling out the Post Office Name for the ZIP code. This is often the name of the city or neighborhood where the post office is located.

You now have the ZIP code's rough latitude and longitude information.

Part 2: Finding the Local Airport

Now that you know where the ZIP code is, you need to find the local airport. At the URL http://www.nws.noaa.gov/data/current_obs/index.xml there is an XML document that lists the NOAA observation stations and the latitude and longitude of each. A sample of the document is shown in Figure 20.3.

Figure 20.3. A Bit of the NOAA XML file showing the data structure.


This is a large file (more than 700KB), it doesn't change often, and you don't want to retrieve it every time you do a weather lookup. It should be saved to a file, and then the file should be referenced when a lookup is necessary. This example assumes it's stored in a file called stations.xml.

To find the closest airport, you're going to use this formula:

distance = 3963.1 * arccos(  sin(Lat1) * sin(Lat2) +
            cos(Lat1) * cos (Lat2) *
                          cos(Lon1-Lon2)))

This formula computes, in miles, the distance on the surface of the earth between two points given their latitude and longitude (expressed in radians).

If you apply this formula to every airport in the list and your ZIP code's latitude and longitude, and remember which airport is closest, you should get the most relevant METAR code.

The code in Listing 20.2 takes the latitude and longitude gathered from Gazetteer and computes the distance to each airport listed in the NOAA database. The closest airport (METAR station) is recorded for later use in Part 3.

Listing 20.2. Finding the Nearest Airport
22:
23: #  Part 2 Using the Latitude and Longitude
24: #         find the closest METAR station
25: my $metar = XMLin("./stations.xml");
26: my $bestDistance = 10000;
27: my $closestStation = undef;
28: my $rad = 57.29577951;
29:
30: my ($latmin, $lonmin);
31: foreach my $station ( @ { $metar->{station} } ) {
32:         my ($lat, $lon) = ($station->{latitude}, $station->{longitude});
33:         next if ($lat eq "NA");
34:         ($lat, $latmin) = $lat=~/(d+).(d+)/;
35:         ($lon, $lonmin) = $lon=~/(d+).(d+)/;
36:         $lat = (int($lat) + $latmin/60)/$rad;
37:         $lon = (int($lon) + $lonmin/60)/$rad;
38:
39:         my $greatcircle = 3963.1 * acos(
40:                 sin($ziplat/$rad)*sin($lat) +
41:                 cos($ziplat/$rad)*cos($lat) *
42:                         cos(abs($ziplon/$rad - $lon))
43:                 );
44:
45:         if ($bestDistance > $greatcircle) {
46:              $bestDistance = $greatcircle;
47:              $closestStation = $station;
48:         }
49: }

Line 25: The stations.xml file is read in, and parsed into a reference. This will be used to traverse the structure and examine each station's information.

Lines 26–27: To compute the closest observation station, you need to keep an indicator to signify which is the closest airport yet seen. $bestDistance will remember how far away the most recent airport is, and $closestStation will remember that airport's information.

Line 28: Throughout this loop, all computations must be computed in radians. To convert degrees to radians, divide by this number.

Line 31: Each station in the list is examined.

Lines 32–33: The latitude and longitude are extracted from the station. If they're labeled NA (and there are some that are) you skip this iteration of the loop. METAR stations with no coordinates are no use to you here.

Lines 34–37: The coordinates in this list are stored as: dd.mm.ss (degrees, minutes, seconds). What you need are fractional degrees. So you match the degrees and minutes, divide the minutes by 60, add them back to the degrees, and then convert to radians. The longitude for Detroit Metro Airport is 42 degrees 13 minutes 53 seconds North (42.13.53 N), but what you really want is .736 radians (42.21 degrees).

Lines 39–43: Compute the distance between the airport and your given point.

Lines 45–47: If the distance computed is less than the best distance seen so far (which you initialized to 10,000 miles), that distance is saved in $bestDistance as well as a reference to the airport data in $closestStation.

After this is run, the variable $closestStation will have a reference to the structure for the closest airport.

Part 3: Fetching the Weather and Putting It All Together

Now that you've got the METAR code in $closestStation->{station_id}, you can use the page at http://www.nws.noaa.gov/data/current_obs/[METAR].xml to retrieve the weather report as an XML file. A sample of what you'll get back is shown in Figure 20.4.

Figure 20.4. Sample of current NOAA weather observations.


To get this XML file use LWP::Simple, and then parse it with XML::Simple. After that it's simply a matter of displaying the data. The final piece is shown in Listing 20.3.

Listing 20.3. Fetching the Weather
50:
51: # Part 3 Retrieve the weather
52: #     and display it
53: my $weatherXML =
54: get("http://www.nws.noaa.gov/data/current_obs/$closestStation->{station_id}.xml");
55: my $w = XMLin($weatherXML);
56:
57: printf "The weather for Zip code %s %s is reported by
", $zip, $poName;
58: printf "%s (%d miles away)
%s
", $w->{location},
59:        $bestDistance, $w->{observation_time};
60: printf "%s and %s degrees Farenheit,
", $w->{weather}, $w->{temp_f};
61: printf "Winds are %s.
", $w->{wind_string};

Line 53: Retrieves the appropriate observation data for the METAR station.

Line 55: Parses that observation data into the structure $w.

Lines 57–61: Print all the relevant information you've learned so far.

A sample of the output from your weather program is shown in Figure 20.5.

Figure 20.5. Sample output from the weather fetching program.


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.81.193