Thursday, September 27, 2012

Populaire

It has been a while since my last blog entry - I've been busy virtually cycling from Lands End to John O Groats. I just arrived at Chester after 600k of riding. There's about 1200k to go.

I finally got to ride with my wife last weekend. She's trying to get back into shape after being hit with breast cancer last year. We rode 22 miles along the beach at Santa Monica. It's sad to see a woman who used to ride double centuries being challanged by a flat 20 mile ride, but she'll come back - I'm sure of it.

To help her, I just registered a new populaire ride with RUSA. A populaire is a brevet style ride between 100k and 200k in length. They are intended to attract new randonneurs who might consider a 124 mile ride too extreme but would consider a 62 mile ride "doable". It's a slightly extended version of one of my favorite rides and it goes from the Panera in San Bernardino to the Lucky Greek in Corona and back.

I hope that Sherry will be able to ride it with me by the end of the year. I'll sign her up with RUSA and we can work on our P12 award together. The P12 award is given to members who ride a populaire for twelve consecutive calendar months. I also plan to ride it once a week after work to help me accumulate miles towards a mondaix award.

The mondaix award is given to a RUSA member who rides a lifetime total of 40,000km of sanctioned rides. I currently have about 17,000km so I have a long way to go. By the way, 40,000km is the approximate circumference of the earth (mondaix is French for world or something).

Hopefully the populaire will be accredited soon so I can start riding it. The temps in the evening are finally starting to cool down to the point where I could squeeze a 100k ride in after work.

Monday, September 17, 2012

Lejog

Lejog is an acronym for Lands End - John O' Groats. Lands End is the most SW point in the UK and John O' Groats is the most NE point. At any time you can find people skating, hopping, jogging, and even cycling from one to the other. Lejog is more popular than Jogel because of the prevailing winds.

I downloaded a set of Lejog gpx files from http://www.cycle-endtoend.org.uk/ (thanks Michael) in 17 stages, ran them through www.ridewithgps.com to add elevation data, and dropped them onto my computer. I plan on virtually riding the whole trip on my exercise bike. I already completed stage 1 from Lands End to Bodmin. What a great route!

Went for a short bike ride to The Crema with Amber on Sunday. What a crazy ride. There was some kind of walk in Anaheim that had the entrance to the Amtrak station closed but I just drove around the cones. Who the hell closes off an Amtrak station for a public event?

There was a lot of traffic on the trail too, some kind of Get Fit Relay. But the really unique aspect of the ride was that the police had the bike trail closed off at Warner. When we got around the closure we saw they were recovering a body from the river bed. I have no idea why they felt they needed to close the trail though - we came nowhere near the investigation scene. Simply a waste of police resources and an inconvenience to all the trail users.

I've been having an email conversation recently with David Nakai who is concerned about safety on the Santa Ana trail especially after dark. When I saw the body I began to think he might have a good point. But when I started researching the incident on the Orange County Coroner's website I noticed that in the same batch of coroner's reports that included the dead man, there were three reports of deaths to cyclists caused by traffic collisions. However you cut it, cycling on the road is more dangerous than cycling on the bike path.

Overdid it this weekend so I'm taking the day off from cycling. Tomorrow I plan on cycling from Bodmin to Bristol (virtually).

Sunday, September 9, 2012

Another night 200k - Virtual cycling part 3

Funny how a ride can go great and yet the same ride a week later doesn't. I rode the Santa Ana 200k again Friday night and logged one of my worst times ever 11h05. It was windy as usual at the start so my time down to the beach was about average. I was trying to use soft cookies and Perpetuum for energy but they weren't working. I also had to take a nap at the 80 miles mark. Mentally I was strong but I just didn't have a good ride.

Three more 200k rides and I have another R12 medal, yay!

I want to finish up the software part of my current project. Starting from where we left off in the last blog entry, add the following line at the end of the initialize function...
panorama = new google.maps.StreetViewPanorama(street_canvas, {});
walkRoute();

Remove the alert from the end of the initialize() function.

Now change the <body></body> section to look like this...
<body style="height:98%" onload="initialize()" onmousedown="walkRoute();">
<div id="street_canvas" style="height:98%; width:100%"></div>
</body>

