Custom Overlays

  • This guide focuses on building custom map overlays using the Google Maps JavaScript API's OverlayView class, enabling dynamic, map-tied elements.

  • Developers learn to create overlays by subclassing OverlayView, defining a constructor, and implementing methods like onAdd(), draw(), and onRemove() for lifecycle management.

  • The guide details how to control overlay visibility through hide(), show(), and toggle() methods, impacting their appearance on the map.

  • It demonstrates adding interactive controls, like buttons, to the map interface, allowing users to directly manipulate the custom overlay's behavior, such as toggling visibility or DOM attachment.

  • Complete sample code in both TypeScript and JavaScript is provided to illustrate the entire process, offering a practical example for building image-based custom overlays.

Select platform: Android iOS JavaScript

Introduction

Overlays are objects on the map that are tied to latitude/longitude coordinates, so they move when you drag or zoom the map. For information on predefined overlay types, see Drawing on the map.

The Maps JavaScript API provides an OverlayView class for creating your own custom overlays. The OverlayView is a base class that provides several methods you must implement when creating your overlays. The class also provides a few methods that make it possible to translate between screen coordinates and locations on the map.

Add a custom overlay

Here is a summary of the steps required to create a custom overlay:

  • Set your custom overlay object's prototype to a new instance of google.maps.OverlayView(). In effect, this will subclass the overlay class.
  • Create a constructor for your custom overlay, and set any initialization parameters.
  • Implement an onAdd() method within your prototype, and attach the overlay to the map. OverlayView.onAdd() will be called when the map is ready for the overlay to be attached.
  • Implement a draw() method within your prototype, and handle the visual display of your object. OverlayView.draw() will be called when the object is first displayed.
  • You should also implement an onRemove() method to clean up any elements you added within the overlay.

Below are more details on each step. You can see the full, working example code: view example code.

Subclass the overlay

The example below uses OverlayView to create a simple image overlay.

Now we create a constructor for the USGSOverlay class, and initialize the passed parameters as properties of the new object.

TypeScript

/**
 * The custom USGSOverlay object contains the USGS image,
 * the bounds of the image, and a reference to the map.
 */
class USGSOverlay extends google.maps.OverlayView {
  private bounds: google.maps.LatLngBounds;
  private image: string;
  private div?: HTMLElement;

  constructor(bounds: google.maps.LatLngBounds, image: string) {
    super();

    this.bounds = bounds;
    this.image = image;
  }

JavaScript

/**
 * The custom USGSOverlay object contains the USGS image,
 * the bounds of the image, and a reference to the map.
 */
class USGSOverlay extends google.maps.OverlayView {
  bounds;
  image;
  div;
  constructor(bounds, image) {
    super();
    this.bounds = bounds;
    this.image = image;
  }

We can't yet attach this overlay to the map in the overlay's constructor. First, we need to ensure that all of the map's panes are available, because they specify the order in which objects are displayed on a map. The API provides a helper method indicating this has occurred. We'll handle that method in the next section.

Initialize the overlay

When the overlay is first instantiated and ready to display, we need to attach it to the map via the browser's DOM. The API indicates that the overlay has been added to the map by invoking the overlay's onAdd() method. To handle this method we create a <div> to hold our image, add an <img> element, attach it to the <div>, and then attach the overlay to one of the map's panes. A pane is a node within the DOM tree.

The panes, of type MapPanes, specify the stacking order for different layers on the map. The following panes are available, and are enumerated in the order in which they are stacked from bottom to top:

