Staying Fit with Bing Maps

I just feel better when I have a certain level of fitness. It has less to do with bringing my body into shape for the speedo season and more with general happiness. For me ‘mens sana’ (a sound mind) lives indeed ‘in corpore sano’ (in a healthy body). Whenever possible I try to get my exercise out in nature, away from distractions, enjoying the fresh air and the occasional wildlife encounter. However, between rubbing the sleep from my face and going to work I find it more convenient to get down to my basement and jump on my bicycle or my rowing machine, especially when it is still dark outside. I also get bored easily and sweating just for the sake of sweating isn’t my cup of tea. Reading eBooks during the workout worked for a while, but it wasn’t very satisfying in the long run so I needed to come up with something that was a bit of a challenge along with some form of entertainment. So, I came up with the idea of the ‘Donkey Bike’.

The idea was to create a virtual trip to visit my parents in Germany and track the progress with every rotation of my wheel and every stroke of the rower. I’d rather choose a scenic route than the direct one, so the first part of the journey is a 6,886 km ride from Redmond, WA to Nova Scotia, Canada.

With 1,081 km down, a flat tire (yes, that can happen even in the basement), a few interesting scenes discovered on the imagery and a few pounds lighter than when I started the journey, I thought it’s time to share some details on the somewhat unusual ‘Donkey Bike’.

The Setup

I already had an indoor bicycle trainer with adjustable resistance to put my road bike on. From an old bicycle computer I re-used the reed-switch. I bought an Arduino Uno with a proto shield, dug out my good old soldering station and got to work. The reed switch needs to be connected to ground and digital pin 3 (D3) on the Arduino and then we can connect the board via the USB port to the laptop and start coding.

Arduino Firmware

I kept this part very simple and just count the rotations, i.e. the number of times that the reed-switch closed and write that number to the serial port that is being read by the USB connection on the laptop.

The Arduino IDE took a bit getting used to, and for more complex code I would be looking at something like Arduino for Visual Studio.

int reedPin = 3;  //digital pin connected to reed switch
int rotationCount = 0; // will be used in interrrupt

void rotation() {
  byte buff = 0x00;
  for (byte i = 0; i < 8; i++) {
    buff |= !digitalRead(reedPin) << i;
  } 
  if (buff == 0xff) {
    rotationCount += 1;
  }
}

void setup(){
  Serial.begin(9600);  // set pin connected to reed switch to input
  pinMode(reedPin, INPUT);  // set reed pin pull up resistor
  digitalWrite(reedPin, HIGH);  // attach interrupt to reed pin
  attachInterrupt(1, rotation, FALLING);
}


void loop(){
  Serial.println(rotationCount, DEC);
  delay(100);
}

Windows Application

Rather than going through the 200-odd lines of code of the WPF application, I will just point out a few things and put the complete source code on my OneDrive here.

For the mapping I use the Bing Maps WPF Control.

To plot the initial route I use the Bing Maps REST Services, avoid highways and return the route-geometry. I then insert the route as a SqlGeography in a SQL Server table. The create table scripts for the tables I use are also in the download. Note that Bing Maps returns coordinates in the order (latitude, longitude) while the Well Known Text (WKT) in which we describe the SqlGeography expects coordinates in the order (longitude latitude) so we have to flip the order for each coordinate.

Private Sub btnGetRoute_Click(sender As Object, e As RoutedEventArgs)
        myMap.CredentialsProvider.GetCredentials(
            Function(bmKey)
                Dim routeRequest As New Uri( _
            "http://dev.virtualearth.net/REST/V1/Routes/Driving" _
            + "?wp.0=[Your Start]" _
            + "&wp.1=[Your End]" _
            + "&avoid=highways" _
            + "&optimize=distance" _
            + "&distanceUnit=km" _
            + "&travelMode=Driving" _
            + "&routePathOutput=Points" _
            + "&key=" + bmKey.ApplicationId)
                Dim wc As New WebClient
                AddHandler wc.DownloadStringCompleted, AddressOf routeCallback
                wc.DownloadStringAsync(routeRequest)
                Return 0
            End Function)
    End Sub

    Private Sub routeCallback(sender As Object, e As DownloadStringCompletedEventArgs)
        Dim jsonText = e.Result
        Dim jsp = New JsonParser()
        Dim json As Object = jsp.Parse(jsonText)
        Dim routePath As String = "LINESTRING("
        For i = 0 To json.resourceSets(0).resources(0).routePath.line.coordinates.count - 1
            routePath += json.resourceSets(0).resources(0).routePath.line.coordinates(i)(1).ToString + _
                " " + json.resourceSets(0).resources(0).routePath.line.coordinates(i)(0).ToString + ","
        Next
        routePath = routePath.Substring(0, routePath.Length - 2) + ")"
        myGeog = SqlGeography.Parse(routePath)
        myGeog.STSrid = 4326
        myConn.Open()
        Dim myCMD As New SqlCommand
        myCMD.Connection = myConn
        myCMD.CommandText = "INSERT INTO Routes (Geog, StartLat, StartLon) VALUES (@Route, " + json.resourceSets(0).resources(0).routePath.line.coordinates(0)(0).ToString + ", " + json.resourceSets(0).resources(0).routePath.line.coordinates(0)(1).ToString + ")"
        Dim myGeogParam As SqlParameter = myCMD.Parameters.Add("@Route", Sys-tem.Data.SqlDbType.Udt)
        myGeogParam.UdtTypeName = "Geography"
        myGeogParam.Value = myGeog
        myCMD.ExecuteNonQuery()
        myConn.Close()
        MessageBox.Show("Done")
    End Sub

In order to track the progress I made along the route I calculate the distance based on the wheel size and the number of rotations that I receive from the Arduino and add it to the total distance I have travelled in previous workouts. Then I determine the corresponding location along the route using the function LocateAlongGeog from the SQL Server Spatial Tools and set the center of the map and the location of the pushpin accordingly.

Private Sub myTimer_Tick(sender As Object, e As EventArgs)
    If stopWatch.IsRunning Then
        Dim ts As TimeSpan = stopWatch.Elapsed
        currentTime = String.Format("{0:00}:{1:00}:{2:00}", ts.Hours, ts.Minutes, ts.Seconds)
        lblTimer.Content = currentTime

        Dim rotations() As String
        rotations = Split(com3.ReadExisting, Environment.NewLine)
        Dim lastRotations = rotations(rotations.Length - 2)
        lblRotations.Content = lastRotations

        Dim thisDistance As Integer = CInt(lastRotations * 700 * 3.14 / 1000)
        Dim totalDistance = thisDistance + myDistSoFar
        lblDistance.Content = thisDistance.ToString

        Dim myCurrentSqlLoc As SqlGeography
        myCurrentSqlLoc = SQLSpatialTools.Functions.LocateAlongGeog(myGeog, totalDis-tance)
        Dim myCurrentBingLoc As Location = New Location(myCurrentSqlLoc.Lat, myCur-rentSqlLoc.Long)
        myPin.Location = myCurrentBingLoc
        myMap.SetView(myCurrentBingLoc, 17)

        myGauge.CurrentValue = thisDistance / ts.TotalSeconds * 3.6
    End If
End Sub

The gauge measures my average speed and makes me feel bad when I get below 35 km/h. For the gauge I re-used a piece of code written by EvelynT and published on Code Project.

I was contemplating creating a multi-user environment where you can race your friends and maybe that is something I’ll do later, but for now it’s been quite fun to figure out how to make this work and it continues to be fun to cycle virtually through beautiful landscapes. On the downside: I may have to resign as the mayor of the Hippo Pool soon. ☺

- Bing Maps Team