Everytime the left or right mouse button is pressed we will call a function called WalkRoute. This will display a Google Streetlevel image in the new street_canvas div. Add the following global variable declarations after the line that says 'var R = 6371;'
var panorama;
var waypoint = 0;
var distanceThisLeg = 0; // km
var rideDistancekm = 0; // Distance ridden - controls start point
var courseLengthkm = 0; // course length
var stepDistancekm = .015; // km - controls distance between frames
var rpf = 4 // revolutions per frame - controls speed
var rslf = rpf; // revolutions since last frame

Now let's write WalkRoute(). Insert this code after the initialize() functions.
function walkRoute() 
{   if (rslf < rpf) 
    {   rslf++;
        return;
    }
    try 
    {   rideDistancekm += stepDistancekm;
        while (rideDistancekm >= route[waypoint].totalAtStartkm + route[waypoint].distancem / 1000)    // Start next leg
        {   waypoint += 1;
            if (waypoint >= route.length)
            {   alert("Done");
                return;
           }
       }
       var legDistancekm = rideDistancekm - route[waypoint].totalAtStartkm;
       var point = getNextLatLong(route[waypoint].latitude, route[waypoint].longitude, legDistancekm, route[waypoint].bearing);
    }
    catch (ex)
    {   alert("Stepping: " + ex.message);
    }
    try
    {   var povOptions = { heading: route[waypoint].bearing, pitch: 0, zoom: 1 };
        panorama.setPov(povOptions);
        panorama.setPosition(point);
        panorama.setVisible(true);
    }
    catch (ex)
    {   alert("Rendering:" + ex.message);
    }
    rslf = 1;
}

function getNextLatLong(oldLat, oldLong, distance, bearing) 
{   var latRad = toRad(oldLat);
    var lngRad = toRad(oldLong);
    var Rdistance = distance / R;    // km
    var dirRad = toRad(bearing);
    var newLatRad = Math.asin(Math.sin(latRad) * Math.cos(Rdistance) + Math.cos(latRad) * Math.sin(Rdistance) * Math.cos(dirRad));
    var newLngRad = lngRad + Math.atan2(Math.sin(dirRad) * Math.sin(Rdistance) * Math.cos(latRad), Math.cos(Rdistance) - Math.sin(latRad) * Math.sin(latRad));

    newLngRad = ((newLngRad + (3 * Math.PI)) % (2 * Math.PI)) - Math.PI;
    var point = new google.maps.LatLng(toDeg(newLatRad),toDeg(newLngRad));
    return point;
}

function toRad(d) {
    return d * Math.PI / 180;
}

function toDeg(r) {
    return r * 180 / Math.PI;
}

Although this works, there's several things that could be improved.
  • Add some information such as distance, elevation, speed, rate of climb.
  • There are small pans left and right on several frames that I would like to remove.
  • When you get to an intersection it's difficult to tell which way you are turning.
  • Magnetic switches tend to bounce. We can detect and ignore bounces.

Thursday, September 6, 2012

Cycle virtually where ever you want

I've been talking for a while about writing a webpage and building some hardware that can make it look as though you were cycling along a gpx route as you use your exercise bike. Today I got it working so in this blog I'm going to talk about the concept and the hardware.

Google maps has a free, public api that allows a web page to get maps and display them. It can also display street level images. Google has created a massive catalog of panoramic images taken at regular intervals along most of the roads in the areas marked blue in this link. This includes most of North America and Europe and many other places around the world too.

You can quickly display a street level view from any of these points in any direction. If you display the view from a series of these points along a road you can create the illusion you are moving along the road. The faster your computer and Internet connections are, the better the illusion.

There are many sites such as ridewithgps, mapmyride, gpsies, etc that allow you to easily create and download gpx files that are merely a list of coordinates that form a sequence of straight-line segments that approximate a route. The segments are normally from 1 to 100 meters long depending on how curvy or straight the road is. If Google maps has created street view images along the route, an application can read the gpx file and drive you along your route at street level.

This in itself is fairly cool, but I wanted to connect my recumbent exercise bike to my computer so that I progressed along the route as I pedalled. I tried an Arduino but ended up with a much easier solution. I bought a thrift store usb mouse for $1, grabbed the sensor and magnet from a broken bicycle computer, soldered the sensor wires across the right-mouse button on the mouse and put the whole thing in a small box. When the mouse is plugged into my laptop the computer thinks the right mouse button is pressed every time the magnet passes near the sensor.

