2

The goal:

  • Display a map with some markers (from an array or file)
  • Let users search for a place/address
  • Mark the place on the map
  • Determine what marker is closest to the place
  • Draw & display directions from the marker to the place
  • Update the directions if a new search is performed

I think I am close. I've cobbled together some code from the maps API documentation & other questions here on Stack Overflow. I have it correctly marking the place that's searched and determining the closest marker, but am having trouble getting it to update the direction markers & drawing the path.

Is something wrong with the onChangeHandler or the listener that should trigger it?

This example from Google Maps API Documentation is where I was looking to for the last part with the listener, to update & draw new directions.

Eventually I intend to add the steps as seen on another Google Maps API sample

 function initMap() {

  var map;
  var myOptions = {
   zoom: 14,
   center: {lat: 40.7484, lng: -73.9857},
   scrollwheel: false,
   mapTypeId: 'roadmap'
  };

  // map for search & directions
  map = new google.maps.Map(document.getElementById('map'), myOptions);

  // create initial origin marker
  // to-do: error if not defined
  var markerA = new google.maps.Marker({
   position: {lat: 40.7484, lng: -73.9857},
   map: map,
  });

  // create array of locations and assign values
  var locs = [];
  locs[0] = ['Location 1 Name', '40.73297438','-73.97860823'];
  locs[1] = ['Location 2 Name', '40.72824765','-73.98219971'];
  locs[2] = ['Location 3 Name', '40.73181838','-73.97871015'];

  var infowindow = new google.maps.InfoWindow;
  var loc, i;
  var locations = [];

  // create markers for each loc and place them on map
  for (i = 0; i < locs.length; i++) {
   loc = new google.maps.Marker({
    position: new google.maps.LatLng(locs[i][1], locs[i][2]),
    map: map
   });

   google.maps.event.addListener(loc, 'click', (function(loc, i)  {
    return function() {
     infowindow.setContent(locs[i][0]);
     infowindow.open(map, loc);
    }
   })(loc, i));
   locations.push(loc.position);
  }

  // Create the search box and link it to the UI element.
  var input = document.getElementById('pac-input');
  var searchBox = new google.maps.places.SearchBox(input);
  map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);

  // Bias the SearchBox results towards current map's viewport.
  map.addListener('bounds_changed', function () {
   searchBox.setBounds(map.getBounds());
  });

  var markers = [];
  // Listen for the event fired when the user selects a prediction and 
  // retrieve more details for that place.
  searchBox.addListener('places_changed', function () {
   var places = searchBox.getPlaces();

   if (places.length == 0) {
    return;
   }

   // Clear out the old markers.
   markers.forEach(function (marker) {
    marker.setMap(null);
   });
   markers = [];

   // For each place, get the icon, name and location.
   var bounds = new google.maps.LatLngBounds();
   places.forEach(function (place) {
    if (!place.geometry) {
     console.log("Returned place contains no geometry");
     return;
    }
    var icon = {
     url: place.icon,
     size: new google.maps.Size(71, 71),
     origin: new google.maps.Point(0, 0),
     anchor: new google.maps.Point(17, 34),
     scaledSize: new google.maps.Size(25, 25)
    };

    // update the origin, markerA, and add to map
    markerA.setPosition(place.geometry.location);
    markerA.setIcon(icon);
    markerA.setTitle(place.name);
    markers.push(markerA);

    if (place.geometry.viewport) {
     // Only geocodes have viewport.
     bounds.union(place.geometry.viewport);
    } else {
     bounds.extend(place.geometry.location);
    }

    });
   map.fitBounds(bounds);
   map.setOptions({
    zoom: 17
   });
  });

  var pointA = markerA.getPosition();
  var markerB;

  findClosest(pointA, locations);

  // improved garage search to determine closest location
  function findClosest(pointA, locations) {
   var closestDistance = Number.MAX_SAFE_INTEGER;
   var closest;
   for (i = 0; i < locations.length; i++) {
    var distance = google.maps.geometry.spherical.computeDistanceBetween(pointA, locations[i]);
    if (distance < closestDistance) {
     closestDistance = distance;
     closest = locations[i];
    }
   }
   markerB = closest;
   // alert(closest);
  }


  var directionsService = new google.maps.DirectionsService;
  var directionsDisplay = new google.maps.DirectionsRenderer({
   map: map
  });

  var pointB = markerB;

  calculateAndDisplayRoute(directionsService, directionsDisplay, pointA, pointB, map);

  var onChangeHandler = function() {
   calculateAndDisplayRoute(directionsService, directionsDisplay, pointA, pointB, map);
  };
  map.addListener('change', onChangeHandler);

 }

 function calculateAndDisplayRoute(directionsService, directionsDisplay, pointA, pointB, map) {
     directionsService.route({
     origin: pointA,
     destination: pointB,
     travelMode: google.maps.TravelMode.WALKING
     }, function (response, status) {
     if (status == google.maps.DirectionsStatus.OK) {
     directionsDisplay.setDirections(response);
     } else {
     window.alert('Could not load directions due to ' + status);
     }
     });
 }
