Displaying a round-the-world itinerary using Google Maps – Part 4

In the first parts of this series we saw a basic, embedded map and then added information windows and labels. In this post we're going to overlay data being recorded by a tracking device to show our progress in real-time.

Tracked in Toronto

This was made pretty easy thanks to Garmin's MapShare service: our Iridium-connected inReach Explorer+ device publishes tracking information automagically to our MapShare page, which has a KML feed that can be embedded as an overlay on Google Maps. The additional code, below, is trivial. It's possible to zoom into the location of the latest data on map load, but I've chosen to leave it as is: the information is there for people to zoom into, of they choose, but we've chosen the option to "preserve the viewport", otherwise.

Here's the updated code:

<html>

  <head>

    <style>

      html,

      body {

        padding: 0;

        margin: 0;

      }

      #map {

        height: 100%;

        width: 100%;

        overflow: hidden;

        float: left;

        border: thin solid #333;

      }

      h3 {

        margin: 0 0 5px 0;

        white-space: nowrap;

        overflow: hidden;

        text-overflow: ellipsis;

      }

      p {

        margin: 0 0 10px 0;

        white-space: nowrap;

        overflow: hidden;

        text-overflow: ellipsis;

      }

    </style>

  </head>

  <body>

    <div id="map"></div>

    <script async defer src="https://maps.googleapis.com/maps/api/js?key=[ENTER_YOUR_KEY_HERE]&libraries=places&callback=initMap"></script>

    <script type='text/javascript'>

      var map;

      var infowindow;

      var service;

      var overlay = 'https://inreach.garmin.com/feed/Share/mondeEnPoche?d1=2017-07-01T00:00Z&d2=2018-01-10T00:00Z';

 

      // Data for the markers consisting of a name, a LatLng and a zIndex for the

      // order in which these markers should display on top of each other.

      var stops = [

        ['Marin-Epagnier', 'ChIJt4RhN0UJjkcR9dxtPHTvIRY', 47.0091808, 7.0015896, 1, 'Start & End'],

        ['Washington', 'ChIJW-T2Wt7Gt4kRKl2I1CJFUsI', 38.9071923, -77.0368707, 2],

        ['New York', 'ChIJOwg_06VPwokRYv534QaPC8g', 40.7127837, -74.0059413, 3],

        ['West Hartford', 'ChIJ_RQEifWs54kRfDtRDlPX-Wc', 41.7620842, -72.7420151, 4],

        ['Boston', 'ChIJGzE9DS1l44kRoOhiASS_fHg', 42.3600825, -71.0588801, 5, 'July'],

        ['Toronto', 'ChIJpTvG15DL1IkRd8S0KlBVNTI', 43.653226, -79.3831843, 6],

        ['Bozeman', 'ChIJE4i6T0xERVMRqmA792TQ9WM', 45.6769979, -111.0429339, 7],

        ['Yellowstone National Park', 'ChIJVVVVVVXlUVMRu-GPNDD5qKw', 44.427963, -110.588455, 8],

        ['Grand Teton National Park', 'ChIJqRtdyZ5RUlMRN6ORzI64oKU', 43.7904282, -110.6817627, 9],

        ['Salt Lake City', 'ChIJ7THRiJQ9UocRyjFNSKC3U1s', 40.7607793, -111.8910474, 10],

        ['Bryce Canyon', 'ChIJbUw47h9pNYcRYv1Jemw3nHU', 37.6283161, -112.1676947, 11],

        ['Zion National Park', 'ChIJ2fhEiNDqyoAR9VY2qhU6Lnw', 37.2982022, -113.0263005, 12],

        ['Las Vegas', 'ChIJ0X31pIK3voARo3mz1ebVzDo', 36.1699412, -115.1398296, 13],

        ['Death Valley', 'ChIJsf-PHqI5x4ARJd0j14NziRw', 36.5322649, -116.9325408, 14],

        ['Sequoia National Park', 'ChIJeWUZLX37v4ARZPQen_nfCkQ', 36.4863668, -118.5657516, 15],

        ['Big Sur', 'ChIJVVikTfuPjYARYuO38cfXpRY', 36.2704212, -121.807976, 16],

        ['Monterey', 'ChIJkfu1cFLkjYARXj1K2AlJSO4', 36.6002378, -121.8946761, 17],

        ['San Francisco', 'ChIJIQBpAG2ahYAR_6128GcTUEo', 37.7749295, -122.4194155, 18, 'August'],

        ['Lima', 'ChIJ3-EpLOzDBZERRBEzku1Ooak', -12.0463667, -77.0427891, 19],

        ['Machu Picchu', 'ChIJVVVViV-abZERJxqgpA43EDo', -13.1631412, -72.5449629, 20],

        ['Cusco', 'ChIJMYRZJtjVbZERXTEYI8yWqSo', -13.53195, -71.9674626, 21],

        ['São Paulo', 'ChIJ0WGkg4FEzpQRrlsz_whLqZs', -23.5505199, -46.6333094, 22],

        ['Rio de Janeiro', 'ChIJW6AIkVXemwARTtIvZ2xC3FA', -22.9068467, -43.1728965, 23, 'September'],

        ['Iguazu Falls', 'ChIJbRuqowzq9pQRfphenBd1e5E', -25.695259, -54.4388549, 24],

        ['Córdoba', 'ChIJaVuPR1-YMpQRkrBmU5pPorA', -31.4200833, -64.1887761, 25],

        ['Parque Provincial Ischigualasto', 'ChIJwynmBT3sgpYR0J11F_1O5cw', -30.167266,-67.9860327, 26],

        ['Parque Nacional Talampaya', 'ChIJUUxbf6rPgpYRaEkBxpGDANQ', -29.8906226, -67.853468, 27],

        ['Catamarca', 'ChIJzZ8PHb8oJJQRGoYJFkvdHn4', -28.469581, -65.7795441, 28],

        ['San Miguel de Tucumán', 'ChIJA2nF1pI3IpQRJ2XFtZJbjfg', -26.8082848, -65.2175903, 29],

        ['Salta', 'ChIJ-bdRUaPDG5QRBvKH1SyZzaU', -24.7821269, -65.4231976, 30],

        ['Salar de Uyuni', 'ChIJh9rdHuC6_5MRkFuFng0T5RI', -20.1595348, -67.4054025, 31],

        ['San Pedro de Atacama', 'ChIJP78qqXpMqJYR0Zf5rExh9Ho', -22.9087073, -68.1997156, 32],

        ['Pan de Azúcar National Park', 'ChIJM6BM4cewvJYRbC7GcVat_6U', -26.177565, -70.5495396, 33],

        ['Raúl Marine Balmaceda', 'ChIJ4V-JqObIkZYRiGptmZGVUn8', -29.9695076, -71.3416309, 34],

        ['Santiago', 'ChIJuzrymgbQYpYRl0jtCfRZnYc', -33.4378305, -70.6504492, 35],

        ['Easter Island', 'ChIJK67UqBfwR5kRti0qwO2z5bs', -27.112723, -109.3496865, 36],

        ['Tahiti', 'ChIJTddtfNB1GHQREVfDCXp6wJs', -17.6509195, -149.4260421, 37],

        ['Auckland', 'ChIJ--acWvtHDW0RF5miQ2HvAAU', -36.8484597, 174.7633315, 38],

        ['Rotorua', 'ChIJK7L2gj2Ybm0RMZmjQ2HvAAU', -38.1368478, 176.2497461, 39, 'October'],

        ['Wellington', 'ChIJy3TpSfyxOG0RcLQTomPvAAo', -41.2864603, 174.776236, 40],

        ['Paparoa National Park', 'ChIJbZoxICBxJW0RIPF5hIbvAAU', -42.1632433, 171.366731, 41],

        ['Queenstown', 'ChIJX96o1_Ed1akRAKZ5hIbvAAU', -45.0311622, 168.6626435, 42],

        ['Sydney', 'ChIJP5iLHkCuEmsRwMwyFmh9AQU', -33.8688197, 151.2092955, 43],

        ['Brisbane', 'ChIJM9KTrJpXkWsRQK_e81qjAgQ', -27.4697707, 153.0251235, 44],

        ['Cairns', 'ChIJEySiW1VieGkRYHggf_HuAAQ', -16.9185514, 145.7780548, 45],

        ['Kuala Lumpur', 'ChIJ5-rvAcdJzDERfSgcL1uO2fQ', 3.139003, 101.686855, 46],

        ['Singapore', 'ChIJdZOLiiMR2jERxPWrUs9peIg', 1.352083, 103.819836, 47],

        ['Coimbatore', 'ChIJtRyXL69ZqDsRgtI-GB7IwS8', 11.0168445, 76.9558321, 48],

        ['Kodaikanal', 'ChIJhwMKf2NmBzsRPMFYNzfp-p8', 10.2381136, 77.4891822, 49],

        ['Bangalore', 'ChIJbU60yXAWrjsR4E9-UejD3_g', 12.9715987, 77.5945627, 50, 'November'],

        ['Durban', 'ChIJt2G8AQCq9x4RgW6qxEZVp8w', -29.8586804, 31.0218404, 51],

        ['Lesotho', 'ChIJ64xf1idIjB4Rsx7ReLhXLSM', -29.609988, 28.233608, 52, 'December'],

        ['Addo Elephant National Park', 'ChIJY2nuzYRPex4RCsT--8cm454', -33.4833333, 25.75, 53],

        ['Tsitsikamma', 'ChIJaTwmTQ5ueR4R5_kNGLX6RBs', -32.2178721, 26.5772048, 54],

        ['Knysna', 'ChIJ2QwBlkDqeB4Rzc5QdeG5Kr4', -34.0350856, 23.0464693, 55],

        ['Oudtshoorn', 'ChIJtRO16obB1R0RYesIjnRHQ40', -33.6007225, 22.2026347, 56],

        ['Franschhoek', 'ChIJz7IFaAe9zR0R-bJW01SGtDw', -33.8974833, 19.1523292, 57],

        ['Stellenbosch', 'ChIJpeKIUfeyzR0R4mvj3gCqCXA', -33.9321045, 18.860152, 58],

        ['Cape Town', 'ChIJ1-4miA9QzB0Rh6ooKPzhf2g', -33.9248685, 18.4240553, 59]

      ];

 

      var labels = [

        ['Start & End', 47.0091808, 7.0015896, 1],

        ['July', 42.409143, -102.280372, 2],

        ['August', 5.247246, -73.979869, 3],

        ['September', -36.753594, -65.018287, 4],

        ['October', -33.622306, 160.985311, 5],

        ['November', 16.921484, 91.724302, 6],

        ['December', -16.011953, 23.167125, 7]

      ];

 

      function initMap() {

        map = new google.maps.Map(document.getElementById('map'), {

          center: new google.maps.LatLng(15, -30),

          zoom: 2,

          mapTypeId: 'satellite'

        });

        infowindow = new google.maps.InfoWindow();

        service = new google.maps.places.PlacesService(map);

 

        loadJS('./maplabel-compiled.js', onInit, document.body);

      }

 

      function loadJS(url, implementationCode, location){

        var scriptTag = document.createElement('script');

        scriptTag.src = url;

        scriptTag.onload = implementationCode;

        scriptTag.onreadystatechange = implementationCode;

        location.appendChild(scriptTag);

      };

 

      function onInit(){

        setMarkers(map);

 

        var kmlOverlayer = new google.maps.KmlLayer(overlay, {

          suppressInfoWindows: true,

          preserveViewport: true,

          map: map

        });

      }

 

      function setMarkers(map) {

 

        // Adds markers to the map with a delay

 

        var delay = 100;

        for (var i = 0; i <= stops.length; i++) {

          var timeout = i * delay;

 

          // If this is the last segment, just add the line

 

          if (i === stops.length) {

            addConnectingLineWithTimeout(stops[i - 1], stops[0], timeout + delay);

          } else if (i >= 0) {

 

            // Otherwise add a marker after a delay, followed by the

            // connecting line to the previous marker, if there is one

 

            addMarkerWithTimeout(stops[i], timeout);

            if (i > 0) {

              addConnectingLineWithTimeout(stops[i], stops[i - 1], timeout + delay);

            }

          }

        }

      }

 

      function addMarkerWithTimeout(stop, timeout) {

        setTimeout(function() {

          var marker = new google.maps.Marker({

            map: map,

            title: stop[0],

            placeId: stop[1],

            position: {

              lat: stop[2],

              lng: stop[3]

            },

            label: stop[4].toString(),

            zIndex: stop[4]

            //animation: google.maps.Animation.DROP, // Cool but too much

          });

 

          // If we have a label listed, find out which and add it to the map

 

          if (stop.length > 5) {

            var idx = labels.findIndex(function(val) {

              return val[0] === stop[5];

            });

            if (idx >= 0) {

              var label = labels[idx];

              addLabelWithTimeout(label[1], label[2], label[0], 0);

            }

          }

 

          // Register the callback for when the marker is clicked

 

          google.maps.event.addListener(marker, 'click', function() {

            onItemClick(event, marker);

          });

        }, timeout);

      }

 

      function addLabelWithTimeout(lat, long, text, timeout) {

        setTimeout(function() {

          var pos = new google.maps.LatLng(lat, long);

          var mapLabel = new MapLabel({

            text: text,

            position: pos,

            map: map,

            fontSize: 14

          });

          mapLabel.set('position', new google.maps.LatLng(lat, long));

        }, timeout);

      }

 

      function addConnectingLineWithTimeout(stop1, stop2, timeout) {

        setTimeout(function() {

          var flightPath = new google.maps.Polyline({

            path: [{

              lat: stop1[2],

              lng: stop1[3]

            }, {

              lat: stop2[2],

              lng: stop2[3]

            }],

            geodesic: true,

            strokeColor: '#D34038',

            strokeOpacity: 1.0,

            strokeWeight: 4

          });

 

          flightPath.setMap(map);

        }, timeout);

      }

 

      // Info window trigger function

 

      function onItemClick(event, pin) {

        service.getDetails({

          placeId: pin.placeId

        }, function(place, status) {

          var cont =

            '<div><h3>' + place.name + '</h3><p>' + place.formatted_address + '</p>' +

            (place.photos && place.photos.length > 0 ?

              ('<img src="' +

                place.photos[0].getUrl({

                  'maxWidth': 300,

                  'maxHeight': 200

                }) + '" />') : '') +

            '</div>'

          infowindow.setContent(cont);

          infowindow.open(map, pin);

        });

      }

    </script>

  </body>