  • mapPane is the lowest pane and is above the tiles. It may not receive DOM events. (Pane 0).
  • overlayLayer contains polylines, polygons, ground overlays and tile layer overlays. It may not receive DOM events. (Pane 1).
  • markerLayer contains markers. It may not receive DOM events. (Pane 2).
  • overlayMouseTarget contains elements that receive DOM events. (Pane 3).
  • floatPane contains the info window. It is above all map overlays. (Pane 4).

Because our image is a "ground overlay," we'll use the overlayLayer pane. When we have that pane, we'll attach our object to it as a child.

TypeScript

/**
 * onAdd is called when the map's panes are ready and the overlay has been
 * added to the map.
 */
onAdd() {
  this.div = document.createElement("div");
  this.div.style.borderStyle = "none";
  this.div.style.borderWidth = "0px";
  this.div.style.position = "absolute";

  // Create the img element and attach it to the div.
  const img = document.createElement("img");

  img.src = this.image;
  img.style.width = "100%";
  img.style.height = "100%";
  img.style.position = "absolute";
  this.div.appendChild(img);

  // Add the element to the "overlayLayer" pane.
  const panes = this.getPanes()!;

  panes.overlayLayer.appendChild(this.div);
}

JavaScript

/**
 * onAdd is called when the map's panes are ready and the overlay has been
 * added to the map.
 */
onAdd() {
  this.div = document.createElement("div");
  this.div.style.borderStyle = "none";
  this.div.style.borderWidth = "0px";
  this.div.style.position = "absolute";

  // Create the img element and attach it to the div.
  const img = document.createElement("img");

  img.src = this.image;
  img.style.width = "100%";
  img.style.height = "100%";
  img.style.position = "absolute";
  this.div.appendChild(img);

  // Add the element to the "overlayLayer" pane.
  const panes = this.getPanes();

  panes.overlayLayer.appendChild(this.div);
}

Draw the overlay

Note that we haven't invoked any special visual display in the code above. The API invokes a separate draw() method on the overlay whenever it needs to draw the overlay on the map, including when first added.

We'll therefore implement this draw() method, retrieve the overlay's MapCanvasProjection using getProjection() and calculate the exact coordinates at which to anchor the object's top right and bottom left points. Then we can resize the <div>. In turn this will resize the image to match the bounds we specified in the overlay's constructor.

TypeScript

draw() {
  // We use the south-west and north-east
  // coordinates of the overlay to peg it to the correct position and size.
  // To do this, we need to retrieve the projection from the overlay.
  const overlayProjection = this.getProjection();

  // Retrieve the south-west and north-east coordinates of this overlay
  // in LatLngs and convert them to pixel coordinates.
  // We'll use these coordinates to resize the div.
  const sw = overlayProjection.fromLatLngToDivPixel(
    this.bounds.getSouthWest()
  )!;
  const ne = overlayProjection.fromLatLngToDivPixel(
    this.bounds.getNorthEast()
  )!;

  // Resize the image's div to fit the indicated dimensions.
  if (this.div) {
    this.div.style.left = sw.x + "px";
    this.div.style.top = ne.y + "px";
    this.div.style.width = ne.x - sw.x + "px";
    this.div.style.height = sw.y - ne.y + "px";
  }
}

JavaScript

draw() {
  // We use the south-west and north-east
  // coordinates of the overlay to peg it to the correct position and size.
  // To do this, we need to retrieve the projection from the overlay.
  const overlayProjection = this.getProjection();
  // Retrieve the south-west and north-east coordinates of this overlay
  // in LatLngs and convert them to pixel coordinates.
  // We'll use these coordinates to resize the div.
  const sw = overlayProjection.fromLatLngToDivPixel(
    this.bounds.getSouthWest(),
  );
  const ne = overlayProjection.fromLatLngToDivPixel(
    this.bounds.getNorthEast(),
  );

  // Resize the image's div to fit the indicated dimensions.
  if (this.div) {
    this.div.style.left = sw.x + "px";
    this.div.style.top = ne.y + "px";
    this.div.style.width = ne.x - sw.x + "px";
    this.div.style.height = sw.y - ne.y + "px";
  }
}

Remove a custom overlay

We also add an onRemove() method to cleanly remove the overlay from the map.

TypeScript

/**
 * The onRemove() method will be called automatically from the API if
 * we ever set the overlay's map property to 'null'.
 */
onRemove() {
  if (this.div) {
    (this.div.parentNode