{"id":307,"date":"2011-07-11T16:57:04","date_gmt":"2011-07-12T00:57:04","guid":{"rendered":"http:\/\/sethjust.com\/?p=307"},"modified":"2012-02-11T18:48:52","modified_gmt":"2012-02-12T02:48:52","slug":"sign-your-email-like-randall-waterhouse","status":"publish","type":"post","link":"https:\/\/sethjust.com\/2011\/07\/11\/sign-your-email-like-randall-waterhouse\/","title":{"rendered":"Sign your email like Randall Waterhouse"},"content":{"rendered":"

Neal Stephenson’s novel Cryptonomicon<\/a>\"\"<\/em>\u00c2\u00a0is a fascinating book; I’m re-reading it for the umpteenth time and loving it every bit as much as when I first read it in 7th grade. One thing that caught my attention on this reading (especially in light of my recent GPS post<\/a>) is the way that one of the main characters, Randall Lawrence Waterhouse, signs his email:<\/p>\n

Randall Lawrence Waterhouse<\/p>\n

Current meatspace coordinates, hot from the GPS receiver card in my laptop:<\/p>\n

8 degrees, 52.33 minutes N latitude 117 degrees, 42.75 minutes E longitude<\/p>\n

Nearest geographical feature: Palawan, the Philippines<\/p><\/blockquote>\n

(from http:\/\/www.euskalnet.net\/larraorma\/crypto\/slide51.html<\/a>)<\/p>\n

Using GPSd’s python bindings, and the free, CC-licenced RESTful API from GeoNames.org<\/a>, I wrote a short python script that will produce a similar output:<\/p>\n

Seth Just<\/p>\n

Current meatspace coordinates, hot from the GPS receiver card in my laptop:<\/p>\n

41.751 N latitude, 111.807 W longitude<\/p>\n

Nearest geographical feature: Logan Canyon, Utah, United States<\/p><\/blockquote>\n

The core of the script is two functions — one that gets GPS coordinates from GPSd, and a second that does a lookup for geographical features (mountains, canyons, islands, etc) on GeoNames.org<\/a>. For more details, see the final section of this post.<\/p>\n

Using gpssig.py<\/h2>\n

To use gpssig.py, first download it from here<\/a>, and change the extension to .py.<\/p>\n

In order to be useful as an email signature, I had to produce output in HTML format, which allows almost any mail client to use the output as a signature. I’ve gone through the effort of setting it up with the two clients I use the most: Mozilla Thunderbird and Apple’s Mail.app. Of these two, Thunderbird has documented support for HTML signatures (see here<\/a>\u00c2\u00a0for details), and should be the most like other email clients. This script has been tested on Mac OS X Snow Leopard, and should run on Linux without problems (although I haven’t tried it on versions of python higher than 2.6.1).<\/p>\n

Using gpssig.py takes two steps: first configure your Mail client to take a signature from a file, and then configure gpssig.py to run automatically to update that file.<\/p>\n

Thunderbird is relatively easy to set up with an HTML signature. Because it only supports one signature per account, you simply need to tell it to draw that signature from a file. Under each account’s settings there’s an option to “Attach the signature from a file” (see here<\/a>\u00c2\u00a0for a detailed howto). I chose to use ~\/.signature.html, but you can use any file you’d like to, just remember the location you choose.<\/p>\n

Mail.app is a more finicky beast — although the script supports it, I recommend using Thunderbird, mainly because Mail only refreshes its signatures from files on every launch, whereas Thunderbird will read the file each time you compose a message. However, if that limitation is acceptable (if you don’t move very much, perhaps), it will work just fine.\u00c2\u00a0The main issue with Mail.app is that it doesn’t store signatures in an HTML file, but instead in the proprietary .webarchive<\/a> format. These files can be found in ~\/Library\/Mail\/Signatures. To use gpssig.py with Mail, you need to follow steps 4&5 from http:\/\/bytes.com\/topic\/macosx\/insights\/825900-setup-html-signatures-apple-mail-mail-app<\/a>.\u00c2\u00a0First create a new signature (under Preferences->Signatures). This will create a new file in the signature folder — take note of the file name. You will use this file name to configure gpssig.py.<\/p>\n

gpssig.py is configured with command line options:<\/p>\n

Usage: gpssig.py [options] NAME FILENAME\r\nNote: NAME should be enclosed in quotes, unless it contains no spaces.\r\n\r\nOptions:\r\n  -h, --help   show this help message and exit\r\n  --no-gps     don't attempt to get coordinates from GPS\r\n  -a, --amail  generate files for Apple's Mail.app (.webarchive)\r\n  --lat=LAT    default latitude, if GPS is unavailable\r\n  --lon=LON    default longitude, if GPS is unavailable<\/pre>\n

The simplest way to use it is manually — simply running it whenever you want to update your signature. For example, I use .\/gpssig.py –lat=41.752 –lon=-111.793 ‘Seth Just’ ~\/.signature.html<\/span> to generate a signature for Thunderbird.<\/p>\n

If you want to make this automatic, you’ll need to have the script run automatically. On OS X you’ll need to use launchd, while on linux you need cron. Both of these make setting up repeating actions very easy — see here<\/a> for information on launchd and here<\/a> for information on cron.<\/p>\n

Sadly I haven’t yet figured out a way to run the script whenever you write an email — if I figure it out, I’ll be sure to post something.<\/p>\n

The Script<\/h2>\n

gpssig.py is built around three major functions — one gets coordinates from GPSd, one gets geographical feature information from<\/p>\n

The function I use to get GPS information is a bit hacky — there doesn’t seem to be terribly much documentation on GPSd’s python bindings, so I made do with what I could figure out:<\/p>\n

class NoGPSError (Exception): pass\r\n\r\ndef get_lat_lon():\r\n  # GPSd Python bindings\r\n  import gps\r\n\r\n  # Create GPS object\r\n  session = gps.gps()\r\n\r\n  # Set GPS object as an iterator over reports from GPSd\r\n  session.stream(gps.WATCH_ENABLE|gps.WATCH_NEWSTYLE)\r\n\r\n  # Loop until we get a report with lat and lon. The limit of 5 loops should be more than enough -- my gps never takes more than three when it has a lock\r\n  i = 0\r\n  while (1):\r\n    try:\r\n      session.next()\r\n      lat, lon = session.data['lat'], session.data['lon']\r\n      break\r\n    except:\r\n      if (i > 5): raise NoGPSError \r\n      i += 1\r\n\r\n  return lat, lon<\/pre>\n

The other function is more straightforward — it makes two http requests to GeoNames.org<\/a> to get the nearest geographical feature and locality. It then combines those responses in a way that should provide a good response almost anywhere in the world (provided that GeoNames has good, local data). I’ve tested it with a variety of coordinates (provided by\u00c2\u00a0http:\/\/www.getlatlon.com\/<\/a>), and the responses seem to be pretty good.<\/p>\n

def get_geo_string(lat, lon):\r\n  # Modules for REST\/XML request from GeoNames\r\n  import urllib\r\n  from xml.etree import ElementTree as ET\r\n  \r\n  # Request the name of the nearest geographical feature\r\n  placename = ET.parse(\r\n      urllib.urlopen(\"http:\/\/api.geonames.org\/findNearby?lat=\" + str(lat) + \"&lng=\" + str(lon) + \"&featureClass=T&username=sethjust\")\r\n      )\r\n  subdiv = ET.parse(\r\n      urllib.urlopen(\"http:\/\/api.geonames.org\/countrySubdivision?lat=\" + str(lat) + \"&lng=\" + str(lon) + \"&username=sethjust\")\r\n      )\r\n  \r\n  # Format and return place, region, country\r\n  try:\r\n    country = subdiv.getiterator(\"countryName\")[0].text\r\n  except:\r\n    try:\r\n      country = placename.getiterator(\"countryName\")[0].text\r\n    except:\r\n      country = ''\r\n  try:\r\n    region = subdiv.getiterator(\"adminName1\")[0].text\r\n  except:\r\n    region = ''\r\n  try:\r\n    name = placename.getiterator(\"name\")[0].text\r\n  except:\r\n    name = ''\r\n  \r\n  if country:\r\n    if region:\r\n      if name:\r\n        result = \"%s, %s, %s\" % (name, region, country)\r\n      else:\r\n        result = \"%s, %s\" % (region, country)\r\n    else:\r\n      if name:\r\n        result = \"%s, %s\" % (name, country)\r\n      else:\r\n        result = country\r\n  else:\r\n    result = \"unknown\"\r\n  \r\n  return result<\/pre>\n

Finally, the main method of the program parses arguments, determines what coordinates to use, gets the geographical feature string, and then generates the proper HTML and copies it into the correct place.<\/p>\n

if (__name__ == \"__main__\"):\r\n  # Parse command line options\r\n  from optparse import OptionParser\r\n  parser = OptionParser(\"Usage: %prog [options] NAME FILENAME\\nNote: NAME should be enclosed in quotes, unless it contains no spaces.\")\r\n  parser.add_option(\"--no-gps\", action=\"store_true\", dest=\"nogps\", default=False, help=\"don't attempt to get coordinates from GPS\")\r\n  parser.add_option(\"-a\", \"--amail\", action=\"store_true\", dest=\"amail\", default=False, help=\"generate files for Apple's Mail.app (.webarchive)\")\r\n  parser.add_option(\"--lat\", action=\"store\", type=\"float\", dest=\"lat\", help=\"default latitude, if GPS is unavailable\")\r\n  parser.add_option(\"--lon\", action=\"store\", type=\"float\", dest=\"lon\", help=\"default longitude, if GPS is unavailable\")\r\n\r\n  options, args = parser.parse_args()\r\n  \r\n  try: assert(len(args) == 2)\r\n  except AssertionError:\r\n    print \"You must provide NAME FILENAME\\n\"\r\n    parser.print_help()\r\n    exit()\r\n\r\n  try: assert(((options.lat!=None) & (options.lon!=None)) | ((options.lat==None) & (options.lon==None)))\r\n  except AssertionError:\r\n    print \"You must provide both lat and lon arguments\\n\"\r\n    parser.print_help()\r\n    exit()\r\n\r\n  # Modules for creating signature file\r\n  import tempfile, os\r\n\r\n  try:\r\n    if (options.nogps): raise NoGPSError\r\n    lat, lon = get_lat_lon()\r\n    print \"Got %f, %f from gps\" % (lat, lon)\r\n  except NoGPSError:\r\n    if (not options.nogps): print \"Failed to get coordinates from GPS\"\r\n    try:\r\n      assert((options.lat!=None) & (options.lon!=None))\r\n    except AssertionError:\r\n      print \"No default coordinates provided; exiting\"\r\n      exit()\r\n    lat, lon = options.lat, options.lon\r\n    print \"Using %f, %f as coordinates\" % (lat, lon)\r\n\r\n  geostring = get_geo_string(lat, lon)\r\n\r\n  lac, loc = 'N', 'E'\r\n  if lat < 0:\r\n    lat *= -1\r\n    lac = 'S'\r\n  if lon < 0:\r\n    lon *= -1\r\n    loc = 'W'\r\n\r\n  file = tempfile.NamedTemporaryFile()\r\n\r\n  file.write(\"\\n

\")\r\n file.write(args[0])\r\n file.write(\"<\/p>\\n

\")\r\n file.write(\"Current meatspace coordinates, hot from the GPS receiver card in my laptop:\")\r\n file.write(\"<\/p>\\n

\")\r\n file.write(\"%.3f %s latitude, %.3f %s longitude\" % (lat, lac, lon, loc))\r\n file.write(\"<\/p>\\n

\")\r\n file.write(\"Nearest geographical feature: \" + geostring)\r\n file.write(\"<\/p>\\n\")\r\n file.write(\"<\/html><\/body>\\n\")\r\n\r\n file.flush()\r\n\r\n if (options.amail):\r\n os.system(\"textutil -convert webarchive -output \" + args[1] + \" \" + file.name)\r\n else:\r\n os.system(\"cp \" + file.name + \" \" + args[1])<\/pre>\n","protected":false},"excerpt":{"rendered":"

Neal Stephenson’s novel Cryptonomicon\u00c2\u00a0is a fascinating book; I’m re-reading it for the umpteenth time and loving it every bit as much as when I first read it in 7th grade. One thing that caught my attention on this reading (especially in light of my recent GPS post) is the way that one of the main […]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[146,3,6,78,7],"tags":[141,28,29,35,47,143,77,158,161,84,159,142],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/sethjust.com\/wp-json\/wp\/v2\/posts\/307"}],"collection":[{"href":"https:\/\/sethjust.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sethjust.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sethjust.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/sethjust.com\/wp-json\/wp\/v2\/comments?post=307"}],"version-history":[{"count":25,"href":"https:\/\/sethjust.com\/wp-json\/wp\/v2\/posts\/307\/revisions"}],"predecessor-version":[{"id":386,"href":"https:\/\/sethjust.com\/wp-json\/wp\/v2\/posts\/307\/revisions\/386"}],"wp:attachment":[{"href":"https:\/\/sethjust.com\/wp-json\/wp\/v2\/media?parent=307"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sethjust.com\/wp-json\/wp\/v2\/categories?post=307"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sethjust.com\/wp-json\/wp\/v2\/tags?post=307"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}