Ïðèãëàøàåì ïîñåòèòü
Ðåëèãèÿ (religion.niv.ru)

Weather Station

Previous Table of Contents Next

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.

Weather Station


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.

Weather Station


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.

Weather Station


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 th

Previous Table of Contents Next