#pac-input {
  background-color: #ffffff;
  font-family: Roboto;
  font-size: 15px;
  font-weight: 300;
  margin-left: 12px;
  margin-top: 9px;
  padding: 5px 11px 5px 13px;
  text-overflow: ellipsis;
  width: 400px;
}
#map { height: 475px; }
<input type="text" id="pac-input" class="controls" placeholder="Where are you headed?" /></p>
<div id="map">&nbsp;</div>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyC8MQrdVaz8JKiUnSU0usHzGRQGrI_x3DQ&amp;callback=initMap&amp;libraries=geometry,places" async="" defer="defer" type="text/javascript"></script>
  • 1
    what would be your starting point ? – Searching Apr 18 '17 at 20:52
  • @Searching - the starting point would be `markerB`, the end point would be `markerA`. `markerB` is whatever location is closest to `markerA` which is defined by the user/input. Initially I gave a value to `markerA` because it would otherwise cause an undefined error. That will be cleaned up, but I wanted to get the functionality running. – whatsupdock Apr 18 '17 at 22:16

1 Answers1

2

There are multiple issues, given that it's cooked up from the web. The main thing to note here is that I've updated/split the requirements into readable functions (as much as possible). I do not pass initMap as callback but this should not be an issue. No attempt was made to validate or optimize the code. Just small fixes.

  1. We setup the map,
  2. setup the initial data,
  3. setup controls and finally
  4. call the FindClosest.

var map;
var locs = [];
var markers = [];
var headToPoint = {};
var input = document.getElementById('pac-input');
var searchBox = {};
var directionsService = new google.maps.DirectionsService();
var directionsDisplay = {};

var initMap = function() {

  var myOptions = {
    zoom: 14,
    center: {
      lat: 40.7484,
      lng: -73.9857
    },
    scrollwheel: false,
    mapTypeId: 'roadmap'
  };

  map = new google.maps.Map(document.getElementById('map'), myOptions);
  directionsDisplay = new google.maps.DirectionsRenderer({
    map: map
  });

  AddInitialMarkers();
  SetupInputsToMap();
  FindClosestDirection();
}

function AddInitialMarkers() {

  headToPoint = new google.maps.Marker({
    position: {
      lat: 40.7484,
      lng: -73.9857
    },
    map: map,
  });

  locs[0] = ['Location 1 Name', '40.73297438', '-73.97860823'];
  locs[1] = ['Location 2 Name', '40.72824765', '-73.98219971'];
  locs[2] = ['Location 3 Name', '40.73181838', '-73.97871015'];

  var infowindow = new google.maps.InfoWindow;
  var loc;
  for (i = 0; i < locs.length; i++) {
    loc = new google.maps.Marker({
      position: new google.maps.LatLng(locs[i][1], locs[i][2]),
      map: map
    });

    google.maps.event.addListener(loc, 'click', (function(loc, i) {
      return function() {
        infowindow.setContent(locs[i][0]);
        infowindow.open(map, loc);
      }
    })(loc, i));
    markers.push(loc)
  }
}

function SetupInputsToMap() {
  searchBox = new google.maps.places.SearchBox(input);
  map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);
  map.addListener('bounds_changed', function() {
    searchBox.setBounds(map.getBounds());
  });
  searchBox.addListener('places_changed', new_place_selected);
}

