Finding the Starting Point of a Route
Nico Bontenbal shares a method for determining the starting point of a route by examining the pixels in the map image using Window API calls
For some time I have tried to find a way to determine the coordinates of the starting point of route segments. I want to be able to send these coordinates to my basic Garmin GPS. I've written code that sends the points of a polyline to my GPS, but I always have to trace a route by hand because MapPoint only returns the middle of a route segment and not the start.
The 'Reverse geocoding, another method' article gave me hope that by using the extrapolation technique I would be able to find the starting points of all the route segments. This works for rather straight route segments, but on a roundabout this does not work; the middle of the route isn't in the middle of the start and end of the segment. While exploring this technique I came up with a completely different solution. I found out that when you select a route segment, a label with the instruction appears on the map at the start of the segment as shown in the images below.
On an enlargement you can see the individual pixels of the map:
I found out that by using the GetPixel and GetBitmapBits API calls I could analyze the map bitmap and find the location of this label and therefore the starting point of a route segment. There are many disadvantages to this technique. It is slow and the different maps are 'flashing' over the screen. Also I have to improve the technique further because it is not working 100%. Nonetheless, because I saw in the forum that I'm not the only one who wanted to solve this problem, I decided to share the technique.
When you use this code:
objmap.ActiveRoute.Directions(intSegment).Select
objmap.ActiveRoute.Directions(intSegment).Location.GoTo
objmap.Altitude = objmap.Altitude * 2
MapPoint wil display a map something like the one in the example above. By using quite a complicated set of API-calls it is possible to scan this map for the location of the label. By using the XYToLocation function these coordinates can be transferred into a Mappoint location object.
I've written a GetRouteSegmentStartLocation function (download code) that does just this. It accepts a MapPoint map object and the number of the route segment and displays the route segment on screen.
The function then uses the GetBitmapBits API-call (along with some others) to transfer the map into an array. After this it is easy to retrieve the colours of the individual pixels of the map.
The map is 'traced' for the 'signature' of the top of a label. I've defined the signature as: a black pixel with a white pixel 1 and 14 pixels below it, and a black pixel 15 pixels below it. This is a very simple definition of the 'signature' but it works most of the times. Improving the signature is one of the areas of the technique that needs improvement.
When the top of a label is found the function scans the bitmap to the left to find the left border of the caption. When it finds the left border the start of the route segment (white dot) is 4 pixels to the left and 4 pixels to the top of the upper left corner of the label.
For performance reasons the bitmap is not scanned line by line. I found that most of the time the label is near the middle of the map so the map is first scanned at the middle. If a caption is not found there, the map it scanned at 3/4 and 1/4 of it’s width, then at 7/8, 5/8, 3/8 and 1/8 etc. until the label is found.
When you put all this together you get the code example you see at the end of this article. The Main procedure loops through all the segments of a route and puts a pushpin at the start of all of them.
To use this example copy the code into a VB(A) project and set a reference to MapPoint. Then start Mappoint and create a route. After this the Main procedure in the code can be run. Ideveloped and test the code in the European version of MapPoint, and it works with the US version as well.
As I said before, the signature of the label needs improvement. This can be done by adding more rules to the signature. For instance, when scanning to the left to find the left border all pixels in between should be whiteor at least 10 pixels to the left or to the right of the top of the label should be black (with white pixels below), etc.
I found that sometimes when the route is selected, the label is not displayed on the map. This is why the GetRouteSegmentStartLocation function stops scanning the map after it has scanned 64 lines;after this it assumes there is no label on the map.
When the selected route segment is large, the map is zoomed out to show the entire segment. This way the XYToLocation function is getting less accurate. This can be improved by scanning the map again after the (inaccurate) location is found, but this time at maximum zoom.
If anyone improves this example, please report these improvements back to MP2Kmag. I hope together we can make this code really useable (perhaps even in a production environment).
Also I can imagine other uses for the 'scan the map pixel by pixel technique' such as the ability to trace a complete route, a road, or a postal code area.