All that remained was to attach the sensor and magnet to my exercise bike and write a javascript program which responds to a right-mouse click. I purposely did not use the left mouse button because Google maps does it's own thing when it sees a left-mouse click.


This is why I'm not an electrical engineer

Here are some unedited results



I'll start on the software in the next blog.

Virtual Cycling part 2

Yesterday I explained my new project and described the hardware. Today I'll start on the software. Although I write this in Visual Studio, all you really need is notepad. I'm going to write this specifically for Google Chrome because it handles Google Maps' memory leaks better. I'm also going to make the software very barebones and simple.

The best way to start this is to create a new folder under Program Files called RouteWalker. In that folder, create a new HTML file called RouteWalker.html. You may need to create a new text file and rename it when you're done.

Whether you created an html or a txt file, make it look like this.

 
<!DOCTYPE html> 
<html style="height:100%">
<head>
</head>
<body onload="initialize()">
</body>
</html>

This is your basic empty web page except it will call a javascript function called initialize() when it has loaded. We will write initialize() soon. We are going to use three Google javascript libraries and write our own javascript so add the following lines between the <HEAD> tags.

 
<script type="text/javascript" src="http://www.google.com/jsapi"></script> 
<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false&libraries=geometry&v=3"></script>
<script type="text/javascript">//<![CDATA[

    //]]>
</script>

The rest of today's javascript will go into the blank line above. The initialize() function will start by loading a gpx file (a type of xml file) into an array of point objects. Our point objects will contain a longitude, latitude, elevation, total distance to this point, distance to next point, and bearing to next point. This requires some math. With that information we can walk the route. All calculations are in metric. To start with we need to define a few variables. Add the following declarations into the blank line above.

 
var R = 6371;   // radius earth in km
var route = []; // an empty array
var courseLengthkm = 0; // course length

Let's write initialize(). All it does for now is call a function that will load the gpx file, but later it will do a little more. I've hardcoded the gpx file name for simplicity. Here is the initialize() function - add it after the declarations above.

 
function initialize() {
    loadRoute('MyRide.gpx');
}

The meat of the javascript today is the loadRoute() function. It starts by loading the specified file into an XML document. Because I'm coding for Chrome I'm using an XMLHttpRequest object. Other browsers load files in different ways. Insert the following code  below the code above.

 
function loadRoute(filename) {
    var xmlDoc;
    var nodes;
    var distancem;
    var bearing;
    var rideDistancekm = 0;

     try {
            var xmlHTTP = new window.XMLHttpRequest();
            xmlHTTP.open("GET", filename, false);
            xmlHTTP.overrideMimeType("text/xml");
            xmlHTTP.send(null);
            xmlDoc = xmlHTTP.responseXML;
    }
    catch (ex) {
        alert("LoadXML: " + ex.message);
        return;
    }

Now we have the file loaded and parsed into XML. The coordinate data we need will either be in a series of rtept or trkpt elements. They both have the same structure, just different names. Let's go get them.

 
nodes = xmlDoc.getElementsByTagName("rtept");
if (nodes.length == 0)
    nodes = xmlDoc.getElementsByTagName("trkpt");


So now nodes is a collection of XML elements. Let's iterate through them and build our route array. For each XML element we create a new point object and then populate it. Note we do not process the last element because it has no 'next' element. Lastly we add each point to the route array.

    try { 
        for (var i = 0; i < nodes.length - 1; i++) {
            var point = { 'latitude': 0, 'longitude': 0, 'bearing': 0, 'distancem': 0, 'totalAtStartkm': 0, 'elevation': 0};
            point.latitude = parseFloat(nodes[i].getAttribute('lat'));
            point.longitude = parseFloat(nodes[i].getAttribute('lon'));
            var nextLat = parseFloat(nodes[i + 1].getAttribute('lat'));
            var nextLng = parseFloat(nodes[i + 1].getAttribute('lon'));
            var eleNode = nodes[i].getElementsByTagName('ele')[0];
            point.elevation = parseFloat(eleNode.textContent);
            bearing = normalizeBearing(getBearing(point.latitude, point.longitude, nextLat, nextLng));
            distancem = getDistance(point.latitude, point.longitude, nextLat, nextLng);
            point.bearing = parseInt(bearing);
            point.distancem = distancem;
            point.totalAtStartkm = courseLengthkm;
            courseLengthkm += distancem / 1000;
            route.push(point);
        }
        alert(route.length + ' points');
    }
    catch (ex)
    {   alert('Parse XML:' + ex.message);  }
//Place holder
}

