POI Click Events

This example demonstrates the use of click event listeners on POIs (points of interest). It listens for the click event on a POI icon and then uses the placeId from the event data with a directionsService.route request to calculate and display a route to the clicked place. It also uses the placeId to get more details of the place.

Read the documentation.

TypeScript

function initMap(): void {
  const origin = { lat: -33.871, lng: 151.197 };

  const map = new google.maps.Map(
    document.getElementById("map") as HTMLElement,
    {
      zoom: 18,
      center: origin,
    }
  );
  new ClickEventHandler(map, origin);
}

function isIconMouseEvent(
  e: google.maps.MapMouseEvent | google.maps.IconMouseEvent
): e is google.maps.IconMouseEvent {
  return "placeId" in e;
}

class ClickEventHandler {
  origin: google.maps.LatLngLiteral;
  map: google.maps.Map;
  directionsService: google.maps.DirectionsService;
  directionsRenderer: google.maps.DirectionsRenderer;
  placesService: google.maps.places.PlacesService;
  infowindow: google.maps.InfoWindow;
  infowindowContent: HTMLElement;
  constructor(map: google.maps.Map, origin: google.maps.LatLngLiteral) {
    this.origin = origin;
    this.map = map;
    this.directionsService = new google.maps.DirectionsService();
    this.directionsRenderer = new google.maps.DirectionsRenderer();
    this.directionsRenderer.setMap(map);
    this.placesService = new google.maps.places.PlacesService(map);
    this.infowindow = new google.maps.InfoWindow();
    this.infowindowContent = document.getElementById(
      "infowindow-content"
    ) as HTMLElement;
    this.infowindow.setContent(this.infowindowContent);

    // Listen for clicks on the map.
    this.map.addListener("click", this.handleClick.bind(this));
  }

  handleClick(event: google.maps.MapMouseEvent | google.maps.IconMouseEvent) {
    console.log("You clicked on: " + event.latLng);

    // If the event has a placeId, use it.
    if (isIconMouseEvent(event)) {
      console.log("You clicked on place:" + event.placeId);

      // Calling e.stop() on the event prevents the default info window from
      // showing.
      // If you call stop here when there is no placeId you will prevent some
      // other map click event handlers from receiving the event.
      event.stop();

      if (event.placeId) {
        this.calculateAndDisplayRoute(event.placeId);
        this.getPlaceInformation(event.placeId);
      }
    }
  }

  calculateAndDisplayRoute(placeId: string) {
    const me = this;
    this.directionsService.route(
      {
        origin: this.origin,
        destination: { placeId: placeId },
        travelMode: google.maps.TravelMode.WALKING,
      },
      (response, status) => {
        if (status === "OK") {
          me.directionsRenderer.setDirections(response);
        } else {
          window.alert("Directions request failed due to " + status);
        }
      }
    );
  }

  getPlaceInformation(placeId: string) {
    const me = this;
    this.placesService.getDetails(
      { placeId: placeId },
      (
        place: google.maps.places.PlaceResult | null,
        status: google.maps.places.PlacesServiceStatus
      ) => {
        if (
          status === "OK" &&
          place &&
          place.geometry &&
          place.geometry.location
        ) {
          me.infowindow.close();
          me.infowindow.setPosition(place.geometry.location);
          (me.infowindowContent.children[
            "place-icon"
          ] as HTMLImageElement).src = place.icon as string;
          (me.infowindowContent.children[
            "place-name"
          ] as HTMLElement).textContent = place.name!;
          (me.infowindowContent.children[
            "place-id"
          ] as HTMLElement).textContent = place.place_id as string;
          (me.infowindowContent.children[
            "place-address"
          ] as HTMLElement).textContent = place.formatted_address as string;
          me.infowindow.open(me.map);
        }
      }
    );
  }
}

JavaScript

function initMap() {
  const origin = { lat: -33.871, lng: 151.197 };
  const map = new google.maps.Map(document.getElementById("map"), {
    zoom: 18,
    center: origin,
  });
  new ClickEventHandler(map, origin);
}

function isIconMouseEvent(e) {
  return "placeId" in e;
}

