Agile App Co.

Adding Highlighted Routes from Polyline Google API to Xamarin Forms Map

When we were tasked with showing a map with a route highlighted on it, we initially went to the Xamarin articles you've no doubt come across already. The issue with these was that it didn't seem to show you where you might get the route coordinates from but just gave some arbirtrary values that they used.

We wanted to know given point A and point B where could you source the necessary route coordinates from in order to display a highlighted route on a map using the Xamarin.Forms.Maps plugin.

In summary here is how to do it...

 

Step one

Follow the instructions on the xamarin tutorial https://developer.xamarin.com/recipes/cross-platform/xamarin-forms/maps/...

Step two 

Link up to the Google API

You'll need the directions API and we wrote some code to help us, I won't post it all so you'll have to do some of the work yourself.

 public async Task<WayPoints> GetWayPointsAsync(string FromAddress, string ToAddress)
        {             
            var d = await GetClient<WayPoints>("directions/json?origin=" + FromAddress + "&destination=" + ToAddress);
            return d;
        }
The GetClient is a simple function we wrote to deserialize a json response from an http request. The way points class is a class that we used the "Paste Json as Classes" feature from Visual Studio to paste in json data from the Google API documentation.
 
Step Three
Now that you have a way of getting points between two addresses you'll need to do something clever. In the response from Google you'll get an encoded string which contains all the coordinates required to create an overlay on the maps. We found a functoin to decode this which we've copied here.
 
public static List<Position> DecodePolyline(string encodedPoints)
        {
            if (string.IsNullOrEmpty(encodedPoints))
                throw new ArgumentNullException("encodedPoints");
 
            char[] polylineChars = encodedPoints.ToCharArray();
            int index = 0;
 
            int currentLat = 0;
            int currentLng = 0;
            int next5bits;
            int sum;
            int shifter;
 
            List<Position> polylinesPosition = new List<Position>();
 
            while (index < polylineChars.Length)
            {
                // calculate next latitude
                sum = 0;
                shifter = 0;
                do
                {
                    next5bits = (int)polylineChars[index++] - 63;
                    sum |= (next5bits & 31) << shifter;
                    shifter += 5;
                } while (next5bits >= 32 && index < polylineChars.Length);
 
                if (index >= polylineChars.Length)
                    break;
 
                currentLat += (sum & 1) == 1 ? ~(sum >> 1) : (sum >> 1);
 
                //calculate next longitude
                sum = 0;
                shifter = 0;
                do
                {
                    next5bits = (int)polylineChars[index++] - 63;
                    sum |= (next5bits & 31) << shifter;
                    shifter += 5;
                } while (next5bits >= 32 && index < polylineChars.Length);
 
                if (index >= polylineChars.Length && next5bits >= 32)
                    break;
 
                currentLng += (sum & 1) == 1 ? ~(sum >> 1) : (sum >> 1);
 
                polylinesPosition.Add(new Position(Convert.ToDouble(currentLat) / 1E5, Convert.ToDouble(currentLng) / 1E5));
            }
 
            return (polylinesPosition);
        }
Step Four
Assign the coordinates you get back from the decode function to the instance of map you have in your Xamarin Forms project.
For us it looked like this but you'll need to adapt it for your project
 var ways = await GoogleService.Current.GetWayPointsAsync(await Trip.StartDetails?.GetAddressAsync(), await Trip.EndDetails?.GetAddressAsync());
            foreach (var way in ways.routes)
            {                
                customMap.RouteCoordinates = GoogleService.DecodePolyline(way.overview_polyline.points);
........
 
Step Five
Alter the code you did in the Xamarin tutorial to update the routes once you've added the way points in the previous step.
In your forms project where your custom map class is add an event to it which you can handle in the renderers on each platform.
    public event EventHandler RouteUpdated;
        public virtual void OnRouteUpdated(EventArgs e)
        {
            RouteUpdated?.Invoke(this, e);
        }
 
        public List<Position> RouteCoordinates { get; set; }
       
The renderer for android looks like this: note that that the if statement is redundant because I was testing out another method here. The code should enter the else statement
 
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<View> e)
        {
            base.OnElementChanged(e);
 
            if (e.OldElement != null)
            {
                map.InfoWindowClick -= OnInfoWindowClick;
            }
 
            if (e.NewElement != null)
            {
                formsMap = (CustomMap)e.NewElement;
                customPins = formsMap.CustomPins;
                routeCoordinates = formsMap.RouteCoordinates;
                ((MapView)Control).GetMapAsync(this);
 
                formsMap.PinsUpdated += FormsMap_PinsUpdated;
                formsMap.RouteUpdated += FormsMap_RouteUpdated;
            }
        }
 
        private void FormsMap_RouteUpdated(object sender, EventArgs e)
        {
            //Routes
            if (!string.IsNullOrEmpty(formsMap.encodedPolyLine))
            {
                var polylineOptions = new PolylineOptions();
                polylineOptions.InvokeColor(System.Drawing.Color.Pink.ToArgb());
                foreach (var position in DecodePolylinePoints(formsMap.encodedPolyLine))
                {
                    polylineOptions.Add(position);
                }
 
                map.AddPolyline(polylineOptions);
                
            }
            else {
                routeCoordinates = formsMap.RouteCoordinates;
                SetRoutePolyline();
            }
 
        }
 
 
 

