seabus tracking

tl;dr I built a thing to track the Vancouver seabus and here it is.

A couple years ago I took my first trip across the harbour to North Vancouver to view an apartment, a few weeks later I moved in and started commuting by seabus to work. It's a great commute and I much prefer it over the skytrain which somehow manages to regularly bring out the worst in people. At times it can also be very pretty.

Running every fifteen minutes during business hours most of the time it's not a big deal if you arrive at the terminal just to watch it pull away. But if time is tight or during the off hours when it's running every thirty minutes literally missing the boat sucks. Cabbing from Waterfront to Lonsdale is about thirty bucks and takes longer than the boat, and if the bridges are busy forget about it. This is why you'll sometimes see folks at Waterfront crouching near the turnstiles to get a look at the seabus countdown timer or sprinting down the gangway hoping to catch it.

Accurate-ish.

The countdown timer is a lie however, as is the schedule which suggests boats will be departing every quarter hour on the quarter hour. The timer just counts down from fifteen minutes and then restarts, having no relationship whatsoever to the whereabouts of the boats. Traffic being busy in the harbour and weather and tides being a small but real factor there are a few minutes of wiggle room on those quarter hour departures which can make or break your trip.

So like any self respecting geek I set about building a wildly over-engineered solution to a problem that amounts to a minor annoyance at best.

Because obviously!

At first I thought I might make use of the Translink API which provides access to location data for buses and trains. My then boss and his fiance had recently hacked up an app for their smart watches which worked out well so it seemed a good place to start. Turns out seabus data isn't available though so that was a non-starter.

Next I turned to aprs.fi, an app stumbled across and then used to completely derail productivity one afternoon when I shared it around the office sending everyone to stare out the window watching boats go by and comparing them to the app.

They do offer an API however the time resolution is too low for a trip that takes only twelve minutes so I tracked down and contacted Lee Woldanski a local HAM radio operator who operates the Bowen Island APRS station which is relaying vessel telemetry to aprs.fi.

He was kind enough to do some digging into the receiver and offer me advice on collecting the data. Specifically he suggested I get my hands on an RTL-SDR, a cheap USB TV tuner device which had been hacked into a generic RF tuner. That turned out to be great advice, thanks Lee!

I'd heard of them but never played with one so I picked one up and started screwing around with it. After a bunch of reading, experimenting, and sitting around by the water with a laptop I was eventually able to receive AIS beacons.

My set up uses rtl_fm to tune to the necessary frequency and pipes the output over a fifo into aisdecoder. These are both running on a raspberry pi situated in a window of my previous employer which has clear line of sight to both seabus terminals. The pi is connected by VPN to a server to which aisdecoder relays the decoded AIS beacons via UDP. This aisdecoder guide was instrumental in getting everything working as was this calibration guide on using kalibrate-rtl.

Sophisticated hardware!

Now that I was receiving the beacons I had to actually do something with them. Turns out they're a bit cryptic at first blush.

/* these are all seabus beacons, can you tell? */
!AIVDM,1,1,,A,14eHnRUPA:G<MCTL<uQ`j6nP0D35,0*49
!AIVDM,1,1,,A,14eH07@00hG<T4FL=gE1JiBr06qd,0*1E
!AIVDM,1,1,,A,14eHnRUOhcG<M;4L<tlHko380HDs,0*7A
!AIVDM,1,1,,A,14eH07@00TG<T:pL=h01UQCT0@M8,0*76

Fortunately this clever guy Kurt Schwehr who does some kind of crazy marine science when he's not working at Google or consulting with JPL wrote libais to do all the hard stuff for me. So I set about building a listener to process and store the decoded AIS beacons as they came in. Using libais it came in at just over a hundred SLOC, thanks Kurt!

Also extremely helpful during this process was this exhaustive reference to the AIVDM and AIVDO data sentence formats assembled by none other than Eric S Raymond, thanks Eric!

Once the unpacked and decoded data started coming in I went way off on a data analysis tangent but eventually I got back around to building stuff and started work on a web app to display realtime updates. Until recently it was very proof-of-concept, employing some poorly performing database queries and client side polling every minute to check for updated data. Also it didn't have a cool domain until my friend Conor whose citizenship allows him access to .us domains picked up seab.us for me. Thanks Conor!

Over the last couple weeks I've added caching for the expensive queries and swapped out polling for push updates via websockets which should not only improve performance server side but also be easier on my phone battery since I'm often checking it on mobile.

I've also been enjoying the recent addition of on-board wifi to some (but not all) of the boats in the seabus fleet so I've tried to document which boats do and don't have it and indicate that on the map. If I've made a mistake there (or anywhere else!) please tweet me or file a bug.

Next I want to build a simple model from the data I've collected to provide estimated arrival times based on current position. More on that, geopandas, jupyter, and other stuff to come ... once I figure it out!