Nakładki niestandardowe

Wybierz platformę: Android iOS JavaScript

Wprowadzenie

Nakładki to obiekty na mapie powiązane ze współrzędnymi geograficznymi, które przesuwają się, gdy przeciągasz mapę lub ją powiększasz. Informacje o wstępnie zdefiniowanych typach nakładek znajdziesz w artykule Rysowanie na mapie.

Interfejs Maps JavaScript API udostępnia klasę OverlayView do tworzenia własnych niestandardowych nakładek. Klasa OverlayView to klasa podstawowa, która udostępnia kilka metod, które musisz zaimplementować podczas tworzenia nakładek. Klasa ta udostępnia też kilka metod, które umożliwiają przekształcanie współrzędnych ekranu na pozycje na mapie i odwrotnie.

Dodawanie niestandardowego nakładki

Oto podsumowanie czynności wymaganych do utworzenia niestandardowego nakładki:

  • Ustaw wartość prototype niestandardowego obiektu nakładki na nową instancję google.maps.OverlayView(). W efekcie spowoduje to utworzenie podklasy klasy overlay.
  • Utwórz konstruktor nakładki niestandardowej i ustaw parametry inicjalizacji.
  • W prototypie zastosuj metodę onAdd() i dołącz nakładkę do mapy. OverlayView.onAdd() zostanie wywołana, gdy mapa będzie gotowa do dołączenia nakładki.
  • W prototypie zaimplementuj metodę draw() i zarządzaj wizualnym wyświetlaniem obiektu. Funkcja OverlayView.draw() jest wywoływana, gdy obiekt jest wyświetlany po raz pierwszy.
  • Musisz też zaimplementować metodę onRemove(), aby usunąć wszystkie elementy dodane w nakładce.

Poniżej znajdziesz więcej informacji o każdym kroku. Pełny działający przykładowy kod możesz zobaczyć, klikając Pokaż przykładowy kod.

Tworzenie podklasy nakładki

W przykładzie poniżej użyto elementu OverlayView do utworzenia prostego nakładania obrazu.

Teraz tworzymy konstruktor klasy USGSOverlay i inicjujemy przekazane parametry jako właściwości nowego obiektu.

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;
  }

Nie możemy jeszcze dołączyć tego nakładki do mapy w konstruktorze nakładki. Po pierwsze musimy się upewnić, że wszystkie panele mapy są dostępne, ponieważ określają one kolejność wyświetlania obiektów na mapie. Interfejs API udostępnia metodę pomocniczą, która informuje o wystąpieniu takiego błędu. Tę metodę omówimy w następnym rozdziale.

Inicjowanie nakładki

Gdy nakładka zostanie po raz pierwszy zainicjowana i będzie gotowa do wyświetlenia, musimy ją dołączyć do mapy za pomocą DOM przeglądarki. Interfejs API informuje, że nakładka została dodana do mapy przez wywołanie metody onAdd() nakładki. Aby zastosować tę metodę, tworzymy element <div>, który będzie przechowywać obraz, dodajemy element <img>, dołączamy go do elementu <div>, a potem dołączamy nakładkę do jednej z płytek mapy. Panel to węzeł w drzewie DOM.

Panele typu MapPanes określają kolejność nakładania różnych warstw na mapie. Dostępne są te karty, które są wymienione w kolejności od dołu do góry:

  • mapPane to najniższa kolumna nad kafelkami. Może nie otrzymywać zdarzeń DOM. (panel 0).
  • overlayLayer zawiera polilinie, wielokąty, nakładki na powierzchnię i nakładki na warstwę kafelków. Może nie otrzymywać zdarzeń DOM. (panel 1).
  • markerLayer zawiera znaczniki. Może nie otrzymywać zdarzeń DOM. (panel 2).
  • overlayMouseTarget zawiera elementy, które odbierają zdarzenia DOM. (panel 3).
  • floatPane zawiera okno z informacjami. Znajduje się ona nad wszystkimi nakładkami mapy. (panel 4).

Nasz obraz jest „nakładką na ziemię”, więc użyjemy panelu overlayLayer. Gdy mamy już tę kartę, możemy dołączyć do niej obiekt jako element podrzędny.

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);
}

Rysowanie nakładki

Pamiętaj, że w powyższym kodzie nie wywołaliśmy żadnego specjalnego wizualnego wyświetlenia. API wywołuje oddzielną metodę draw() na nakładce, gdy musi narysować nakładkę na mapie, w tym po jej pierwszym dodaniu.

Dlatego zastosujemy metodę draw(), aby za pomocą funkcji getProjection() pobrać MapCanvasProjection nakładki i obliczyć dokładne współrzędne, w których należy zakotwiczyć punkty prawy górny i lewy dolny obiektu. Następnie możemy zmienić rozmiar <div>. W efekcie obraz zostanie dopasowany do ograniczeń określonych w konstruktorze nakładki.

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";
  }
}

