رونهادهای سفارشی

پلاتفرم را انتخاب کنید: Android iOS JavaScript

مقدمه

لایه‌های رویی اشیایی روی نقشه هستند که به مختصات طول/عرض جغرافیایی گره خورده‌اند، بنابراین وقتی نقشه را می‌کشید یا بزرگ‌نمایی می‌کنید، آن‌ها نیز حرکت می‌کنند. برای اطلاعات درباره انواع هم‌پوشانی ازپیش تعریف‌شده، ترسیم روی نقشه را ببینید.

‫Maps JavaScript API یک کلاس OverlayView برای ایجاد هم‌پوشانی‌های سفارشی خودتان ارائه می‌دهد. OverlayView کلاس پایه‌ای است که چندین روش را ارائه می‌دهد که باید هنگام ایجاد هم‌پوشانی‌هایتان پیاده‌سازی کنید. کلاس همچنین چند روش ارائه می‌دهد که ترجمه بین مختصات صفحه‌نمایش و مکان‌های روی نقشه را ممکن می‌سازد.

افزودن رونهاد سفارشی

در اینجا خلاصه‌ای از مراحل لازم برای ایجاد یک هم‌پوشانی سفارشی آورده شده است:

  • prototype شیء رونهاد سفارشی‌تان را روی نمونه جدیدی از google.maps.OverlayView() تنظیم کنید. در واقع، این کار کلاس رونهاد را زیرکلاس خواهد کرد.
  • سازنده‌ای برای هم‌پوشانی سفارشی‌تان ایجاد کنید و پارامترهای مقداردهی اولیه را تنظیم کنید.
  • روش onAdd() را در نمونه اولیه خود پیاده‌سازی کنید و رونهاد را به نقشه پیوست کنید. وقتی نقشه برای پیوست شدن هم‌پوشانی آماده باشد، OverlayView.onAdd() فراخوانده می‌شود.
  • روش draw() را در نمونه اولیه خود پیاده‌سازی کنید و نمایش بصری شیء خود را مدیریت کنید. وقتی شیء برای اولین‌بار نمایش داده می‌شود، OverlayView.draw() فراخوانی خواهد شد.
  • همچنین باید روش onRemove() را برای پاک‌سازی هر عنصری که در هم‌پوشانی اضافه کرده‌اید پیاده‌سازی کنید.

در زیر جزئیات بیشتری درباره هر مرحله ارائه شده است. می‌توانید نمونه کد کامل و فعال را ببینید: مشاهده نمونه کد.

زیرکلاس کردن رونهاد

مثال زیر از OverlayView برای ایجاد رونهاد تصویر ساده استفاده می‌کند.

اکنون سازنده‌ای برای کلاس USGSOverlay ایجاد می‌کنیم و پارامترهای ارسال‌شده را به‌عنوان ویژگی‌های شیء جدید مقداردهی اولیه می‌کنیم.

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

هنوز نمی‌توانیم این هم‌پوشانی را در سازنده هم‌پوشانی به نقشه پیوست کنیم. ابتدا باید مطمئن شویم که همه قاب‌های نقشه دردسترس هستند، زیرا آن‌ها ترتیب نمایش اشیاء را روی نقشه مشخص می‌کنند. میانای برنامه‌سازی کاربردی روش کمکی‌ای ارائه می‌دهد که نشان می‌دهد این اتفاق افتاده است. در بخش بعدی به این روش خواهیم پرداخت.

مقداردهی اولیه برهم‌گذاری

وقتی هم‌پوشانی برای اولین‌بار نمونه‌سازی می‌شود و آماده نمایش است، باید آن را ازطریق DOM مرورگر به نقشه متصل کنیم. میانای برنامه‌سازی کاربردی نشان می‌دهد که هم‌پوشانی با فراخوانی روش onAdd() هم‌پوشانی به نقشه اضافه شده است. برای مدیریت این روش، یک <div> ایجاد می‌کنیم تا تصویرمان را نگه دارد، یک عنصر <img> اضافه می‌کنیم، آن را به <div> متصل می‌کنیم، و سپس هم‌پوشانی را به یکی از قاب‌های نقشه متصل می‌کنیم. قاب گره‌ای در درخت DOM است.