There are several helper functions we use here so we need to write them next. Insert the following code BEFORE function initialize()

 
// Get bearing between 0 and 359
function normalizeBearing(bearing) {
    if (bearing >= 360) return (bearing - 360);
    if (bearing < 0) return (bearing + 360);
    return bearing;
}

function getBearing(lat1, long1, lat2, long2) {
    return google.maps.geometry.spherical.computeHeading(new google.maps.LatLng(lat1, long1), new google.maps.LatLng(lat2, long2));
}

function getDistance(lat1, long1, lat2, long2) {
    return google.maps.geometry.spherical.computeDistanceBetween(new google.maps.LatLng(lat1, long1), new google.maps.LatLng(lat2, long2));
}

Save your file and rename it to RouteWalker.html. Now down load a sample gpx file like this one (go to the Export tab on the right of the page and select gpx route or track) and save it as MyRide.gpx in the RouteWalker folder.

Now right-click on the RouteWalker.html file and select 'open with Chrome'. You will see an error like 'NETWORK_ERR. XMLHttpRequest Error 101'. This is Chrome's way of telling you it doesn't think you have the security chops to open a file like this. You get around this problem by launching Chrome with a special parameter.

Create a shortcut in RouteWalker with the following properties.

 
<Path to Chrome> --allow-file-access-from-files  "C:\Program Files\RouteWalker\RouteWalker.html"

When you double-click the shortcut you will now see an alert telling how many points are in your gpx file. If you used my example file the answer is 1866. Tomorrow we'll start using Google maps.

Monday, September 3, 2012

Blue Moon

 

August 31st was a blue moon (two full moons in the same calendar month) and I don't think I had ever ridden a brevet under one so I started the Santa Ana 200k permanent at 7pm after work.

The temperature was a cool 86F and the wind was uncharacteristically calm. With such ideal conditions I was hoping I'd be able to set a good time. For some reason all the clouds were stretched out in a form called 'lenticular' and as the full moon passed behind them they lit up like a candle glowing through frosted glass - an effect called irisation. Absolutely gorgeous.


I rode straight through the first 63 miles and got to the turn-around point in 4h10 - not a personal best but pretty good. I spent 15 minutes at the Beach Deli cramming as many calories in as I could. There was a chap there, covered in tattoos, who had $100 worth of lottery tickets. He was ecstatic that he had won $80. I forbore from pointing out the obvious - it's best not to irritate a heavily tattooed man in the middle of the night.

Because it was relatively cool and I hadn't had to fight strong headwinds all the way to the beach, I found myself more alert on the way back than I had ever felt before. I had filled up with Gatorade at the control and it started bugging my stomach within the first 20 miles. In the last few years I've found myself becoming less tolerant of commercial strength Gatorade so I fixed that problem by refilling my half-empty bottle with straight water. I foresee having no tolerance for Gatorade at all at some point in the future.

I stopped at the Chevron at the 90 mile mark and had one of my favorite in-ride foods - a Hostess apple pie. Even though they have too much saturated fat and salt to be considered healthy, they are nearly 500 calories of tasty and easily digested food for a little over a dollar.

One problem I have on longer rides is keeping stoked up with calories. Even though I'm carrying enough fat calories to ride all the way to Las Vegas my body seems loath to tap into this resource (unfortunately). I've taken to carrying a top-tube bag that I can put food in (I suppose it could carry other stuff, but it never does) and I've been using trail mix. Every time I think about food or I feel the very early signs of bonking I eat a little. The problem is it's too dry and I find bits getting stuck under my tongue or in my throat. I need something that's full of calories but is easier to eat. I'm thinking soft-cookies. I'm going to try that on my next brevet. I'm also thinking about onigiri (rice balls) if I can find somewhere that sells them to go.

Anyhow, I got to the end of the ride at 4:21am for a total time of 9h21 - close to my personal best for this route. It's also the first time I've completed a night 200k without napping on the route.

On Sunday I hiked 11 miles in the mountain. I'm taking today off :-)