class ClickEventHandler {
  constructor(map, origin) {
    this.origin = origin;
    this.map = map;
    this.directionsService = new google.maps.DirectionsService();
    this.directionsRenderer = new google.maps.DirectionsRenderer();
    this.directionsRenderer.setMap(map);
    this.placesService = new google.maps.places.PlacesService(map);
    this.infowindow = new google.maps.InfoWindow();
    this.infowindowContent = document.getElementById("infowindow-content");
    this.infowindow.setContent(this.infowindowContent);
    // Listen for clicks on the map.
    this.map.addListener("click", this.handleClick.bind(this));
  }
  handleClick(event) {
    console.log("You clicked on: " + event.latLng);

    // If the event has a placeId, use it.
    if (isIconMouseEvent(event)) {
      console.log("You clicked on place:" + event.placeId);
      // Calling e.stop() on the event prevents the default info window from
      // showing.
      // If you call stop here when there is no placeId you will prevent some
      // other map click event handlers from receiving the event.
      event.stop();

      if (event.placeId) {
        this.calculateAndDisplayRoute(event.placeId);
        this.getPlaceInformation(event.placeId);
      }
    }
  }
  calculateAndDisplayRoute(placeId) {
    const me = this;
    this.directionsService.route(
      {
        origin: this.origin,
        destination: { placeId: placeId },
        travelMode: google.maps.TravelMode.WALKING,
      },
      (response, status) => {
        if (status === "OK") {
          me.directionsRenderer.setDirections(response);
        } else {
          window.alert("Directions request failed due to " + status);
        }
      }
    );
  }
  getPlaceInformation(placeId) {
    const me = this;
    this.placesService.getDetails({ placeId: placeId }, (place, status) => {
      if (
        status === "OK" &&
        place &&
        place.geometry &&
        place.geometry.location
      ) {
        me.infowindow.close();
        me.infowindow.setPosition(place.geometry.location);
        me.infowindowContent.children["place-icon"].src = place.icon;
        me.infowindowContent.children["place-name"].textContent = place.name;
        me.infowindowContent.children["place-id"].textContent = place.place_id;
        me.infowindowContent.children["place-address"].textContent =
          place.formatted_address;
        me.infowindow.open(me.map);
      }
    });
  }
}

CSS

/* Always set the map height explicitly to define the size of the div
       * element that contains the map. */
#map {
  height: 100%;
}

/* Optional: Makes the sample page fill the window. */
html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
}

.title {
  font-weight: bold;
}

#infowindow-content {
  display: none;
}

#map #infowindow-content {
  display: inline;
}

HTML

<!DOCTYPE html>
<html>
  <head>
    <title>POI Click Events</title>
    <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
    <link rel="stylesheet" type="text/css" href="./style.css" />
    <script src="./index.js"></script>
  </head>
  <body>
    <div id="map"></div>
    <div id="infowindow-content">
      <img id="place-icon" src="" height="16" width="16" />
      <span id="place-name" class="title"></span><br />
      Place ID <span id="place-id"></span><br />
      <span id="place-address"></span>
    </div>

    <!-- Async script executes immediately and must be after any DOM elements used in callback. -->
    <script
      src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap&libraries=places&v=weekly"
      async
    ></script>
  </body>
</html>

All

<!DOCTYPE html>
<html>
  <head>
    <title>POI Click Events</title>
    <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
    <style type="text/css">
      /* Always set the map height explicitly to define the size of the div
       * element that contains the map. */
      #map {
        height: 100%;
      }

      /* Optional: Makes the sample page fill the window. */
      html,
      body {
        height: 100%;
        margin: 0;
        padding: 0;
      }

      .title {
        font-weight: bold;
      }

      #infowindow-content {
        display: none;
      }

      #map #infowindow-content {
        display: inline;
      }
    </style>
    <script>
      function initMap() {
        const origin = { lat: -33.871, lng: 151.197 };
        const map = new google.maps.Map(document.getElementById("map"), {
          zoom: 18,
          center: origin,
        });
        new ClickEventHandler(map, origin);
      }

      function isIconMouseEvent(e) {
        return "placeId" in e;
      }

      class ClickEventHandler {
        constructor(map, origin) {
          this.origin = origin;
          this.map = map;
          this.directionsService = new google.maps.DirectionsService();
          this.directionsRenderer = new google.maps.DirectionsRenderer();
          this.directionsRenderer.setMap(map);
          this.placesService = new google.maps.places.PlacesService(map);
          this.infowindow = new google.maps.InfoWindow();
          this.infowindowContent = document.getElementById(
            "infowindow-content"
          );
          this.infowindow.setContent(this.infowindowContent);
          // Listen for clicks on the map.
          this.map.addListener("click", this.handleClick.bind(this));
        }
        handleClick(event) {
          console.log("You clicked on: " + event.latLng);

          // If the event has a placeId, use it.
          if (isIconMouseEvent(event)) {
            console.log("You clicked on place:" + event.placeId);
            // Calling e.stop() on the event prevents the default info window from
            // showing.
            // If you call stop here when there is no placeId you will prevent some
            // other map click event handlers from receiving the event.
            event.stop();

            if (event.placeId) {
              this.calculateAndDisplayRoute(event.placeId);
              this.getPlaceInformation(event.placeId);
            }
          }
        }
        calculateAndDisplayRoute(placeId) {
          const me = this;
          this.directionsService.route(
            {
              origin: this.origin,
              destination: { placeId: placeId },
              travelMode: google.maps.TravelMode.WALKING,
            },
            (response, status) => {
              if (status === "OK") {
                me.directionsRenderer.setDirections(response);
              } else {
                window.alert("Directions request failed due to " + status);
              }
            }
          );
        }
        getPlaceInformation(placeId) {
          const me = this;
          this.placesService.getDetails(
            { placeId: placeId },
            (place, status) => {
              if (
                status === "OK" &&
                place &&
                place.geometry &&
                place.geometry.location
              ) {
                me.infowindow.close();
                me.infowindow.setPosition(place.geometry.location);
                me.infowindowContent.children["place-icon"].src = place.icon;
                me.infowindowContent.children["place-name"].textContent =
                  place.name;
                me.infowindowContent.children["place-id"].textContent =
                  place.place_id;
                me.infowindowContent.children["place-address"].textContent =
                  place.formatted_address;
                me.infowindow.open(me.map);
              }
            }
          );
        }
      }
    </script>
  </head>
  <body>
    <div id="map"></div>
    <div id="infowindow-content">
      <img id="place-icon" src="" height="16" width="16" />
      <span id="place-name" class="title"></span><br />
      Place ID <span id="place-id"></span><br />
      <span id="place-address"></span>
    </div>

    <!-- Async script executes immediately and must be after any DOM elements used in callback. -->
    <script
      src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap&libraries=places&v=weekly"
      async
    ></script>
  </body>
