Lab 9 - Social Skills and Mapping

 

Overview
Today, we'll extend the social networking script from last week. We want to do two things:
  • Make it so that we can store people's addresses, as well as their friends lists.
  • Add functionality to export the entire list of people to an XML (RSS) file, or just a list of someone's friends. We'll export the addresses too, so that we can use this RSS file as an input to the Yahoo Maps REST query interface (sound familiar?) This way, we can see where all our friends live.
Let's go one at a time. You should begin by copying socialnet.pl from last week to this week's lab directory. This week, let's call it socialnet2.pl.

 

Storing addresses

Right now, each person's key in the hash just stores a list of their friends. That means if we have two people in our database, Alice Smith and Bob Davis, it will look like this if they are friends:

%db = (
   'Alice Smith' => [ 'Bob Davis' ],
   'Bob Davis'   => [ 'Alice Smith' ]
);

If we add a third person, Cindy Jones, and make her friends with Bob, our database will look like this:

%db = (
   'Alice Smith' => [ 'Bob Davis' ],
   'Bob Davis'   => [ 'Alice Smith', 'Cindy Jones' ],
   'Cindy Jones' => [ 'Bob Davis' ]
);

We would like to change our database so that it can store other information about each person, not just their friends list. In particular, we'd like to store each person's address in two fields - a street address, and a city and state (as one field). We'll store it this way because this is the way that Yahoo Maps expects it.

Right now each key in the hash is associated with a list - more precisely, with a reference to a list. We'll update this data structure so that each key in the hash is associated with a hash (well, a hash reference) that stores information about the person. One of the fields of the hash will be friends, which will be associated with the old list (reference) of friends.

So now we're going to have a hash of hash references, which in turn contain list references. Scared? Don't be, a step at a time makes it easy. In the hypothetical example above, and with some imaginary addresses around Bloomington, our above example database might look like this:

%db = (
     'Cindy Jones' => {
       'friends' => [ 'Bob Davis' ],
       'street' => '515 E. 4th St',
       'citystate' => 'Bloomington, IN'
     },

     'Bob Davis' = {
       'friends' => [ 'Cindy Jones', 'Alice Smith' ],
       'street' => '732 E. Smith Ave',
       'citystate' => 'Bloomington, IN'
     },

     'Alice Smith' => {
       'friends' => [ 'Bob Davis' ],
       'street' => '751 Alpine Trail',
       'citystate' => 'Bloomington, IN'
     }
);

Take a minute to think about what needs to be changed. If you have developed your socialnet.pl file carefully, we'll only need to change a few functions to let us store the new data:

  • add_person - used to represent an empty person by [ ], the reference to an empty list. Need to change it to use a hash.
  • add_link - this used to assume the list could be found by accessing the value associated with the person's name in the hash. Now, accessing the person's name in the hash gives a reference to a hash. We need to do one more step to get access to the friends list..
  • remove_link - similar to add_link.
  • print_db - There's lots more stuff to print now, so this subroutine will have to be changed to print it all.

Hint: your new add_person method will look like this:

############################################################
# adds a new person to the database 
#
sub add_person {
  my ($db_ref, $who) = @_;

  $db_ref->{$who} = { street => "", 
                      citystate => "", 
                      friends => [ ] };
}

This method associates the person's name with a hash containing all the keys we need, and initializing those keys to sensible default values. That way, if we make a person but don't set her address or friends, we can still use the print functionality without it blowing up on us.

Do this:
Replace your add_person method with the code above, and then change the other methods listed above so that they don't give error messages when compiled. Of course you can't test that the address works yet, but adding and removing friends should work again when you are done. Remember that all you have to do is:
  1. Extract from the big hash (the database) the little hash associated with the person we care about. This will be a reference; let's assume it's called $p_r.
  2. Modify @{$p_r->{friends}} in the appropriate way.

Your print_db function should now print out each person's address and city/state (these should be blank, of course, but still printed). It should print each person's friends as usual. Test your program to make sure it does this.

