Sunday, February 26, 2012

The Carrot on the Stick

Sixty mile loop today down Victoria Ave to the Lucky Greek and back on the SART. It's starting to become one of my favorite local rides partially because of the route and partially because I really like the Lucky Greek. It has a Coke machine with 106 flavors. Seriously - I tried Diet Cherry and Vanilla Dr. Pepper. Amazing. Cold and foggy at the start, then the sun came out and we got a slight headwind near the turnaround. When we came out of the Greek it was nearly 70 and we had a roaring cross/tailwind. Perfect riding.

So how many of you are like me? You ride so you can indulge in a few extra calories. There's two kinds of riders - those that name their rides after the geography (We're riding Opal Canyon), and those who name their rides after the place they're going to eat at (We're doing the Farmer Boy's loop). I'm solidly in the latter camp, in fact if a ride involves geography worth naming I probably don't want to do it. Even if I do have a ride called 'the Back Bay loop' I only ride it because we eat at a fabulous French Cafe. Anyhow, bays are far less threatening than canyons.

So I'm not ashamed to admit I don't ride to conquer hills, I ride to burn calories so can enjoy my food with a little less guilt. The carrot on the stick may be obvious but it works.

Here's to the carrot - may I never lose sight of it.

Saturday, February 25, 2012

Google Maps API part 3 Elevation Profiles

As a big cyclist I live and die by elevation profiles. In part 2 of my Google Maps API blog I showed how to get recommended cycling routes from Google Maps. In this blog I will show how to get the elevation profile for that route and how to display it. We need to display the profile in a new DIV element. To make room for the element we will reduce the height of the map DIV to 80% and display the profile DIV below it. You need to have completed parts 1 and 2 of this tutorial. Find the map_canvas DIV and change the height to 80%. Then add the following HTML below. Do not use the "/>" shortcut because of a bug in IE.
<div id="elevation_chart" style="height:20%; width:100%"></div>

We will get the elevation profile after we have displayed the route. As you might expect by now this will be an asynchronous call. We start by declaring an elevationService object and a chart object to display the elevation profile in. Because the chart object will be bound to the elevation_profile DIV it can't be initialized at this point. Add the following declarations before the initialize function.
var elevationService = new google.maps.ElevationService();
var chart;

Now we need to make the call to the elevationService. We also need to store the total distance of the route. Create a global variable to store the distance by adding the following line above the initialize function.
var totalDistance = 0;

Add the following lines of code after the line "directionsRenderer.setMap(map);" in displaydirections. It tells the elevationService to calculate the elevation at 100 evenly spaced points along the path returned by the directionsService and pass the results to a function called plotElevation. It also stores the route distance to totalDistance.
var elevationOptions = {'path': result.routes[0].overview_path, 'samples': 100};
elevationService.getElevationAlongPath(elevationOptions, plotElevation);
totalDistance = result.routes[0].legs[0].distance.value;

The last thing to do is to write the plotElevation function. We will start off simple and then add more functionality later. We need to populate a datatable, pass it to the chart control, and then render the chart control. To do visualization we need to reference a new library and load a new package. Add the new library by adding the following script tag after the <head> tag.
<script type="text/javascript" src="http://www.google.com/jsapi"></script>

And then load the visualization package by adding the following line before the initialize function.
google.load("visualization", "1", { packages: ["linechart"] });

Now we can write our new function to render the results returned by the elevationService call.
function plotElevation(results, status)
{ if (status == google.maps.ElevationStatus.OK)
  { var data = new google.visualization.DataTable();
    var distance;
    data.addColumn('string', 'Distance');
    data.addColumn('number', 'Elevation');
    for (var i=0; i < results.length; i++)
    { distance = Math.round(totalDistance * i / results.length);
      data.addRow([distance.toString(), results[i].elevation]);
    }
    var chartOptions = {legend:'none',
        titleX:'Distance (m)',
        titleY:'Elevation (m)'};
    var chartDiv = document.getElementById("elevation_chart");
    chart = new google.visualization.LineChart(chartDiv);
    chart.draw(data, chartOptions);
  }
  else
    alert("Plotting elevation" + status);
}

