A year on, the new and improved version of what started as (and I suppose, still is) a learning exercise in Erlang.

I had this idea quite early on after finishing the first version, but, owing to a change in commute route, I thought it would actually be pretty useful to implement it. The first version just assumed a single overall heading/direction of travel and reported out quite simply whether that meant a headwind or a tailwind. The old commute wasn't exactly a straight line from A to B, but was much more so than the new commute. With the new commute, assuming a single heading or direction is a little more misleading. My plan was quite simple: for a route compile a list of headings and distances and for a given wind direction work out whether the majority of the distance was a headwind or tailwind.

I was somewhat aware of OSRM from when I was trying to further de-googlefy my life and, after reading the API notes, I thought that would be the best option as the route_instructions returned would contain everything I need: a list of compass directions and distances.

Making the HTTP query was just an adaptation of what I was already doing for the Met Office API, although it is important to specify a real User Agent with the OSRM API. For the time being I'm writing the response out to a text file and then reading this back in (to avoid having to query for the same route all the time). The OSRM API returns JSON so I used Jiffy over any of the other alternatives as it seems like the most "native" solution (since it's based on EEP-0018).

However, then I realised that the OSRM API doesn't actually seem to return the route instructions (at least not that I could figure out, perhaps it does so only for certain countries?), all I got was:

{
    "alternative_names":[["",""]],
    "route_name":["",""],
    "alternative_indices":[0,000],
    "hint_data":{
        "locations":[
            "...",
            "..."
        ],
        "checksum":000000000
    },
    "alternative_geometries":["..."],
    "found_alternative":true,
    "alternative_summaries":[
        {
            "end_point":"Some Place",
            "start_point":"",
            "total_time":0000,
            "total_distance":00000
        }
    ],
    "via_points":[
        [00.000000,-0.00000],
        [00.000000,-0.000000]
    ],
    "route_summary":{
        "end_point":"Some Place",
        "start_point":"",
        "total_time":0000,
        "total_distance":00000
    },
    "route_geometry":"...",
    "status_message":"Found route between points",
    "via_indices":[0,000],
    "status":0
}

compared to the documented output.

Which meant I then had the unplanned detour into the land of Polyline decoding so I could use the route geometry in lieu of the instructions. Once decoded though, this was ideal as the points are all offsets rather than absolute co-ordinates which could be recursed over, performing some simple trigonometry/Pythagorus on the way:

    Distance = math:sqrt(math:pow(Lat,2) + math:pow(Lon,2)),
    Heading_signed = math:atan2(Lon, Lat),

to get my list of distances and headings. Once this was in place, headings (after being converted from a signed value to a value between 0 and 2π) could be converted to compass directions:

get_compass_direction_for(Heading) ->
    Segment = 2*math:pi()/16,
    Segments = erlang:round(Heading/Segment)+1,
    Compass = ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"],
    %Handle the case of Segments = 16. Need to wrap around.
    nth_wrap(Segments, Compass).

(Whilst doing this I found a really stupid bug in how I'd implemented nth_wrap. I think it's just luck that I hadn't been affected by this over the past year).

And these could then be compared to the wind direction in a manner very similar to version 1 (whereas originally I took the direction of travel and used that to build groups of directions that would be head, side or tail winds, this time round the direction of travel can vary so I used the wind direction to build the groups of directions; I'm still only using one weather location as even I'm not cycling that far that the wind speed or direction will be wildly different between journey points). The list was then split out into three, one containing headwind distances, one containing sidewind distances and one containing tailwind distances. I did this by mapping a filter over the aforementioned groups of directions of head, side and tail winds, but there could well be a nicer way:

[Headwind_distances, Sidewind_distances, Tailwind_distances] = lists:map(
    fun(Wind_type_filter) ->
        lists:filter(
            fun({_Distance, Wind_type}) ->
                Wind_type == Wind_type_filter
            end,
            Distances_and_wind_type_list)
    end,
    [headwind, sidewind, tailwind]),

Finally I could sum these up using a fold to see which contained the greatest distance:

[Sum_of_headwind_distances, Sum_of_sidewind_distances, Sum_of_tailwind_distances] = lists:map(
    fun(Wind_type) ->
        lists:foldl(
            fun({Distance, _Wind}, Sum) ->
                Distance + Sum
            end,
            0,
            Wind_type)
    end,
    [Headwind_distances, Sidewind_distances, Tailwind_distances]),

There are lots of further improvements that I can make over the next year (if I don't get distracted by something else):

  • The OSRM API is only half implemented. There is a list of items that clients are "supposed to" implement.
  • Using postcodes for start and finish locations as opposed to the latitudes and longitudes.
  • Automatically using the start or end location to set the weather location instead of having to determine and specify that separately.
  • Figuring out how to handle different weather locations for theoretically longer journeys.