Additional uncaught exception thrown while handling exception.

Original

PDOException: SQLSTATE[HY000]: General error: 2006 MySQL server has gone away: SELECT 1 AS expression FROM {variable} variable WHERE ( (name = :db_condition_placeholder_0) ); Array ( [:db_condition_placeholder_0] =&gt; cron_last ) in variable_set() (line 1245 of C:\inetpub\wwwroot\agileapp.co\public_html\includes\bootstrap.inc).

Additional

PDOException: SQLSTATE[HY000]: General error: 2006 MySQL server has gone away: SELECT af.* FROM {advagg_files} af WHERE (filename_hash IN (:db_condition_placeholder_0, :db_condition_placeholder_1, :db_condition_placeholder_2, :db_condition_placeholder_3, :db_condition_placeholder_4, :db_condition_placeholder_5, :db_condition_placeholder_6, :db_condition_placeholder_7, :db_condition_placeholder_8, :db_condition_placeholder_9, :db_condition_placeholder_10, :db_condition_placeholder_11, :db_condition_placeholder_12, :db_condition_placeholder_13, :db_condition_placeholder_14, :db_condition_placeholder_15, :db_condition_placeholder_16, :db_condition_placeholder_17, :db_condition_placeholder_18, :db_condition_placeholder_19)) ; Array ( [:db_condition_placeholder_0] =&gt; vNguyKT44qzCV0N-b6VsRqkKDuhES_FNB5ugGBriTwg [:db_condition_placeholder_1] =&gt; x8PgrxCbaLgfo-8ZT8UEB8muQrso7qduUUoXklAvvvo [:db_condition_placeholder_2] =&gt; 9LcmovGG6jrOJH7cREym7ZHQRpixpXjR2NJypdTIBjg [:db_condition_placeholder_3] =&gt; JTUZMjGi5QBMtdy5S7Bonuo9E1qezkYQpCtWPx9MQJY [:db_condition_placeholder_4] =&gt; d2b-ZidA1utVuLSNMPwQUJ2LYg5MrnCcKxXACnHNayI [:db_condition_placeholder_5] =&gt; kGJvWQ1Qdwrh1Wng3oQ5KlCbPryE_OtnSD-vB7G5USE [:db_condition_placeholder_6] =&gt; o56IIqheUwcVqXJphxxx7iPKRzPSdVP2oGcwZt-XHr0 [:db_condition_placeholder_7] =&gt; BxZKcQWoHAAoulDfN4la1lMDGKWhm2lMwuvFJhHvRWI [:db_condition_placeholder_8] =&gt; J1c4S7yjwt5aU7q6YiL0136TFXNts_bbdQKe8-ykiNU [:db_condition_placeholder_9] =&gt; p4THTI3jz4x7Sd9tKn2jYnmig3tOMY7rK9oUZ3WV60s [:db_condition_placeholder_10] =&gt; KyM8A1lAciQEUNqsghADR4OxfzEqlOjs3zM8WMIbrSk [:db_condition_placeholder_11] =&gt; Gv9QBwlCYC5mW8Env7xoJGLdAZimhU9KpEIUHa8YGK4 [:db_condition_placeholder_12] =&gt; VLNAjgbZVTmopjO6fEvBkRRshTpyQJuO7v5j2iU7zIw [:db_condition_placeholder_13] =&gt; aAZdapCdQCD4TLIjl2MThytuD2_GzdJ-UjpwfCzwIHA [:db_condition_placeholder_14] =&gt; HJewk3gc_INoYhF4KgChZ51KMCtlm91IZ2wn1EtFpyo [:db_condition_placeholder_15] =&gt; E6NR-Ndd4mZTNhlebABSJnv14Gu41v9hkzryGAPcS4U [:db_condition_placeholder_16] =&gt; ikttF_g5DBgAJZzsAjiU11MdEoVHAIoKwgty198ndSk [:db_condition_placeholder_17] =&gt; vi7nqAbMiij5EVs47rA2t1k0ToowpXfdpk-1-wa7XYk [:db_condition_placeholder_18] =&gt; cI0iuvEPpPBWpIn5M1-PP9mZhgocO7c2CG64LpKtOSc [:db_condition_placeholder_19] =&gt; QLfy7zLRNJt50prNMqAEpA0JrlAHgl2NB6iWTvVqzmc ) in advagg_insert_update_files() (line 171 of C:\inetpub\wwwroot\agileapp.co\public_html\sites\all\modules\advagg\advagg.inc).


Uncaught exception thrown in shutdown function.

PDOException: SQLSTATE[HY000]: General error: 2006 MySQL server has gone away: DELETE FROM {semaphore} WHERE (value = :db_condition_placeholder_0) ; Array ( [:db_condition_placeholder_0] =&gt; 8332624865c9ae3bd159d33.50396114 ) in lock_release_all() (line 269 of C:\inetpub\wwwroot\agileapp.co\public_html\includes\lock.inc).