Try selecting a route now. About 10 seconds after the route is displayed you will see the elevation profile charted below the map. It's a bit slow.

Now the most important number for any route is the total climbing ;-) Let's add some javascript to calculate and display the total distance, total ascending, and total descending. We already know the total distance and we can calculate the others while we populate the data table. In plotElevation find the line "var distance;" and insert the following lines below it.
var totalAscending = 0;
var totalDescending = 0;

Now in the same function, after the line "data.addRow..." add the following code.
if (i > 0)
{ if (results[i-1].elevation < results[i].elevation)
    totalAscending += results[i].elevation - results[i-1].elevation;
  else
    totalDescending += results[i-1].elevation - results[i].elevation;
}

And finally we will show the user this information by inserting the following code after the line "chart.draw..."
window.status='Total Distance: ' + Math.round(totalDistance) + 'm, ' +
      'Total Ascending: ' + Math.round(totalAscending) + 'm, ' +
      'Total Descending: ' + Math.round(totalDescending) + 'm';

Google Maps API part 2 Bicycle Routing

My daughter rescheduled today's ride to tomorrow so I decided to spend more time looking at the Google Maps API. I found that routing and elevation profiles are easy too. If you have completed yesterday's tutorial, take a look at this.

I want to be able to click on two locations on the map and have the recommended cycling route displayed. To do this we need to define two map marker objects to show the start and end of the route and a directionsService object to get the route. Add the following variable declarations before the initialize function.
var directionsService = new google.maps.DirectionsService();
var startMarker, endMarker;