Usuwanie nakładki niestandardowej

Dodaliśmy też metodę onRemove(), która pozwala całkowicie usunąć nakładkę z mapy.

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 as HTMLElement).removeChild(this.div);
    delete this.div;
  }
}

JavaScript

/**
 * 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.removeChild(this.div);
    delete this.div;
  }
}

Ukrywanie i wyświetlanie niestandardowej nakładki

Jeśli chcesz ukryć lub wyświetlić nakładkę, a nie tylko ją utworzyć lub usunąć, możesz zaimplementować własne metody hide()show(), aby dostosować widoczność nakładki. Możesz też odłączyć nakładkę od DOM mapy, ale ta operacja jest nieco droższa. Pamiętaj, że jeśli ponownie dołączysz nakładkę do DOM mapy, wywoła to ponownie metodę onAdd() nakładki.

W tym przykładzie do prototypu nakładki dodawane są metody hide()show(), które przełączają widoczność kontenera <div>. Dodatkowo dodaliśmy metodę toggleDOM(), która umożliwia dołączanie i odłączanie nakładki do mapy.

TypeScript

/**
 *  Set the visibility to 'hidden' or 'visible'.
 */
hide() {
  if (this.div) {
    this.div.style.visibility = "hidden";
  }
}

show() {
  if (this.div) {
    this.div.style.visibility = "visible";
  }
}

toggle() {
  if (this.div) {
    if (this.div.style.visibility === "hidden") {
      this.show();
    } else {
      this.hide();
    }
  }
}

toggleDOM(map: google.maps.Map) {
  if (this.getMap()) {
    this.setMap(null);
  } else {
    this.setMap(map);
  }
}

JavaScript

/**
 *  Set the visibility to 'hidden' or 'visible'.
 */
hide() {
  if (this.div) {
    this.div.style.visibility = "hidden";
  }
}
show() {
  if (this.div) {
    this.div.style.visibility = "visible";
  }
}
toggle() {
  if (this.div) {
    if (this.div.style.visibility === "hidden") {
      this.show();
    } else {
      this.hide();
    }
  }
}
toggleDOM(map) {
  if (this.getMap()) {
    this.setMap(null);
  } else {
    this.setMap(map);
  }
}

Dodawanie elementów sterujących przyciskami

Aby wywołać metody toggletoggleDom, do mapy dodano przyciski sterujące.

TypeScript

const toggleButton = document.createElement("button");

toggleButton.textContent = "Toggle";
toggleButton.classList.add("custom-map-control-button");

const toggleDOMButton = document.createElement("button");

toggleDOMButton.textContent = "Toggle DOM Attachment";
toggleDOMButton.classList.add("custom-map-control-button");

toggleButton.addEventListener("click", () => {
  overlay.toggle();
});

toggleDOMButton.addEventListener("click", () => {
  overlay.toggleDOM(map);
});

map.controls[google.maps.ControlPosition.TOP_RIGHT].push(toggleDOMButton);
map.controls[google.maps.ControlPosition.TOP_RIGHT].push(toggleButton);

JavaScript

const toggleButton = document.createElement("button");

toggleButton.textContent = "Toggle";
toggleButton.classList.add("custom-map-control-button");

const toggleDOMButton = document.createElement("button");

toggleDOMButton.textContent = "Toggle DOM Attachment";
toggleDOMButton.classList.add("custom-map-control-button");
toggleButton.addEventListener("click", () => {
  overlay.toggle();
});
toggleDOMButton.addEventListener("click", () => {
  overlay.toggleDOM(map);
});
map.controls[google.maps.ControlPosition.TOP_RIGHT].push(toggleDOMButton);
map.controls[google.maps.ControlPosition.TOP_RIGHT].push(toggleButton);

Pełny przykładowy kod

Poniżej znajduje się pełny przykładowy kod:

TypeScript

// This example adds hide() and show() methods to a custom overlay's prototype.
// These methods toggle the visibility of the container <div>.
// overlay to or from the map.