The next step is to make it so we can actually set the address. Because specifying the address on the command line would be annoying, let's do it like this:

  1. Add a function called get_address_interactive that prompts a user for the two parts of an address (the street address, and the city + state in one), and returns this in a hash. This hash might look like (street => '123 Someplace Dr', citystate => 'Littleton, AK').
  2. Add a function called set_address that takes as parameters three things:
    • A reference to the database ($db_ref)
    • A person's name ($p)
    • An address hash, like gets returned by the other function (%addr).
    This function should find the entry in the database for the person $p, and set their address to %addr (by modifying the street and citystate attributes of the person's hash).
  3. Finally, make it so that when someone types ./socialnet2.pl address Alice Smith, your code prompts them for Alice's address, then stores it under her name.

When this part is all done, your program should look like this when run:

jpr@sulu (i211/lab9) % ./socialnet2.pl addr Alice Smith
Setting address for Alice Smith.
Enter street address => 751 Alpine Trail
Enter city, state => Bloomington, IN
jpr@sulu (i211/lab9) %

When you print everything, you should see addresses now:

jpr@sulu (i211/lab9) % ./socialnet2.pl pr
Cindy Jones is friends with:
        Bob Davis
Cindy Jones lives at 515 E. 4th St, Bloomington, IN.
Bob Davis is friends with:
        Cindy Jones, Alice Smith
Bob Davis lives at 732 E. Smith Ave, Bloomington, IN.
Alice Smith is friends with:
        Bob Davis
Alice Smith lives at 751 Alpine Trail, Bloomington, IN.
jpr@sulu (i211/lab9) %

If that all looks right, congratulations, you're done with this part!

 

Exporting to XML/RSS

The next step is to export the database to XML/RSS in a format that Yahoo Maps can read. We want to end up with something like this:

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
 xmlns:ymaps="http://api.maps.yahoo.com/Maps/V1/AnnotatedMaps.xsd">

<channel>
   <item>
     <title>Cindy Jones</title>
     <ymaps:Address>515 E. 4th St</ymaps:Address>
     <ymaps:CityState>Bloomington, IN</ymaps:CityState>
   </item>
   <item>
     <title>Bob Davis</title>
     <ymaps:Address>732 E. Smith Ave</ymaps:Address>
     <ymaps:CityState>Bloomington, IN</ymaps:CityState>
   </item>
   <item>
     <title>Alice Smith</title>
     <ymaps:Address>751 Alpine Trail</ymaps:Address>
     <ymaps:CityState>Bloomington, IN</ymaps:CityState>
   </item>
</channel>

This is relatively simple - we just want to create another version of the print_db function that prints out the database in this RSS format, instead of in plain text.

Do this:

Create a method called export_rss that takes two parameters - a $db_ref, and a file name. Write the method so that it exports the entire database, as RSS in the format above, into the file named by the file name.

Here's a twist - use a file test operator to check if the file exists before writing. If the file exists, your function should die with an appropriate warning (don't want to overwrite a file accidentally).

Next, change your main function so that a user can type ./socialnet2.pl export something.xml to export the entire database to the file called something.xml (or whatever they put as the argument). Test your program and make sure it works.

Finally, let's make one more improvement. Imagine we want to export only a certain person's friends to the XML file, instead of everyone. Let's make that possible.

Do this:

Change your export_rss subroutine so that it takes a third parameter - $p, a person's name. It should check to see if this paramter is defined. If it is, it should export only the friends of $p. However, if $p is not defined, it should export everyone.

You might be tempted to put all the code currently in your export_rss subroutine into an if or else statement, and write another branch that just exports a certain person's friends. While this approach will work, there is a much shorter one...

Next, change the main part of your program so that the elsif branch that handles the 'export' command checks to see if a name is on the command line (@ARGV). If @ARGV is long enough, you should stick the first and last name together in one variable to pass to export_rss (just like 'add' and others do). Otherwise, call export_rss as before (which will make $p undefined inside export_rss, which is what you want).

Finally, test your program to make sure everything is working correctly. If all the exports look good, it's time to move on to interfacing with Yahoo Maps.

 

Interface with Yahoo Maps

If you have an application ID from the project, this is easy. The first step is to make sure that your web space is set up correctly:

jpr@sulu (i211/lab9) % chmod a+x ~
jpr@sulu (i211/lab9) % chmod a+x ~/public_html
You only have to do that once, so don't worry about doing it next time you have to interface with Yahoo Maps.

Next, copy your XML file (let's assume it's called friends.xml) into your public directory:

jpr@sulu (i211/lab9) % cp friends.xml ~/public_html
jpr@sulu (i211/lab9) % chmod a+r ~/public_html/friends.xml

Finally, point your web browser to the apppriate URL. This depends on your application ID (which you get from Yahoo) and your username on Sulu:

http://api.maps.yahoo.com/Maps/V1/annotatedMaps?appid=YOURKEY
&xmlsrc=http://sulu.informatics.indiana.edu/~username/friends.xml

Really that should be on one line, but then it would be too wide to fit on the screen.

 

Turn in your assignment

Strictly speaking, there's only one file in this week's lab, so a tarball isn't really required. However, let's make one anyway.

As a reminder, here's the command you can use:

jpr@sulu (i211/lab9) $ cd ..
jpr@sulu (i211) $ tar czv lab9 > lab9.tar.gz
socialnet2.pl
jpr@sulu (i211) $

You can use the less command to see what is inside this tarball. less lab9.tar.gz should show socialnet2.pl, and with the correct size.

You should then download this lab9.tar.gz file and upload to the Lab 9 assignment on Oncourse. Make sure you press "submit" and "finish" enough times to actually upload your file. We will take off points for incorrect submissions, even if you really finished the lab on time.

Okay, that's it! Have a good weekend.

 

Thanks to Jacob Ratkiewicz for lab content, Sid Stamm for CSS stylesheets, and Rob Signorelli for original syntax-highlighting JavaScript.