Now the click event handler we wrote in the previous tutorial needs to be extended. The first time we click the startMarker will be null so we will create it at the click location. The second time we click the startMarker will not be null so we will create the endMarker at the click location and then calculate the route. Like geocoding, routing is also asynchronous so we will make the request and tell Google Maps where to send the results. Add the following code to the end of the showaddress function (ideally we would rename it at this point but for simplicity we won't).
if (startMarker != null && endMarker != null)
{ // Clear the existing route, if any
  startMarker=null;
  endMarker=null;
  directionsRenderer.setMap(null);
}

if (startMarker == null)
{ // No markers so create the startMarker
  var markerOptions={position:event.latLng, map:map, title:"Start"};
  startMarker = new google.maps.Marker(markerOptions);
}
else if (endMarker == null)
{ // Start marker exists so create end marker and request route
  var markerOptions={position:event.latLng, map:map, title:"End"};
  endMarker = new google.maps.Marker(markerOptions);
  var DirectionsRequest = { origin: startMarker.position,
     destination: endMarker.position,
     travelMode: google.maps.TravelMode.BICYCLING,
     unitSystem: google.maps.UnitSystem.METRIC,
     avoidHighways: true,
     avoidTolls: true };
  directionsService.route(DirectionsRequest, displaydirections);
}

Now we need to add the routing callback function for Google Maps to pass the results to. It will use a directions rendering object to draw the route. Add the following declaration below the directionsService declaration.
var directionsRenderer = new google.maps.DirectionsRenderer();

Add the following function below the showaddress function.
function displaydirections(result, status)
{ if (status == google.maps.DirectionsStatus.OK)
  { startMarker.setMap(null); // the renderer displays its own markers
    endMarker.setMap(null);
    directionsRenderer.setDirections(result);
    directionsRenderer.setMap(map);
  }
  else
    alert("Plotting route:" + status);
}

Run the page and click on two locations. The recommended route between those locations is shown. Note how the map redisplays with the cycling layer displayed because we specified TravelMode.BYCYCLING. It would be nice if the map originally displayed in cycling mode. Bicycling mode is added as a layer. Add the following code to the end of the initialize function.
var bikeLayer = new google.maps.BicyclingLayer();
bikeLayer.setMap(map);

Friday, February 24, 2012

Google Maps API Part 1 Introduction

I spent some time over the past few days looking at Google's Maps API because I wondered how those site like ridewithgps, mapmyride, and gpsies worked. If any of you are semi-skilled javascript coders you'll be impressed at how easy it is to use.

Here's a simple tutorial showing how to display a map of Yorba Linda Regional Park, display the latitude and longitude at the cursor location, and reverse geocode (get nearest address) when the user clicks the mouse. It's designed for IE8.

Start by declaring the document as HTML5 and add the default html, head, and body tags. The style attributes are required because IE won't resize the body automatically when the map is rendered.
<!DOCTYPE html>
<html style="height:100%">
    <head>
    </head>
    <body style="height:100%">
    </body>
</html>

Now add the DIV that will host the map into the body. Do not use the shorthand "/>" because of a bug in IE8. Note that when you bind a Google Maps mapping or visualization object to an HTML element all the child nodes of the element are deleted.
<div id="map_canvas" style="width:100%; height:100%"></div>

Add the references to Google's mapping API. We are only using basic functions so we only need the basic library. Extended functionality like charting is available through other libraries. The example below goes into the head and makes anonymous requests to Google's servers. If you have a gmail account you can get a key which you pass by adding a key=xxxxxx parameter. The key lets you make more requests and also track your usage.
<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>

Now let's display a map of the Yorba Linda regional park. The simplest way to do this is with latitude and longitude. To do this we create a function called initialize which defines a dictionary of map options and then creates the map using the hosting DIV and the map options. This script goes immediately after the script tag above.
<script type="text/javascript">
function initialize()
{ var mapOptions = {center:new google.maps.LatLng(33.86, -117.76),
            zoom: 14,
            mapTypeId: google.maps.MapTypeId.ROADMAP}
  var divCanvas = document.getElementById("map_canvas");
  map = new google.maps.Map(divCanvas, mapOptions);
}
</script>

Now all we have to do is call initialize once the page has loaded. Add an attribute to the body tag so it looks like this.
<body style="height:100%" onload="initialize()">

You can now run the page. You will see a Google Map of Yorba Linda which you can pan, zoom, change layers, even go to street level. Not bad for a little javascript. Let's add an event handler now so we can see the latitude and longitude at the mouse location.

Add the following line of code as the last line in the initialize function. It tells the map object to call a new function called showlatlong whenever the mouse moves over the map.
google.maps.event.addListener(map, "mousemove", showlatlong);

Add the following function after the initialize function. It will be our mousemove event handler. Note that all event handlers receive an event argument.
function showlatlong(event) {
  var latitude = Math.round(event.latLng.lat() * 10000) / 10000;
  var longitude = Math.round(event.latLng.lng() * 10000) / 10000;
  window.status= 'Lat: ' + latitude + ', Long: ' + longitude;
}

Now run the mouse over the page again and note that the longitude and latitude are displayed in the window's status bar. The last thing I'll cover is reverse Geocoding which determines the nearest street address to a latitude/longitude. It is an asynchronous call which is common in Google Maps. This means we have to assign a callback function that is called when the results are available.

We start by declaring a geocoding object. Add the following line of code before the initialize function.
var geocoder=new google.maps.Geocoder();

Now we add a new event handler as the last line of the initialize function.
google.maps.event.addListener(map, "click", showaddress);

Because reverse geocaching is asynchronous all we do in the event handler is make a request to Google Maps and tell it where to send the results. The geocode method takes a dictionary of options just like the map method.
function showaddress(event) {
  geocoder.geocode({ 'latLng': event.latLng }, showGeocodeResults);
}

All Google Maps callbacks receive a results object and a status string. The geocode results are an array of address objects. The first address is the most specific but may not be populated (if you clicked on the North Pole you may not get a street address). We iterate through the array until we find an entry with a populated address property.
function showGeocodeResults(results, status)
{ if (status == google.maps.GeocoderStatus.OK)
  { for (var i = 0; i < results.length; i++)
    { if (results[i].formatted_address != undefined)
      { window.status = results[i].formatted_address;
        return;
      }
    }
  }
}

Try running the page again and click on the map. You will see the nearest mailing address displayed in the window's status bar. Because this is an asynchronous call there will be a slight pause before the result comes back.

There are some good tutorials at http://code.google.com/apis/maps/documentation/javascript/tutorial.html

Thursday, February 23, 2012

Niterider MiNewt 600 review

Let's start with the good. This is an awesome little light. It's the ultimate flashlight - suitable for camping, hiking, power outages, and of course cycling. It's high-beam setting lights up the road or trail. Consider that a single car headlight (dipped) puts out about 1000 lumen and this light has a maximum output of 600 lumen you can guess how bright it is. Bring on the gnarly single-track. The beam pattern is excellent, just like all other MiNewt's I own (total 4). It's smooth, with no bright or dark spots, perfectly round, and gradually brightens in the middle.

It's has a lot of modes including 4 solid, 1 flashing, and 2 off modes (yes two). All are accessed with a single button. I could see being a bit brain-addled near the end of a long ride and getting confused.

The locked-off mode is a good idea. I can see that the button could easily get pressed while transporting the light in a gym bag. Best thing to do is carry it in the box it came in. I heard some people returned the light saying it didn't work because it ships in this mode. That's pretty retarded.

The flashing mode is too bright. I would use it in daylight especially around dawn or dusk but it's so bright that it causes distracting reflections on all sorts of stuff.

The walk mode is actually bright enough to ride at 15mph with and is possibly the most useful mode for me.

I test rode it last night on the brightest setting and it switched to low mode about 80 minutes into the ride. That's exactly what it was supposed to do.

Now for the bad.
The mount sucks. That's really the only word for it. I've never seen a light mount that didn't have some kind of force-multiplier to get a really good grip on the handlebar. Cateye uses a screw thread, Planet Bike uses a cam/lever, but this mount only grips the handlebar as hard as you can squeeze it. The light will not stay pointing in the direction I set it. Every couple of miles it droops down and I have to readjust it. The pavement was pretty smooth too. Off road would be dreadful. I have a 25.4mm handlebar - I've no idea if it's better on 31.8mm.

Perhaps a couple of turns of electrical tape would help but at $100+ I expect a much better mount.

Tuesday, February 21, 2012

Which is most important - Life or Cycling?

Easy question? Maybe not so easy for Kyle Hewitt who withdrew from the World Cycle Racing World Tour (http://www.worldcycleracing.com/) only 40km into a round-the-world race on Saturday. Even though he's only twenty-five, Kyle is almost a professional fund raiser and has a long history of raising money for charities with his cycling exploits. I'm sure when he signed up for this race he had no idea he would have a new wife and child by the time he reached the start line.

How hard must it have been for him to start this race and how hard also to abandon in favor of his new family. There's no doubt Kyle would have been a serious competitor and that the race will be diminished by his absence. There's also no doubt in my mind that he, eventually, did the right thing. Like the reluctant bride turning away at the altar - better late than never.

Your day will come, my friend, never doubt that.

Monday, February 20, 2012

World Cycle Race

102 miles on the 'bent yesterday. Strong headwinds and mild tailwinds (always the way, isn't it?). Total time including stops was 6:45.

Grand tour (ride around the world) started yesterday. They have a great site at http://worldcycleracing.com/ which is well worth a look. I see Sean Conway has bucked the trend and is heading south into Africa. The other riders are all in Europe. They're all hoping to beat the current world record of 106 days. They are required to cycle at least 18,000 miles in that time which works out to 170 miles a day for over 3 months. I feel so inadequate.

Saturday, February 18, 2012

One man and his bike

I've started re-reading Mike Carter's "One Man and His Bike" on my Kindle Fire. It really is a great book. I was trying to guestimate his route around the UK on ridewithgps.com, but the site crashed at 2,500 miles. The elevation profile around Scotland really is quite sick but the street level views were amazing. It's not until you start looking at the trip in detail that you realize just how tough and modest Mike must be.

'Bent century tomorrow. Possibility of showers. Such fun.