function new_place_selected() {

  var places = searchBox.getPlaces();
  if (places.length == 0) {
    return;
  }
  // For each place, get the icon, name and location.
  var bounds = new google.maps.LatLngBounds();
  places.forEach(function(place) {
    if (!place.geometry) {
      console.log("Returned place contains no geometry");
      return;
    }
    var icon = {
      url: place.icon,
      size: new google.maps.Size(71, 71),
      origin: new google.maps.Point(0, 0),
      anchor: new google.maps.Point(17, 34),
      scaledSize: new google.maps.Size(25, 25)
    };

    headToPoint.setPosition(place.geometry.location);
    headToPoint.setIcon(icon);
    headToPoint.setTitle(place.name);
    markers.push(headToPoint);

    if (place.geometry.viewport) {
      // Only geocodes have viewport.
      bounds.union(place.geometry.viewport);
    } else {
      bounds.extend(place.geometry.location);
    }

  });
  map.fitBounds(bounds);
  map.setOptions({
    zoom: 17
  });
  FindClosestDirection();
}

var FindClosestDirection = function() {

  var closestDistance = Number.MAX_SAFE_INTEGER;
  var closest;

  for (i = 0; i < markers.length; i++) {
    if (markers[i].position !== headToPoint.getPosition()) {
      var distance = google.maps.geometry.spherical.computeDistanceBetween(headToPoint.getPosition(), markers[i].position);
      if (distance < closestDistance) {
        closestDistance = distance;
        closest = markers[i];
      }
    }
  }
  calculateAndDisplayRoute(directionsService, directionsDisplay, headToPoint.getPosition(), closest.position);

}

function calculateAndDisplayRoute(directionsService, directionsDisplay, pointA, pointB) {
  directionsService.route({
    origin: pointA,
    destination: pointB,
    travelMode: 'WALKING'
  }, function(response, status) {
    if (status == google.maps.DirectionsStatus.OK) {
      directionsDisplay.setDirections(response);
    } else {
      window.alert('Could not load directions due to ' + status);
    }
  });
}
initMap();
#pac-input {
  background-color: #ffffff;
  font-family: Roboto;
  font-size: 15px;
  font-weight: 300;
  margin-left: 12px;
  margin-top: 9px;
  padding: 5px 11px 5px 13px;
  text-overflow: ellipsis;
  width: 400px;
}

#map {
  height: 475px;
}
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyC8MQrdVaz8JKiUnSU0usHzGRQGrI_x3DQ&amp;libraries=geometry,places" type="text/javascript"></script>

<input type="text" id="pac-input" class="controls" placeholder="Where are you headed?" />
<div id="map">&nbsp;</div>

Let us know.

Searching
  • 2,269
  • 1
  • 17
  • 28
  • The snippet works great. As noted, it could be improved but the functionality is working. One thing to note, I had to adjust my page to load the local js file **after** the Google Maps API script was loaded. http://stackoverflow.com/questions/14229695/google-maps-api-throws-uncaught-referenceerror-google-is-not-defined-only-whe – whatsupdock Apr 20 '17 at 15:33
  • This does not work in Internet Explorer - was testing in IE11. It works in the new "Edge" browser, though. I noticed in the console it says `Unable to get property 'position' of undefined or null reference` at line 128. This is closest.position which isn't set before a place is searched, but it doesn't work after either. Can the map load without the points set, or not run the calculate & display until a place is searched? – whatsupdock Apr 25 '17 at 22:40
  • Ahaa... Browser issues.. don't we all like it.... Given that the error is related to getting details from an `undefined`, just check if it has a value before executing. It is possible that the location is still waiting to be populated by load/get. Would be good to check if there are any network errors in loading content/scripts etc...@whatsupdock – Searching Apr 25 '17 at 22:57
  • IE11 and older seem to have issues with Number.MAX_SAFE_INTEGER, despite [saying they do here](https://learn.microsoft.com/en-us/scripting/javascript/reference/number-constants-javascript). I tested it using the actual value (according to MS docs: 9007199254740991) and it works. Is there another constant you'd suggest using here? – whatsupdock Apr 27 '17 at 21:52
  • Given that it's just the starting point to measure the closest distance, you could use [max_value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_VALUE) supported on all major browsers.@whatsupdock – Searching Apr 27 '17 at 22:21
  • Nice job, it helped me a lot. Do you know how to make it that "B" marker will get information from array from loc[] ? – mat Mar 17 '20 at 13:50