قاب‌ها، از نوع MapPanes، ترتیب انباشتگی لایه‌های مختلف را روی نقشه مشخص می‌کنند. قاب‌های زیر دردسترس هستند و به‌ترتیبی که از پایین به بالا روی هم قرار گرفته‌اند شماره‌گذاری شده‌اند:

  • mapPane پایین‌ترین قاب است و بالای کاشی‌ها قرار دارد. ممکن است رویدادهای DOM را دریافت نکند. (قاب 0).
  • overlayLayer حاوی چندخطی‌ها، چندضلعی‌ها، رونهادهای زمین، و رونهادهای لایه کاشی است. ممکن است رویدادهای DOM را دریافت نکند. (قاب ۱).
  • ‫markerLayer حاوی نشانگر است. ممکن است رویدادهای DOM را دریافت نکند. (قاب ۲).
  • overlayMouseTarget حاوی عناصری است که رویدادهای DOM را دریافت می‌کنند. (قاب ۳).
  • floatPane حاوی پنجره اطلاعات است. این نشانگر بالاتر از همه لایه‌های نقشه است. (قاب ۴).

چون تصویر ما «پوشش زمین» است، از قاب overlayLayer استفاده می‌کنیم. وقتی آن قاب را داشته باشیم، شیء خود را به‌عنوان فرزند به آن متصل می‌کنیم.

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

ترسیم رونهاد

توجه داشته باشید که ما هیچ نمایش بصری خاصی را در کد بالا فراخوانی نکرده‌ایم. هرگاه API نیاز داشته باشد که پوشش را روی نقشه ترسیم کند، ازجمله زمانی که برای اولین‌بار اضافه می‌شود، روش draw() جداگانه‌ای را روی پوشش فراخوانی می‌کند.

بنابراین، این روش draw() را پیاده‌سازی می‌کنیم، MapCanvasProjection هم‌پوشانی را بااستفاده از getProjection() بازیابی می‌کنیم، و مختصات دقیق را برای لنگر کردن نقاط بالا سمت راست و پایین سمت چپ شیء محاسبه می‌کنیم. سپس می‌توانیم اندازه <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";
  }
}

برداشتن هم‌پوشانی سفارشی

همچنین یک روش onRemove() اضافه می‌کنیم تا لایه رویی را به‌طور کامل از نقشه برداریم.

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

پنهان کردن و نمایش دادن هم‌پوشانی سفارشی

اگر می‌خواهید به‌جای ایجاد یا برداشتن ساده هم‌پوشانی، آن را پنهان کنید یا نمایش دهید، می‌توانید روش‌های hide() و show() خودتان را برای تنظیم نمایان بودن هم‌پوشانی پیاده‌سازی کنید. یا می‌توانید هم‌پوشانی را از DOM نقشه جدا کنید، هرچند این عملیات کمی پرهزینه‌تر است. توجه داشته باشید که اگر سپس هم‌پوشانی را دوباره به DOM نقشه پیوست کنید، روش onAdd() هم‌پوشانی را دوباره فراخوانی خواهد کرد.

مثال زیر روش‌های hide() و show() را به نمونه اولیه رونهاد اضافه می‌کند که رؤیت‌پذیری ظرف <div> را تغییر می‌دهد. علاوه‌براین، روش toggleDOM() را اضافه می‌کنیم که هم‌پوشانی را به نقشه متصل یا از آن جدا می‌کند.

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

افزودن کنترل‌های دکمه‌ای

برای راه‌اندازی روش‌های toggle و toggleDom، کنترل‌های دکمه به نقشه اضافه شده است.

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

کدگذاری نمونه کامل

کد نمونه کامل در زیر آمده است:

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>