</html>

 

Here's how it looks when we zoom in on an area with tracking data (Toronto, which is where I am right now):

Our map with the added tracking overlay

Here's the embedded map for you to try yourself.

 

 

You can always find the embedded map on our website, and be sure to subscribe to our Instagram feed if you want to keep up-to-date with what we're up to (this is where we're posting most regularly, it turns out).

Tomorrow we're leaving Toronto (and Canada) to head across to Montana and Yellowstone Park. From there we'll be travelling through the US, camping in various National Parks, which should be quite the adventure. We'll see!

4 responses to “Displaying a round-the-world itinerary using Google Maps – Part 4”

  1. Rgulf Constant Avatar
    Rgulf Constant

    it is nice to visit this site.
    nuwair.com/

  2. Very interesting series, thanks from a Swiss fellow. I could not quite make it work yet, but I still have a few monthes to study the code and experiment.

  3. FYI I had to change the inReach URL to get it working : from
    " rel="nofollow noopener" target="_blank" title="https://inreach.garmin.com/feed/Share/">https://inreach.garmin.com/......
    to
    " rel="nofollow noopener" target="_blank" title="https://share.garmin.com/feed/Share/">https://share.garmin.com/fe...... (as specified in the Social tab of the Garmin Explore platform)

    Also, maplabel-compiled.js gave me some trouble, downloading and using maplabel.js instead did solve the issue.

    1. Hi Philippe,

      You're very welcome - glad if it's been of help.

      Many thanks for letting people know the changes needed to get it working.

      All the best on your trip(s)!

      Kean

Leave a Reply

Your email address will not be published. Required fields are marked *