</html>
function initMap() { const origin = { lat: -33.871, lng: 151.197 }; const map = new google.maps.Map(document.getElementById("map"), { zoom: 18, center: origin, }); new ClickEventHandler(map, origin); } function isIconMouseEvent(e) { return "placeId" in e; } class ClickEventHandler { constructor(map, origin) { this.origin = origin; this.map = map; this.directionsService = new google.maps.DirectionsService(); this.directionsRenderer = new google.maps.DirectionsRenderer(); this.directionsRenderer.setMap(map); this.placesService = new google.maps.places.PlacesService(map); this.infowindow = new google.maps.InfoWindow(); this.infowindowContent = document.getElementById("infowindow-content"); this.infowindow.setContent(this.infowindowContent); // Listen for clicks on the map. this.map.addListener("click", this.handleClick.bind(this)); } handleClick(event) { console.log("You clicked on: " + event.latLng); // If the event has a placeId, use it. if (isIconMouseEvent(event)) { console.log("You clicked on place:" + event.placeId); // Calling e.stop() on the event prevents the default info window from // showing. // If you call stop here when there is no placeId you will prevent some // other map click event handlers from receiving the event. event.stop(); if (event.placeId) { this.calculateAndDisplayRoute(event.placeId); this.getPlaceInformation(event.placeId); } } } calculateAndDisplayRoute(placeId) { const me = this; this.directionsService.route( { origin: this.origin, destination: { placeId: placeId }, travelMode: google.maps.TravelMode.WALKING, }, (response, status) => { if (status === "OK") { me.directionsRenderer.setDirections(response); } else { window.alert("Directions request failed due to " + status); } } ); } getPlaceInformation(placeId) { const me = this; this.placesService.getDetails({ placeId: placeId }, (place, status) => { if ( status === "OK" && place && place.geometry && place.geometry.location ) { me.infowindow.close(); me.infowindow.setPosition(place.geometry.location); me.infowindowContent.children["place-icon"].src = place.icon; me.infowindowContent.children["place-name"].textContent = place.name; me.infowindowContent.children["place-id"].textContent = place.place_id; me.infowindowContent.children["place-address"].textContent = place.formatted_address; me.infowindow.open(me.map); } }); } }
/* Always set the map height explicitly to define the size of the div * element that contains the map. */ #map { height: 100%; } /* Optional: Makes the sample page fill the window. */ html, body { height: 100%; margin: 0; padding: 0; } .title { font-weight: bold; } #infowindow-content { display: none; } #map #infowindow-content { display: inline; }
<!DOCTYPE html> <html> <head> <title>POI Click Events</title> <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script> <!-- jsFiddle will insert css and js --> </head> <body> <div id="map"></div> <div id="infowindow-content"> <img id="place-icon" src="" height="16" width="16" /> <span id="place-name" class="title"></span><br /> Place ID <span id="place-id"></span><br /> <span id="place-address"></span> </div> <!-- Async script executes immediately and must be after any DOM elements used in callback. --> <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg&callback=initMap&libraries=places&v=weekly" async ></script> </body> </html>