function initMap(): void {
  const map = new google.maps.Map(
    document.getElementById("map") as HTMLElement,
    {
      zoom: 11,
      center: { lat: 62.323907, lng: -150.109291 },
      mapTypeId: "satellite",
    }
  );

  const bounds = new google.maps.LatLngBounds(
    new google.maps.LatLng(62.281819, -150.287132),
    new google.maps.LatLng(62.400471, -150.005608)
  );

  // The photograph is courtesy of the U.S. Geological Survey.
  let image = "https://developers.google.com/maps/documentation/javascript/";

  image += "examples/full/images/talkeetna.png";

  /**
   * 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;
    }

    /**
     * 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() {
      // 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";
      }
    }

    /**
     * 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 as HTMLElement).removeChild(this.div);
        delete this.div;
      }
    }

    /**
     *  Set the visibility to 'hidden' or 'visible'.
     */
    hide() {
      if (this.div) {
        this.div.style.visibility = "hidden";
      }
    }

    show() {
      if (this.div) {
        this.div.style.visibility = "visible";
      }
    }

    toggle() {
      if (this.div) {
        if (this.div.style.visibility === "hidden") {
          this.show();
        } else {
          this.hide();
        }
      }
    }

    toggleDOM(map: google.maps.Map) {
      if (this.getMap()) {
        this.setMap(null);
      } else {
        this.setMap(map);
      }
    }
  }

  const overlay: USGSOverlay = new USGSOverlay(bounds, image);

  overlay.setMap(map);

  const toggleButton = document.createElement("button");

  toggleButton.textContent = "Toggle";
  toggleButton.classList.add("custom-map-control-button");

  const toggleDOMButton = document.createElement("button");

  toggleDOMButton.textContent = "Toggle DOM Attachment";
  toggleDOMButton.classList.add("custom-map-control-button");

  toggleButton.addEventListener("click", () => {
    overlay.toggle();
  });

  toggleDOMButton.addEventListener("click", () => {
    overlay.toggleDOM(map);
  });

  map.controls[google.maps.ControlPosition.TOP_RIGHT].push(toggleDOMButton);
  map.controls[google.maps.ControlPosition.TOP_RIGHT].push(toggleButton);
}

declare global {
  interface Window {
    initMap: () => void;
  }
}
window.initMap = initMap;

JavaScript

// This example adds hide() and show() methods to a custom overlay's prototype.
// These methods toggle the visibility of the container <div>.
// overlay to or from the map.
function initMap() {
  const map = new google.maps.Map(document.getElementById("map"), {
    zoom: 11,
    center: { lat: 62.323907, lng: -150.109291 },
    mapTypeId: "satellite",
  });
  const bounds = new google.maps.LatLngBounds(
    new google.maps.LatLng(62.281819, -150.287132),
    new google.maps.LatLng(62.400471, -150.005608),
  );
  // The photograph is courtesy of the U.S. Geological Survey.
  let image = "https://developers.google.com/maps/documentation/javascript/";

  image += "examples/full/images/talkeetna.png";
  /**
   * 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;
    }
    /**
     * 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() {
      // 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";
      }
    }
    /**
     * 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.removeChild(this.div);
        delete this.div;
      }
    }
    /**
     *  Set the visibility to 'hidden' or 'visible'.
     */
    hide() {
      if (this.div) {
        this.div.style.visibility = "hidden";
      }
    }
    show() {
      if (this.div) {
        this.div.style.visibility = "visible";
      }
    }
    toggle() {
      if (this.div) {
        if (this.div.style.visibility === "hidden") {
          this.show();
        } else {
          this.hide();
        }
      }
    }
    toggleDOM(map) {
      if (this.getMap()) {
        this.setMap(null);
      } else {
        this.setMap(map);
      }
    }
  }

  const overlay = new USGSOverlay(bounds, image);

  overlay.setMap(map);

  const toggleButton = document.createElement("button");

  toggleButton.textContent = "Toggle";
  toggleButton.classList.add("custom-map-control-button");

  const toggleDOMButton = document.createElement("button");

  toggleDOMButton.textContent = "Toggle DOM Attachment";
  toggleDOMButton.classList.add("custom-map-control-button");
  toggleButton.addEventListener("click", () => {
    overlay.toggle();
  });
  toggleDOMButton.addEventListener("click", () => {
    overlay.toggleDOM(map);
  });
  map.controls[google.maps.ControlPosition.TOP_RIGHT].push(toggleDOMButton);
  map.controls[google.maps.ControlPosition.TOP_RIGHT].push(toggleButton);
}

window.initMap = initMap;

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;
}

.custom-map-control-button {
  background-color: #fff;
  border: 0;
  border-radius: 2px;
  box-shadow: 0 1px 4px -1px rgba(0, 0, 0, 0.3);
  margin: 10px;
  padding: 0 0.5em;
  font: 400 18px Roboto, Arial, sans-serif;
  overflow: hidden;
  height: 40px;
  cursor: pointer;
}
.custom-map-control-button:hover {
  background: rgb(235, 235, 235);
}

HTML

<html>
  <head>
    <title>Showing/Hiding Overlays</title>

    <link rel="stylesheet" type="text/css" href="./style.css" />
    <script type="module" src="./index.js"></script>
  </head>
  <body>
    <div id="map"></div>

    <!-- 
      The `defer` attribute causes the script to execute after the full HTML
      document has been parsed. For non-blocking uses, avoiding race conditions,
      and consistent behavior across browsers, consider loading using Promises. See
      https://developers.google.com/maps/documentation/javascript/load-maps-js-api
      for more information.
      -->
    <script
      src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB41DRUbKWJHPxaFjMAwdrzWzbVKartNGg&callback=initMap&v=weekly"
      defer
    ></script>
  </body>
</html>

Wypróbuj próbkę